Files
stack/packages/mosaic/src/commands/interaction.ts
jason.woltje 0b621660c8
Some checks failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
feat(tess): wire durable interaction surfaces (#732)
2026-07-13 10:05:29 +00:00

233 lines
6.9 KiB
TypeScript

import { randomUUID } from 'node:crypto';
import type { Command } from 'commander';
import { withAuth } from './with-auth.js';
import {
attachInteractionSession,
enrollInteractionSession,
fetchInteractionHealth,
fetchInteractionSessions,
fetchInteractionStatus,
fetchInteractionTree,
recoverInteractionSession,
sendInteractionMessage,
stopInteractionSession,
streamInteractionSession,
} from '../tui/gateway-api.js';
interface InteractionOptions {
gateway: string;
agent?: string;
correlationId?: string;
}
function configuredAgentName(options: InteractionOptions): string {
const name = options.agent?.trim() || process.env['MOSAIC_AGENT_NAME']?.trim();
if (!name) throw new Error('Interaction agent name is required (--agent or MOSAIC_AGENT_NAME)');
return name;
}
async function authenticatedGateway(options: InteractionOptions) {
return withAuth(options.gateway);
}
async function authRequest(options: InteractionOptions) {
const auth = await authenticatedGateway(options);
return {
gateway: auth.gateway,
cookie: auth.cookie,
agentName: configuredAgentName(options),
correlationId: options.correlationId?.trim() || randomUUID(),
};
}
function options(command: Command): Command {
return command
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
.option('--agent <name>', 'Configured interaction agent name')
.option('--correlation-id <id>', 'Correlation ID for audit tracing');
}
function print(value: unknown): void {
console.log(JSON.stringify(value, null, 2));
}
/**
* Generic interaction command surface. The deployed instance name is data
* (`--agent` / MOSAIC_AGENT_NAME), not a source-code command literal.
*/
export function registerInteractionCommand(program: Command): Command {
const command = program
.command('interaction')
.description('Operate a configured durable interaction agent')
.configureHelp({ sortSubcommands: true });
options(
command
.command('status')
.description('Show credential-safe effective policy and provider status'),
).action(async (opts: InteractionOptions) => {
const auth = await authenticatedGateway(opts);
print(await fetchInteractionStatus(auth.gateway, auth.cookie));
});
options(
command
.command('health')
.description('Show readiness without configuration or credential data'),
).action(async (opts: InteractionOptions) => {
print(await fetchInteractionHealth(opts.gateway));
});
options(
command
.command('sessions <provider>')
.description('List provider sessions visible to this actor'),
).action(async (provider: string, opts: InteractionOptions) => {
const request = await authRequest(opts);
print(
await fetchInteractionSessions(request.gateway, request.cookie, {
...request,
providerId: provider,
}),
);
});
options(
command.command('tree <provider>').description('Show the provider session hierarchy'),
).action(async (provider: string, opts: InteractionOptions) => {
const request = await authRequest(opts);
print(
await fetchInteractionTree(request.gateway, request.cookie, {
...request,
providerId: provider,
}),
);
});
options(
command
.command('enroll <sessionId> <provider> <runtimeSessionId>')
.description('Bind an authorized runtime session to a durable conversation handle'),
).action(
async (
sessionId: string,
providerId: string,
runtimeSessionId: string,
opts: InteractionOptions,
) => {
const request = await authRequest(opts);
print(
await enrollInteractionSession(request.gateway, request.cookie, {
...request,
sessionId,
providerId,
runtimeSessionId,
}),
);
},
);
options(
command
.command('attach <sessionId>')
.description('Create a scoped read or write attachment and stream the session'),
)
.option('--control', 'Request control mode (provider policy may deny it)')
.action(async (sessionId: string, opts: InteractionOptions & { control?: boolean }) => {
const request = await authRequest(opts);
const attachment = await attachInteractionSession(request.gateway, request.cookie, {
...request,
sessionId,
mode: opts.control ? 'control' : 'read',
});
print(attachment);
for await (const event of streamInteractionSession(request.gateway, request.cookie, {
...request,
sessionId,
})) {
print(event);
}
});
const send = options(
command
.command('send <sessionId> <message>')
.description('Durably queue and dispatch a message'),
)
.option('--idempotency-key <key>', 'Stable idempotency key')
.action(
async (
sessionId: string,
message: string,
opts: InteractionOptions & { idempotencyKey?: string },
) => {
const request = await authRequest(opts);
print(
await sendInteractionMessage(request.gateway, request.cookie, {
...request,
sessionId,
content: message,
idempotencyKey: opts.idempotencyKey?.trim() || randomUUID(),
}),
);
},
);
void send;
options(
command
.command('chat <sessionId> <message>')
.description('Send a chat message through the durable interaction session'),
)
.option('--idempotency-key <key>', 'Stable idempotency key')
.action(
async (
sessionId: string,
message: string,
opts: InteractionOptions & { idempotencyKey?: string },
) => {
const request = await authRequest(opts);
print(
await sendInteractionMessage(request.gateway, request.cookie, {
...request,
sessionId,
content: message,
idempotencyKey: opts.idempotencyKey?.trim() || randomUUID(),
}),
);
},
);
options(
command
.command('stop <sessionId>')
.description('Terminate a session using a one-time exact-action approval'),
)
.requiredOption('--approval <ref>', 'Durable exact-action approval reference')
.action(async (sessionId: string, opts: InteractionOptions & { approval: string }) => {
const request = await authRequest(opts);
print(
await stopInteractionSession(request.gateway, request.cookie, {
...request,
sessionId,
approvalRef: opts.approval,
}),
);
});
options(
command
.command('recover <sessionId>')
.description(
'Recover durable inbox/checkpoint state without replaying ambiguous outbox effects',
),
).action(async (sessionId: string, opts: InteractionOptions) => {
const request = await authRequest(opts);
print(
await recoverInteractionSession(request.gateway, request.cookie, { ...request, sessionId }),
);
});
return command;
}