233 lines
6.9 KiB
TypeScript
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;
|
|
}
|