feat(cli): add sessions list/resume/destroy subcommands (#130)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Add `mosaic sessions list`, `mosaic sessions resume <id>`, and `mosaic sessions destroy <id>` CLI subcommands that interact with the existing gateway `/api/sessions` endpoints. Also adds `fetchSessions`, `deleteSession`, and related DTO interfaces to `gateway-api.ts`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,117 @@ program
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ─── sessions ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const sessionsCmd = program.command('sessions').description('Manage active agent sessions');
|
||||||
|
|
||||||
|
sessionsCmd
|
||||||
|
.command('list')
|
||||||
|
.description('List active agent sessions')
|
||||||
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:4000')
|
||||||
|
.action(async (opts: { gateway: string }) => {
|
||||||
|
const { loadSession, validateSession } = await import('./auth.js');
|
||||||
|
const { fetchSessions } = await import('./tui/gateway-api.js');
|
||||||
|
|
||||||
|
const session = loadSession(opts.gateway);
|
||||||
|
if (!session) {
|
||||||
|
console.error('Not signed in. Run `mosaic login` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await validateSession(opts.gateway, session.cookie);
|
||||||
|
if (!valid) {
|
||||||
|
console.error('Session expired. Run `mosaic login` again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetchSessions(opts.gateway, session.cookie);
|
||||||
|
if (result.total === 0) {
|
||||||
|
console.log('No active sessions.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Active sessions (${result.total}):\n`);
|
||||||
|
for (const s of result.sessions) {
|
||||||
|
const created = new Date(s.createdAt).toLocaleString();
|
||||||
|
const durationSec = Math.round(s.durationMs / 1000);
|
||||||
|
console.log(` ID: ${s.id}`);
|
||||||
|
console.log(` Model: ${s.provider}/${s.modelId}`);
|
||||||
|
console.log(` Created: ${created}`);
|
||||||
|
console.log(` Prompts: ${s.promptCount}`);
|
||||||
|
console.log(` Duration: ${durationSec}s`);
|
||||||
|
if (s.channels.length > 0) {
|
||||||
|
console.log(` Channels: ${s.channels.join(', ')}`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err instanceof Error ? err.message : String(err));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sessionsCmd
|
||||||
|
.command('resume <id>')
|
||||||
|
.description('Resume an existing agent session in the TUI')
|
||||||
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:4000')
|
||||||
|
.action(async (id: string, opts: { gateway: string }) => {
|
||||||
|
const { loadSession, validateSession } = await import('./auth.js');
|
||||||
|
|
||||||
|
const session = loadSession(opts.gateway);
|
||||||
|
if (!session) {
|
||||||
|
console.error('Not signed in. Run `mosaic login` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await validateSession(opts.gateway, session.cookie);
|
||||||
|
if (!valid) {
|
||||||
|
console.error('Session expired. Run `mosaic login` again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { render } = await import('ink');
|
||||||
|
const React = await import('react');
|
||||||
|
const { TuiApp } = await import('./tui/app.js');
|
||||||
|
|
||||||
|
render(
|
||||||
|
React.createElement(TuiApp, {
|
||||||
|
gatewayUrl: opts.gateway,
|
||||||
|
conversationId: id,
|
||||||
|
sessionCookie: session.cookie,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
sessionsCmd
|
||||||
|
.command('destroy <id>')
|
||||||
|
.description('Terminate an active agent session')
|
||||||
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:4000')
|
||||||
|
.action(async (id: string, opts: { gateway: string }) => {
|
||||||
|
const { loadSession, validateSession } = await import('./auth.js');
|
||||||
|
const { deleteSession } = await import('./tui/gateway-api.js');
|
||||||
|
|
||||||
|
const session = loadSession(opts.gateway);
|
||||||
|
if (!session) {
|
||||||
|
console.error('Not signed in. Run `mosaic login` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await validateSession(opts.gateway, session.cookie);
|
||||||
|
if (!valid) {
|
||||||
|
console.error('Session expired. Run `mosaic login` again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteSession(opts.gateway, session.cookie, id);
|
||||||
|
console.log(`Session ${id} destroyed.`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err instanceof Error ? err.message : String(err));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ─── prdy ───────────────────────────────────────────────────────────────
|
// ─── prdy ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const prdyWrapper = buildPrdyCli();
|
const prdyWrapper = buildPrdyCli();
|
||||||
|
|||||||
@@ -15,6 +15,21 @@ export interface ProviderInfo {
|
|||||||
models: ModelInfo[];
|
models: ModelInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionInfo {
|
||||||
|
id: string;
|
||||||
|
provider: string;
|
||||||
|
modelId: string;
|
||||||
|
createdAt: string;
|
||||||
|
promptCount: number;
|
||||||
|
channels: string[];
|
||||||
|
durationMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionListResult {
|
||||||
|
sessions: SessionInfo[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the list of available models from the gateway.
|
* Fetch the list of available models from the gateway.
|
||||||
* Returns an empty array on network or auth errors so the TUI can still function.
|
* Returns an empty array on network or auth errors so the TUI can still function.
|
||||||
@@ -60,3 +75,40 @@ export async function fetchProviders(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the list of active agent sessions from the gateway.
|
||||||
|
* Throws on network or auth errors.
|
||||||
|
*/
|
||||||
|
export async function fetchSessions(
|
||||||
|
gatewayUrl: string,
|
||||||
|
sessionCookie: string,
|
||||||
|
): Promise<SessionListResult> {
|
||||||
|
const res = await fetch(`${gatewayUrl}/api/sessions`, {
|
||||||
|
headers: { Cookie: sessionCookie, Origin: gatewayUrl },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`Failed to list sessions (${res.status}): ${body}`);
|
||||||
|
}
|
||||||
|
return (await res.json()) as SessionListResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy (terminate) an agent session on the gateway.
|
||||||
|
* Throws on network or auth errors.
|
||||||
|
*/
|
||||||
|
export async function deleteSession(
|
||||||
|
gatewayUrl: string,
|
||||||
|
sessionCookie: string,
|
||||||
|
sessionId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const res = await fetch(`${gatewayUrl}/api/sessions/${encodeURIComponent(sessionId)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { Cookie: sessionCookie, Origin: gatewayUrl },
|
||||||
|
});
|
||||||
|
if (!res.ok && res.status !== 204) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`Failed to destroy session (${res.status}): ${body}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user