proofscan 0.10.62 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -0
- package/README.md +2 -0
- package/dist/a2a/agent-card.d.ts +2 -0
- package/dist/a2a/agent-card.d.ts.map +1 -1
- package/dist/a2a/agent-card.js +2 -2
- package/dist/a2a/agent-card.js.map +1 -1
- package/dist/a2a/client.d.ts +74 -12
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +228 -29
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/normalizer.d.ts +4 -0
- package/dist/a2a/normalizer.d.ts.map +1 -1
- package/dist/a2a/normalizer.js +7 -4
- package/dist/a2a/normalizer.js.map +1 -1
- package/dist/a2a/session-manager.d.ts +81 -0
- package/dist/a2a/session-manager.d.ts.map +1 -0
- package/dist/a2a/session-manager.js +176 -0
- package/dist/a2a/session-manager.js.map +1 -0
- package/dist/a2a/types.d.ts +60 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +35 -10
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +12 -10
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/connectors.js +2 -2
- package/dist/commands/connectors.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/plans.js +1 -1
- package/dist/commands/plans.js.map +1 -1
- package/dist/commands/record.js +5 -4
- package/dist/commands/record.js.map +1 -1
- package/dist/commands/rpc.d.ts.map +1 -1
- package/dist/commands/rpc.js +90 -28
- package/dist/commands/rpc.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +8 -10
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +11 -10
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/sessions.js +2 -2
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/summary.d.ts.map +1 -1
- package/dist/commands/summary.js +4 -2
- package/dist/commands/summary.js.map +1 -1
- package/dist/commands/task.d.ts +14 -0
- package/dist/commands/task.d.ts.map +1 -0
- package/dist/commands/task.js +520 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +56 -1
- package/dist/db/connection.js.map +1 -1
- package/dist/db/events-store.d.ts +307 -8
- package/dist/db/events-store.d.ts.map +1 -1
- package/dist/db/events-store.js +620 -26
- package/dist/db/events-store.js.map +1 -1
- package/dist/db/proofs-store.d.ts +8 -1
- package/dist/db/proofs-store.d.ts.map +1 -1
- package/dist/db/proofs-store.js +18 -8
- package/dist/db/proofs-store.js.map +1 -1
- package/dist/db/schema.d.ts +15 -3
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +150 -5
- package/dist/db/schema.js.map +1 -1
- package/dist/db/tool-analysis.d.ts +15 -3
- package/dist/db/tool-analysis.d.ts.map +1 -1
- package/dist/db/tool-analysis.js +35 -17
- package/dist/db/tool-analysis.js.map +1 -1
- package/dist/db/types.d.ts +64 -1
- package/dist/db/types.d.ts.map +1 -1
- package/dist/filter/fields.d.ts.map +1 -1
- package/dist/filter/fields.js +22 -0
- package/dist/filter/fields.js.map +1 -1
- package/dist/filter/parser.js +2 -2
- package/dist/filter/parser.js.map +1 -1
- package/dist/filter/types.d.ts +1 -1
- package/dist/filter/types.d.ts.map +1 -1
- package/dist/html/analytics.test.ts +682 -0
- package/dist/html/analytics.ts +499 -0
- package/dist/html/browser.ts +39 -0
- package/dist/html/index.ts +97 -0
- package/dist/html/rpc-inspector.test.ts +529 -0
- package/dist/html/rpc-inspector.ts +1700 -0
- package/dist/html/templates.js +4 -4
- package/dist/html/templates.js.map +1 -1
- package/dist/html/templates.test.ts +861 -0
- package/dist/html/templates.ts +3163 -0
- package/dist/html/trace-viewer.html +624 -0
- package/dist/html/types.d.ts +3 -3
- package/dist/html/types.d.ts.map +1 -1
- package/dist/html/types.ts +491 -0
- package/dist/html/utils.ts +107 -0
- package/dist/monitor/data/connectors.d.ts.map +1 -1
- package/dist/monitor/data/connectors.js +113 -8
- package/dist/monitor/data/connectors.js.map +1 -1
- package/dist/monitor/data/popl.js +2 -2
- package/dist/monitor/data/popl.js.map +1 -1
- package/dist/monitor/routes/api.js +2 -2
- package/dist/monitor/routes/api.js.map +1 -1
- package/dist/monitor/routes/connectors.js +15 -15
- package/dist/monitor/routes/connectors.js.map +1 -1
- package/dist/monitor/routes/popl.js +5 -5
- package/dist/monitor/routes/popl.js.map +1 -1
- package/dist/monitor/templates/components.js +2 -2
- package/dist/monitor/templates/components.js.map +1 -1
- package/dist/monitor/templates/popl.js +4 -4
- package/dist/monitor/templates/popl.js.map +1 -1
- package/dist/monitor/types.d.ts +2 -2
- package/dist/monitor/types.d.ts.map +1 -1
- package/dist/proxy/bridge-utils.d.ts +41 -0
- package/dist/proxy/bridge-utils.d.ts.map +1 -0
- package/dist/proxy/bridge-utils.js +60 -0
- package/dist/proxy/bridge-utils.js.map +1 -0
- package/dist/proxy/ipc-client.d.ts.map +1 -1
- package/dist/proxy/ipc-client.js +1 -2
- package/dist/proxy/ipc-client.js.map +1 -1
- package/dist/proxy/ipc-server.d.ts.map +1 -1
- package/dist/proxy/ipc-server.js +4 -2
- package/dist/proxy/ipc-server.js.map +1 -1
- package/dist/proxy/mcp-server.d.ts +31 -0
- package/dist/proxy/mcp-server.d.ts.map +1 -1
- package/dist/proxy/mcp-server.js +393 -4
- package/dist/proxy/mcp-server.js.map +1 -1
- package/dist/proxy/types.d.ts +95 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/secrets/management.d.ts +2 -2
- package/dist/secrets/management.d.ts.map +1 -1
- package/dist/secrets/management.js +7 -7
- package/dist/secrets/management.js.map +1 -1
- package/dist/shell/completer.d.ts.map +1 -1
- package/dist/shell/completer.js +16 -0
- package/dist/shell/completer.js.map +1 -1
- package/dist/shell/context-applicator.d.ts.map +1 -1
- package/dist/shell/context-applicator.js +32 -0
- package/dist/shell/context-applicator.js.map +1 -1
- package/dist/shell/filter-mappers.d.ts +5 -1
- package/dist/shell/filter-mappers.d.ts.map +1 -1
- package/dist/shell/filter-mappers.js +12 -0
- package/dist/shell/filter-mappers.js.map +1 -1
- package/dist/shell/find-command.js +13 -13
- package/dist/shell/find-command.js.map +1 -1
- package/dist/shell/inscribe-commands.js +5 -5
- package/dist/shell/inscribe-commands.js.map +1 -1
- package/dist/shell/pager/less-pager.d.ts +1 -1
- package/dist/shell/pager/less-pager.d.ts.map +1 -1
- package/dist/shell/pager/less-pager.js +5 -2
- package/dist/shell/pager/less-pager.js.map +1 -1
- package/dist/shell/pager/more-pager.d.ts +1 -1
- package/dist/shell/pager/more-pager.d.ts.map +1 -1
- package/dist/shell/pager/more-pager.js +3 -2
- package/dist/shell/pager/more-pager.js.map +1 -1
- package/dist/shell/pager/renderer.d.ts.map +1 -1
- package/dist/shell/pager/renderer.js +66 -15
- package/dist/shell/pager/renderer.js.map +1 -1
- package/dist/shell/pager/types.d.ts +5 -2
- package/dist/shell/pager/types.d.ts.map +1 -1
- package/dist/shell/pager/utils.d.ts +5 -2
- package/dist/shell/pager/utils.d.ts.map +1 -1
- package/dist/shell/pager/utils.js +14 -17
- package/dist/shell/pager/utils.js.map +1 -1
- package/dist/shell/pipeline-types.d.ts +12 -4
- package/dist/shell/pipeline-types.d.ts.map +1 -1
- package/dist/shell/ref-commands.js +7 -7
- package/dist/shell/ref-commands.js.map +1 -1
- package/dist/shell/ref-resolver.d.ts +15 -15
- package/dist/shell/ref-resolver.d.ts.map +1 -1
- package/dist/shell/ref-resolver.js +34 -20
- package/dist/shell/ref-resolver.js.map +1 -1
- package/dist/shell/repl.d.ts +25 -0
- package/dist/shell/repl.d.ts.map +1 -1
- package/dist/shell/repl.js +285 -51
- package/dist/shell/repl.js.map +1 -1
- package/dist/shell/router-commands.d.ts +30 -0
- package/dist/shell/router-commands.d.ts.map +1 -1
- package/dist/shell/router-commands.js +1011 -62
- package/dist/shell/router-commands.js.map +1 -1
- package/dist/shell/selector.d.ts +1 -1
- package/dist/shell/selector.d.ts.map +1 -1
- package/dist/shell/selector.js +1 -1
- package/dist/shell/selector.js.map +1 -1
- package/dist/shell/types.d.ts.map +1 -1
- package/dist/shell/types.js +3 -1
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/where-command.d.ts.map +1 -1
- package/dist/shell/where-command.js +19 -3
- package/dist/shell/where-command.js.map +1 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +7 -1
- package/dist/utils/output.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* for exploring connectors, sessions, and RPC calls.
|
|
6
6
|
*/
|
|
7
7
|
import { PIPELINE_SESSION_LIMIT } from './types.js';
|
|
8
|
+
import { AgentCacheStore } from '../db/agent-cache-store.js';
|
|
9
|
+
import { A2AClient } from '../a2a/client.js';
|
|
8
10
|
import { printSuccess, printError, printInfo, shortenSessionId } from './prompt.js';
|
|
11
|
+
import ora from 'ora';
|
|
9
12
|
import { selectSession, canInteract } from './selector.js';
|
|
10
13
|
import { EventLineStore } from '../eventline/store.js';
|
|
11
14
|
import { EventsStore } from '../db/events-store.js';
|
|
12
15
|
import { ConfigManager } from '../config/index.js';
|
|
16
|
+
import { TargetsStore } from '../db/targets-store.js';
|
|
13
17
|
import { setCurrentSession, clearCurrentSession, formatRelativeTime } from '../utils/index.js';
|
|
18
|
+
import { createA2ASessionManager } from '../a2a/session-manager.js';
|
|
19
|
+
import { randomUUID } from 'crypto';
|
|
14
20
|
import { createRefFromContext, refToJson, parseRef, isRef, RefResolver, createRefDataProvider, } from './ref-resolver.js';
|
|
15
21
|
// ProtoType is imported from types.ts
|
|
16
22
|
/**
|
|
@@ -36,9 +42,10 @@ export function detectProto(store, sessionId) {
|
|
|
36
42
|
if (hasMcpInit || hasMcpTools) {
|
|
37
43
|
return 'mcp';
|
|
38
44
|
}
|
|
39
|
-
// A2A detection: a2a
|
|
45
|
+
// A2A detection: a2a.*, agent.*, message/*, or tasks/* methods
|
|
40
46
|
for (const method of allMethods) {
|
|
41
|
-
if (method.startsWith('a2a.') || method.startsWith('agent.')
|
|
47
|
+
if (method.startsWith('a2a.') || method.startsWith('agent.') ||
|
|
48
|
+
method.startsWith('message/') || method.startsWith('tasks/')) {
|
|
42
49
|
return 'a2a';
|
|
43
50
|
}
|
|
44
51
|
}
|
|
@@ -108,26 +115,26 @@ function savePreviousLocation(context) {
|
|
|
108
115
|
*
|
|
109
116
|
* @returns true if a session was selected, false otherwise
|
|
110
117
|
*/
|
|
111
|
-
async function selectSessionFromMatches(matches, prefix, context, store,
|
|
118
|
+
async function selectSessionFromMatches(matches, prefix, context, store, targetId) {
|
|
112
119
|
if (matches.length === 1) {
|
|
113
120
|
savePreviousLocation(context);
|
|
114
121
|
context.session = matches[0].session_id;
|
|
115
|
-
context.connector =
|
|
122
|
+
context.connector = targetId;
|
|
116
123
|
context.proto = detectProto(store, matches[0].session_id);
|
|
117
|
-
setCurrentSession(matches[0].session_id,
|
|
118
|
-
printSuccess(`→ /${
|
|
124
|
+
setCurrentSession(matches[0].session_id, targetId);
|
|
125
|
+
printSuccess(`→ /${targetId}/${shortenSessionId(matches[0].session_id)}`);
|
|
119
126
|
return true;
|
|
120
127
|
}
|
|
121
128
|
if (canInteract()) {
|
|
122
129
|
printInfo(`Multiple sessions match "${prefix}". Select one:`);
|
|
123
|
-
const selected = await selectSession(matches.slice(0, MAX_INTERACTIVE_OPTIONS).map(s => ({ id: s.session_id, connector_id: s.
|
|
130
|
+
const selected = await selectSession(matches.slice(0, MAX_INTERACTIVE_OPTIONS).map(s => ({ id: s.session_id, connector_id: s.target_id })));
|
|
124
131
|
if (selected) {
|
|
125
132
|
savePreviousLocation(context);
|
|
126
133
|
context.session = selected;
|
|
127
|
-
context.connector =
|
|
134
|
+
context.connector = targetId;
|
|
128
135
|
context.proto = detectProto(store, selected);
|
|
129
|
-
setCurrentSession(selected,
|
|
130
|
-
printSuccess(`→ /${
|
|
136
|
+
setCurrentSession(selected, targetId);
|
|
137
|
+
printSuccess(`→ /${targetId}/${shortenSessionId(selected)}`);
|
|
131
138
|
return true;
|
|
132
139
|
}
|
|
133
140
|
}
|
|
@@ -242,7 +249,7 @@ export async function handleCc(args, context, configPath) {
|
|
|
242
249
|
printInfo('Already at session context');
|
|
243
250
|
return;
|
|
244
251
|
}
|
|
245
|
-
// Get latest session (optionally filtered by current
|
|
252
|
+
// Get latest session (optionally filtered by current target)
|
|
246
253
|
const sessions = store.getSessions(context.connector, 1);
|
|
247
254
|
if (sessions.length === 0) {
|
|
248
255
|
if (context.connector) {
|
|
@@ -281,44 +288,44 @@ export async function handleCc(args, context, configPath) {
|
|
|
281
288
|
// Navigate based on ref kind
|
|
282
289
|
if (ref.kind === 'rpc') {
|
|
283
290
|
// For RPC refs, navigate to the containing session
|
|
284
|
-
if (!ref.session || !ref.
|
|
291
|
+
if (!ref.session || !ref.target) {
|
|
285
292
|
printError(`Cannot navigate to RPC reference: missing session/connector`);
|
|
286
293
|
printInfo(`Use: show ${arg} to view RPC details`);
|
|
287
294
|
return;
|
|
288
295
|
}
|
|
289
296
|
savePreviousLocation(context);
|
|
290
|
-
context.connector = ref.
|
|
297
|
+
context.connector = ref.target;
|
|
291
298
|
context.session = ref.session;
|
|
292
299
|
context.proto = detectProto(store, ref.session);
|
|
293
|
-
setCurrentSession(ref.session, ref.
|
|
294
|
-
printSuccess(`→ /${ref.
|
|
300
|
+
setCurrentSession(ref.session, ref.target);
|
|
301
|
+
printSuccess(`→ /${ref.target}/${shortenSessionId(ref.session)}`);
|
|
295
302
|
printInfo(`(navigated to session containing RPC)`);
|
|
296
303
|
return;
|
|
297
304
|
}
|
|
298
305
|
if (ref.kind === 'session') {
|
|
299
|
-
if (!ref.session || !ref.
|
|
306
|
+
if (!ref.session || !ref.target) {
|
|
300
307
|
printError(`Invalid session reference: missing session/connector`);
|
|
301
308
|
return;
|
|
302
309
|
}
|
|
303
310
|
savePreviousLocation(context);
|
|
304
|
-
context.connector = ref.
|
|
311
|
+
context.connector = ref.target;
|
|
305
312
|
context.session = ref.session;
|
|
306
313
|
context.proto = detectProto(store, ref.session);
|
|
307
|
-
setCurrentSession(ref.session, ref.
|
|
308
|
-
printSuccess(`→ /${ref.
|
|
314
|
+
setCurrentSession(ref.session, ref.target);
|
|
315
|
+
printSuccess(`→ /${ref.target}/${shortenSessionId(ref.session)}`);
|
|
309
316
|
return;
|
|
310
317
|
}
|
|
311
318
|
if (ref.kind === 'connector') {
|
|
312
|
-
if (!ref.
|
|
319
|
+
if (!ref.target) {
|
|
313
320
|
printError(`Invalid connector reference: missing connector`);
|
|
314
321
|
return;
|
|
315
322
|
}
|
|
316
323
|
savePreviousLocation(context);
|
|
317
|
-
context.connector = ref.
|
|
324
|
+
context.connector = ref.target;
|
|
318
325
|
context.session = undefined;
|
|
319
|
-
context.proto = detectConnectorProto(store, ref.
|
|
320
|
-
setCurrentSession('', ref.
|
|
321
|
-
printSuccess(`→ /${ref.
|
|
326
|
+
context.proto = detectConnectorProto(store, ref.target);
|
|
327
|
+
setCurrentSession('', ref.target);
|
|
328
|
+
printSuccess(`→ /${ref.target}`);
|
|
322
329
|
return;
|
|
323
330
|
}
|
|
324
331
|
if (ref.kind === 'context') {
|
|
@@ -345,13 +352,13 @@ export async function handleCc(args, context, configPath) {
|
|
|
345
352
|
// Validate absoluteTarget is not empty (handles "cd //" case)
|
|
346
353
|
if (!absoluteTarget || absoluteTarget.trim() === '') {
|
|
347
354
|
printError('Invalid path: empty connector ID');
|
|
348
|
-
printInfo('Use: cd /
|
|
355
|
+
printInfo('Use: cd /target or cd /target/session');
|
|
349
356
|
return;
|
|
350
357
|
}
|
|
351
358
|
// Validate against path traversal attempts (e.g., /.. or /../foo)
|
|
352
359
|
if (absoluteTarget.includes('..')) {
|
|
353
360
|
printError('Invalid path: path traversal not allowed');
|
|
354
|
-
printInfo('Use: cd /
|
|
361
|
+
printInfo('Use: cd /target or cd /target/session');
|
|
355
362
|
return;
|
|
356
363
|
}
|
|
357
364
|
// Check if target contains another / (i.e., /connector/session)
|
|
@@ -359,19 +366,31 @@ export async function handleCc(args, context, configPath) {
|
|
|
359
366
|
const segments = absoluteTarget.split('/').filter(s => s !== '');
|
|
360
367
|
if (segments.length > 2) {
|
|
361
368
|
printError('Invalid path format: too many segments');
|
|
362
|
-
printInfo('Use: cd /
|
|
369
|
+
printInfo('Use: cd /target or cd /target/session');
|
|
363
370
|
return;
|
|
364
371
|
}
|
|
365
372
|
if (segments.length === 2) {
|
|
366
373
|
const [absConnector, absSession] = segments;
|
|
367
|
-
// Find connector
|
|
374
|
+
// Find connector or agent
|
|
368
375
|
const connectors = store.getConnectors();
|
|
369
|
-
|
|
376
|
+
let connector = connectors.find(c => c.id === absConnector || c.id.startsWith(absConnector));
|
|
370
377
|
if (!connector) {
|
|
371
|
-
|
|
378
|
+
try {
|
|
379
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
380
|
+
const targetsStore = new TargetsStore(configDir);
|
|
381
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
382
|
+
const agent = agents.find(a => a.id === absConnector || a.id.startsWith(absConnector));
|
|
383
|
+
if (agent) {
|
|
384
|
+
connector = { id: agent.id, session_count: 0, latest_session: undefined };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch { /* targets table may not exist */ }
|
|
388
|
+
}
|
|
389
|
+
if (!connector) {
|
|
390
|
+
printError(`Target not found: ${absConnector}`);
|
|
372
391
|
const available = connectors.map(c => c.id);
|
|
373
392
|
if (available.length > 0) {
|
|
374
|
-
printInfo(`Available
|
|
393
|
+
printInfo(`Available: ${available.join(', ')}`);
|
|
375
394
|
}
|
|
376
395
|
return;
|
|
377
396
|
}
|
|
@@ -385,15 +404,28 @@ export async function handleCc(args, context, configPath) {
|
|
|
385
404
|
await selectSessionFromMatches(matches, absSession, context, store, connector.id);
|
|
386
405
|
return;
|
|
387
406
|
}
|
|
388
|
-
// Just /connectorId - navigate to connector (segments.length === 1)
|
|
407
|
+
// Just /connectorId - navigate to connector or agent (segments.length === 1)
|
|
389
408
|
const absConnectorId = segments[0];
|
|
390
409
|
const connectors = store.getConnectors();
|
|
391
|
-
|
|
410
|
+
let connector = connectors.find(c => c.id === absConnectorId || c.id.startsWith(absConnectorId));
|
|
411
|
+
// Also check A2A agents if not found in MCP connectors
|
|
392
412
|
if (!connector) {
|
|
393
|
-
|
|
413
|
+
try {
|
|
414
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
415
|
+
const targetsStore = new TargetsStore(configDir);
|
|
416
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
417
|
+
const agent = agents.find(a => a.id === absConnectorId || a.id.startsWith(absConnectorId));
|
|
418
|
+
if (agent) {
|
|
419
|
+
connector = { id: agent.id, session_count: 0, latest_session: undefined };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch { /* targets table may not exist */ }
|
|
423
|
+
}
|
|
424
|
+
if (!connector) {
|
|
425
|
+
printError(`Target not found: ${absConnectorId}`);
|
|
394
426
|
const available = connectors.map(c => c.id);
|
|
395
427
|
if (available.length > 0) {
|
|
396
|
-
printInfo(`Available
|
|
428
|
+
printInfo(`Available: ${available.join(', ')}`);
|
|
397
429
|
}
|
|
398
430
|
return;
|
|
399
431
|
}
|
|
@@ -415,7 +447,7 @@ export async function handleCc(args, context, configPath) {
|
|
|
415
447
|
const connectors = store.getConnectors();
|
|
416
448
|
const connector = connectors.find(c => c.id === connectorPart || c.id.startsWith(connectorPart));
|
|
417
449
|
if (!connector) {
|
|
418
|
-
printError(`
|
|
450
|
+
printError(`Target not found: ${connectorPart}`);
|
|
419
451
|
return;
|
|
420
452
|
}
|
|
421
453
|
// Find session by prefix
|
|
@@ -436,11 +468,19 @@ export async function handleCc(args, context, configPath) {
|
|
|
436
468
|
const historyConnectors = store.getConnectors();
|
|
437
469
|
const manager = new ConfigManager(configPath);
|
|
438
470
|
const configuredConnectors = await manager.getConnectors();
|
|
439
|
-
// Merge IDs from
|
|
471
|
+
// Merge IDs from all sources (deduplicated): history + config + A2A agents
|
|
440
472
|
const allConnectorIds = new Set([
|
|
441
473
|
...historyConnectors.map(c => c.id),
|
|
442
474
|
...configuredConnectors.map(c => c.id),
|
|
443
475
|
]);
|
|
476
|
+
try {
|
|
477
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
478
|
+
const targetsStore = new TargetsStore(configDir);
|
|
479
|
+
for (const a of targetsStore.list({ type: 'agent' })) {
|
|
480
|
+
allConnectorIds.add(a.id);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch { /* targets table may not exist */ }
|
|
444
484
|
// Find matching connector (exact match first, then prefix)
|
|
445
485
|
let matchId;
|
|
446
486
|
for (const id of allConnectorIds) {
|
|
@@ -458,7 +498,7 @@ export async function handleCc(args, context, configPath) {
|
|
|
458
498
|
}
|
|
459
499
|
}
|
|
460
500
|
if (!matchId) {
|
|
461
|
-
printError(`
|
|
501
|
+
printError(`Target not found: ${arg}`);
|
|
462
502
|
printInfo('Available: ' + [...allConnectorIds].join(', '));
|
|
463
503
|
return;
|
|
464
504
|
}
|
|
@@ -582,15 +622,36 @@ export async function handleLs(args, context, configPath, executeCommand) {
|
|
|
582
622
|
printError('No connector in context');
|
|
583
623
|
return;
|
|
584
624
|
}
|
|
585
|
-
|
|
625
|
+
// Check if this is an A2A agent
|
|
626
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
627
|
+
let isA2A = false;
|
|
628
|
+
try {
|
|
629
|
+
const targetsStore = new TargetsStore(configDir);
|
|
630
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
631
|
+
isA2A = agents.some(a => a.id === context.connector);
|
|
632
|
+
}
|
|
633
|
+
catch { /* targets table may not exist */ }
|
|
634
|
+
if (isA2A) {
|
|
635
|
+
await listA2ASessions(configDir, context.connector, isLong, isJson, idsOnly);
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
await listSessions(store, context.connector, isLong, isJson, idsOnly);
|
|
639
|
+
}
|
|
586
640
|
return;
|
|
587
641
|
}
|
|
588
|
-
// Session level - list RPC calls
|
|
642
|
+
// Session level - list RPC calls or A2A messages
|
|
589
643
|
if (!context.session) {
|
|
590
644
|
printError('No session in context');
|
|
591
645
|
return;
|
|
592
646
|
}
|
|
593
|
-
|
|
647
|
+
// Check if this is an A2A session (proto = 'a2a')
|
|
648
|
+
if (context.proto === 'a2a') {
|
|
649
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
650
|
+
await listA2AMessages(configDir, context.session, isLong, isJson, idsOnly);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
await listRpcs(store, context.session, isLong, isJson, idsOnly, executeCommand);
|
|
654
|
+
}
|
|
594
655
|
}
|
|
595
656
|
/**
|
|
596
657
|
* List connectors at root level (router-style table)
|
|
@@ -627,6 +688,25 @@ async function listConnectors(store, configPath, _isLong, isJson, idsOnly) {
|
|
|
627
688
|
});
|
|
628
689
|
}
|
|
629
690
|
}
|
|
691
|
+
// Add A2A agents from targets store
|
|
692
|
+
try {
|
|
693
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
694
|
+
const targetsStore = new TargetsStore(configDir);
|
|
695
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
696
|
+
for (const agent of agents) {
|
|
697
|
+
mergedConnectors.push({
|
|
698
|
+
id: agent.id,
|
|
699
|
+
session_count: 0,
|
|
700
|
+
latest_session: null,
|
|
701
|
+
configured: true,
|
|
702
|
+
hasHistory: false,
|
|
703
|
+
proto: 'a2a',
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
// targets table may not exist yet
|
|
709
|
+
}
|
|
630
710
|
if (mergedConnectors.length === 0) {
|
|
631
711
|
printInfo('No connectors found. Add one with: connectors add --id <id> --command <cmd>');
|
|
632
712
|
return;
|
|
@@ -651,7 +731,7 @@ async function listConnectors(store, configPath, _isLong, isJson, idsOnly) {
|
|
|
651
731
|
const isTTY = process.stdout.isTTY;
|
|
652
732
|
const data = mergedConnectors.map(c => ({
|
|
653
733
|
id: c.id,
|
|
654
|
-
proto: c.hasHistory ? detectConnectorProto(store, c.id) : '?',
|
|
734
|
+
proto: c.proto || (c.hasHistory ? detectConnectorProto(store, c.id) : '?'),
|
|
655
735
|
sessions: c.session_count,
|
|
656
736
|
latest: c.latest_session ? formatRelativeTime(c.latest_session) : '-',
|
|
657
737
|
configured: c.configured,
|
|
@@ -693,23 +773,23 @@ async function listConnectors(store, configPath, _isLong, isJson, idsOnly) {
|
|
|
693
773
|
if (hasHistoryOnly || hasConfigOnly) {
|
|
694
774
|
console.log();
|
|
695
775
|
if (hasConfigOnly) {
|
|
696
|
-
printInfo('+ = ready (
|
|
776
|
+
printInfo('+ = ready (mcp: plans run basic-mcp, a2a: agent scan <id>)');
|
|
697
777
|
}
|
|
698
778
|
if (hasHistoryOnly) {
|
|
699
779
|
printInfo('* = history only (not in config)');
|
|
700
780
|
}
|
|
701
781
|
}
|
|
702
782
|
console.log();
|
|
703
|
-
printInfo(`Hint: cd <
|
|
783
|
+
printInfo(`Hint: cd <target> to enter, show <target> for details`);
|
|
704
784
|
}
|
|
705
785
|
/**
|
|
706
|
-
* List sessions for a
|
|
786
|
+
* List sessions for a target (router-style table)
|
|
707
787
|
*/
|
|
708
|
-
async function listSessions(store,
|
|
709
|
-
const sessions = store.getSessions(
|
|
788
|
+
async function listSessions(store, targetId, _isLong, isJson, idsOnly) {
|
|
789
|
+
const sessions = store.getSessions(targetId, 50);
|
|
710
790
|
if (sessions.length === 0) {
|
|
711
|
-
printInfo(`No sessions for
|
|
712
|
-
printInfo('
|
|
791
|
+
printInfo(`No sessions for target: ${targetId}`);
|
|
792
|
+
printInfo('Start a session first (mcp: plans run basic-mcp, a2a: agent scan <id>)');
|
|
713
793
|
return;
|
|
714
794
|
}
|
|
715
795
|
if (isJson) {
|
|
@@ -854,11 +934,11 @@ export async function handleShow(args, context, configPath, executeCommand) {
|
|
|
854
934
|
return;
|
|
855
935
|
}
|
|
856
936
|
if (ref.kind === 'connector') {
|
|
857
|
-
if (!ref.
|
|
937
|
+
if (!ref.target) {
|
|
858
938
|
printError(`Invalid connector reference: missing connector ID`);
|
|
859
939
|
return;
|
|
860
940
|
}
|
|
861
|
-
await executeCommand(['connectors', 'show', '--id', ref.
|
|
941
|
+
await executeCommand(['connectors', 'show', '--id', ref.target, ...(isJson ? ['--json'] : []), ...htmlOptions]);
|
|
862
942
|
return;
|
|
863
943
|
}
|
|
864
944
|
if (ref.kind === 'context') {
|
|
@@ -870,8 +950,22 @@ export async function handleShow(args, context, configPath, executeCommand) {
|
|
|
870
950
|
}
|
|
871
951
|
if (level === 'root') {
|
|
872
952
|
if (target) {
|
|
873
|
-
//
|
|
874
|
-
|
|
953
|
+
// Check if target is an A2A agent
|
|
954
|
+
let isAgent = false;
|
|
955
|
+
try {
|
|
956
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
957
|
+
const targetsStore = new TargetsStore(configDir);
|
|
958
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
959
|
+
isAgent = agents.some(a => a.id === target || a.id.startsWith(target));
|
|
960
|
+
}
|
|
961
|
+
catch { /* targets table may not exist */ }
|
|
962
|
+
if (isAgent) {
|
|
963
|
+
await executeCommand(['agent', 'show', target]);
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
// Show connector details with HTML support
|
|
967
|
+
await executeCommand(['connectors', 'show', '--id', target, ...(isJson ? ['--json'] : []), ...htmlOptions]);
|
|
968
|
+
}
|
|
875
969
|
}
|
|
876
970
|
else {
|
|
877
971
|
printInfo('At root level. Use: show <connector> or cc <connector>');
|
|
@@ -902,8 +996,21 @@ export async function handleShow(args, context, configPath, executeCommand) {
|
|
|
902
996
|
await executeCommand(['sessions', 'show', '--id', matches[0].session_id, ...(isJson ? ['--json'] : []), ...htmlOptions]);
|
|
903
997
|
}
|
|
904
998
|
else {
|
|
905
|
-
// Show connector details
|
|
906
|
-
|
|
999
|
+
// Show connector/agent details (connector-level)
|
|
1000
|
+
let isAgent = false;
|
|
1001
|
+
try {
|
|
1002
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
1003
|
+
const targetsStore = new TargetsStore(configDir);
|
|
1004
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
1005
|
+
isAgent = agents.some(a => a.id === context.connector || a.id.startsWith(context.connector));
|
|
1006
|
+
}
|
|
1007
|
+
catch { /* targets table may not exist */ }
|
|
1008
|
+
if (isAgent) {
|
|
1009
|
+
await executeCommand(['agent', 'show', context.connector]);
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
await executeCommand(['connectors', 'show', '--id', context.connector, ...(isJson ? ['--json'] : []), ...htmlOptions]);
|
|
1013
|
+
}
|
|
907
1014
|
}
|
|
908
1015
|
return;
|
|
909
1016
|
}
|
|
@@ -912,11 +1019,17 @@ export async function handleShow(args, context, configPath, executeCommand) {
|
|
|
912
1019
|
printError('No session in context');
|
|
913
1020
|
return;
|
|
914
1021
|
}
|
|
915
|
-
// Handle --id option for specific RPC (e.g., show --html --id 1)
|
|
1022
|
+
// Handle --id option for specific RPC/message (e.g., show --html --id 1)
|
|
916
1023
|
const rpcTarget = idValue || target;
|
|
917
1024
|
if (rpcTarget) {
|
|
918
|
-
//
|
|
919
|
-
|
|
1025
|
+
// A2A sessions show message details, MCP sessions show RPC details
|
|
1026
|
+
if (context.proto === 'a2a') {
|
|
1027
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
1028
|
+
await showA2AMessage(configDir, context.session, rpcTarget, isJson);
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
await executeCommand(['rpc', 'show', '--session', context.session, '--id', rpcTarget, ...(isJson ? ['--json'] : []), ...htmlOptions]);
|
|
1032
|
+
}
|
|
920
1033
|
}
|
|
921
1034
|
else {
|
|
922
1035
|
// Show session details
|
|
@@ -1058,6 +1171,39 @@ export function getLsRows(context, configPath) {
|
|
|
1058
1171
|
const connectors = getConnectorRowsInternal(store, configPath);
|
|
1059
1172
|
return { kind: 'rows', rows: connectors, rowType: 'connector' };
|
|
1060
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Get history result as pipeline rows for filtering
|
|
1176
|
+
*
|
|
1177
|
+
* Used by: history | where <filter-expr>
|
|
1178
|
+
*/
|
|
1179
|
+
export function getHistoryRows(context, configPath) {
|
|
1180
|
+
const level = getContextLevel(context);
|
|
1181
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
1182
|
+
const eventsStore = new EventsStore(configDir);
|
|
1183
|
+
if (level === 'session' && context.session) {
|
|
1184
|
+
const messages = eventsStore.getA2AMessages(context.session, 100);
|
|
1185
|
+
// Convert to rows format
|
|
1186
|
+
const rows = messages.map(m => ({
|
|
1187
|
+
id: m.id,
|
|
1188
|
+
role: m.role,
|
|
1189
|
+
content: m.content,
|
|
1190
|
+
timestamp: m.timestamp,
|
|
1191
|
+
}));
|
|
1192
|
+
return { kind: 'rows', rows, rowType: 'a2a-message' };
|
|
1193
|
+
}
|
|
1194
|
+
if (level === 'connector' && context.connector) {
|
|
1195
|
+
const messages = eventsStore.getA2AMessagesForTarget(context.connector, 100);
|
|
1196
|
+
const rows = messages.map(m => ({
|
|
1197
|
+
id: m.id,
|
|
1198
|
+
session_id: m.sessionId,
|
|
1199
|
+
role: m.role,
|
|
1200
|
+
content: m.content,
|
|
1201
|
+
timestamp: m.timestamp,
|
|
1202
|
+
}));
|
|
1203
|
+
return { kind: 'rows', rows, rowType: 'a2a-message' };
|
|
1204
|
+
}
|
|
1205
|
+
return { kind: 'rows', rows: [], rowType: 'a2a-message' };
|
|
1206
|
+
}
|
|
1061
1207
|
/**
|
|
1062
1208
|
* Extract tool name from request JSON.
|
|
1063
1209
|
* Fallback chain: params.name ?? params.tool ?? params.toolName
|
|
@@ -1132,13 +1278,14 @@ function getRpcRowsInternal(store, sessionId) {
|
|
|
1132
1278
|
});
|
|
1133
1279
|
}
|
|
1134
1280
|
/**
|
|
1135
|
-
* Get session rows for a
|
|
1281
|
+
* Get session rows for a target (internal helper)
|
|
1136
1282
|
*/
|
|
1137
|
-
function getSessionRowsInternal(store,
|
|
1138
|
-
const sessions = store.getSessions(
|
|
1283
|
+
function getSessionRowsInternal(store, targetId) {
|
|
1284
|
+
const sessions = store.getSessions(targetId, PIPELINE_SESSION_LIMIT);
|
|
1139
1285
|
return sessions.map((session) => ({
|
|
1140
1286
|
session_id: session.session_id,
|
|
1141
|
-
connector_id:
|
|
1287
|
+
connector_id: targetId,
|
|
1288
|
+
target_id: targetId,
|
|
1142
1289
|
started_at: session.started_at,
|
|
1143
1290
|
ended_at: session.ended_at,
|
|
1144
1291
|
event_count: session.event_count ?? 0,
|
|
@@ -1158,4 +1305,806 @@ function getConnectorRowsInternal(store, configPath) {
|
|
|
1158
1305
|
created_at: c.latest_session ?? new Date().toISOString(),
|
|
1159
1306
|
}));
|
|
1160
1307
|
}
|
|
1308
|
+
// ===== send command (A2A) =====
|
|
1309
|
+
/**
|
|
1310
|
+
* Send a message to an A2A agent
|
|
1311
|
+
* Usage: send <message>
|
|
1312
|
+
* Must be cd'd into an A2A agent target
|
|
1313
|
+
*/
|
|
1314
|
+
export async function handleA2ASend(args, context, configPath) {
|
|
1315
|
+
const level = getContextLevel(context);
|
|
1316
|
+
if (level === 'root') {
|
|
1317
|
+
printError('Not in a target context. Use: cd <agent-id> first, then send <message>');
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (!context.connector) {
|
|
1321
|
+
printError('No target in context');
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
const message = args.join(' ').trim();
|
|
1325
|
+
if (!message) {
|
|
1326
|
+
printError('Usage: send <message>');
|
|
1327
|
+
printInfo('Example: send hello');
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
// Check if current target is an A2A agent
|
|
1331
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
1332
|
+
let agentCard;
|
|
1333
|
+
let allowLocal = false;
|
|
1334
|
+
try {
|
|
1335
|
+
const targetsStore = new TargetsStore(configDir);
|
|
1336
|
+
const agents = targetsStore.list({ type: 'agent' });
|
|
1337
|
+
const agent = agents.find(a => a.id === context.connector);
|
|
1338
|
+
if (!agent) {
|
|
1339
|
+
printError(`'${context.connector}' is not an A2A agent. send is only for A2A agents.`);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
// Get allow_local from agent config (set during agent add --allow-local)
|
|
1343
|
+
const agentConfig = agent.config;
|
|
1344
|
+
allowLocal = agentConfig?.allow_local ?? false;
|
|
1345
|
+
// Get cached agent card
|
|
1346
|
+
const cacheStore = new AgentCacheStore(configDir);
|
|
1347
|
+
const cache = cacheStore.get(agent.id);
|
|
1348
|
+
if (!cache?.agentCard) {
|
|
1349
|
+
printError(`No Agent Card cached for '${agent.id}'. Run: agent scan ${agent.id}${allowLocal ? ' --allow-local' : ''}`);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
agentCard = cache.agentCard;
|
|
1353
|
+
}
|
|
1354
|
+
catch (err) {
|
|
1355
|
+
// Log error for debugging (visible with DEBUG=1 or similar)
|
|
1356
|
+
if (process.env.DEBUG) {
|
|
1357
|
+
console.error('[handleA2ASend] Agent lookup failed:', err);
|
|
1358
|
+
}
|
|
1359
|
+
printError(`Failed to lookup agent: ${err instanceof Error ? err.message : String(err)}`);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
// Initialize session manager for recording
|
|
1363
|
+
let sessionManager;
|
|
1364
|
+
try {
|
|
1365
|
+
const eventsStore = new EventsStore(configDir);
|
|
1366
|
+
sessionManager = createA2ASessionManager(eventsStore, context.connector);
|
|
1367
|
+
}
|
|
1368
|
+
catch {
|
|
1369
|
+
// Session manager initialization failed - continue without recording
|
|
1370
|
+
}
|
|
1371
|
+
// Generate RPC ID for request/response pairing
|
|
1372
|
+
const rpcId = randomUUID();
|
|
1373
|
+
// Store request message to record later with correct contextId
|
|
1374
|
+
const requestMessage = {
|
|
1375
|
+
role: 'user',
|
|
1376
|
+
parts: [{ text: message }],
|
|
1377
|
+
messageId: randomUUID(),
|
|
1378
|
+
};
|
|
1379
|
+
// Send message via A2A client
|
|
1380
|
+
try {
|
|
1381
|
+
const client = new A2AClient(agentCard, { allowLocal });
|
|
1382
|
+
// Check if agent supports streaming
|
|
1383
|
+
const supportsStreaming = agentCard.capabilities?.streaming === true;
|
|
1384
|
+
// Hoisted: used in both streaming and non-streaming paths
|
|
1385
|
+
const isTTY = process.stdout.isTTY;
|
|
1386
|
+
const botPrefix = isTTY ? '\x1b[36m🤖\x1b[0m' : '🤖';
|
|
1387
|
+
// SSE Streaming mode: When agent advertises streaming capability,
|
|
1388
|
+
// use streamMessage() for real-time message display instead of
|
|
1389
|
+
// blocking sendMessage(). Messages are accumulated for session
|
|
1390
|
+
// recording to maintain history parity with non-streaming path.
|
|
1391
|
+
if (supportsStreaming) {
|
|
1392
|
+
const spinner = isTTY ? ora({ text: 'Waiting for response...', spinner: 'dots' }).start() : null;
|
|
1393
|
+
// Accumulate streamed messages for session recording
|
|
1394
|
+
const streamedMessages = [];
|
|
1395
|
+
let streamContextId;
|
|
1396
|
+
// Spinner helper to avoid repetition (with error safety)
|
|
1397
|
+
const withSpinner = (fn) => {
|
|
1398
|
+
if (spinner)
|
|
1399
|
+
spinner.stopAndPersist();
|
|
1400
|
+
try {
|
|
1401
|
+
fn();
|
|
1402
|
+
}
|
|
1403
|
+
finally {
|
|
1404
|
+
if (spinner)
|
|
1405
|
+
spinner.start();
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
const streamResult = await client.streamMessage(message, {
|
|
1409
|
+
onStatus: (event) => {
|
|
1410
|
+
withSpinner(() => {
|
|
1411
|
+
const statusText = `[${event.status}] ${event.taskId}`;
|
|
1412
|
+
console.log(statusText);
|
|
1413
|
+
if (event.contextId)
|
|
1414
|
+
streamContextId = event.contextId;
|
|
1415
|
+
if (event.message) {
|
|
1416
|
+
streamedMessages.push(event.message);
|
|
1417
|
+
const parts = event.message.parts || [];
|
|
1418
|
+
for (const part of parts) {
|
|
1419
|
+
if ('text' in part && part.text) {
|
|
1420
|
+
console.log(` ${botPrefix} ${part.text}`);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
},
|
|
1426
|
+
onArtifact: (event) => {
|
|
1427
|
+
withSpinner(() => {
|
|
1428
|
+
console.log(`\n[Artifact] ${event.artifact?.name || 'unnamed'}`);
|
|
1429
|
+
});
|
|
1430
|
+
},
|
|
1431
|
+
onMessage: (msg) => {
|
|
1432
|
+
withSpinner(() => {
|
|
1433
|
+
// Fallback: extract contextId from message if not yet set
|
|
1434
|
+
if (msg.contextId && !streamContextId) {
|
|
1435
|
+
streamContextId = msg.contextId;
|
|
1436
|
+
}
|
|
1437
|
+
streamedMessages.push(msg);
|
|
1438
|
+
const parts = msg.parts || [];
|
|
1439
|
+
for (const part of parts) {
|
|
1440
|
+
if ('text' in part && part.text) {
|
|
1441
|
+
console.log(`${botPrefix} ${part.text}`);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
},
|
|
1446
|
+
onError: (err) => {
|
|
1447
|
+
withSpinner(() => {
|
|
1448
|
+
printError(`Stream error: ${err}`);
|
|
1449
|
+
});
|
|
1450
|
+
},
|
|
1451
|
+
});
|
|
1452
|
+
if (spinner)
|
|
1453
|
+
spinner.stop();
|
|
1454
|
+
if (!streamResult.ok) {
|
|
1455
|
+
// Record partial messages before error (if any were received)
|
|
1456
|
+
if (sessionManager && streamedMessages.length > 0) {
|
|
1457
|
+
const ctxId = streamContextId;
|
|
1458
|
+
sessionManager.recordMessage(ctxId, requestMessage, true, rpcId);
|
|
1459
|
+
for (const msg of streamedMessages) {
|
|
1460
|
+
sessionManager.recordMessage(ctxId, msg, false, rpcId);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
// Record error - match success pattern with completeRpcCall
|
|
1464
|
+
if (sessionManager) {
|
|
1465
|
+
const eventsStore = sessionManager.getEventsStore();
|
|
1466
|
+
const sessionId = sessionManager.getOrCreateSession(streamContextId);
|
|
1467
|
+
eventsStore.completeRpcCall(sessionId, rpcId, false);
|
|
1468
|
+
sessionManager.recordError(streamContextId, rpcId, streamResult.error || 'Unknown error');
|
|
1469
|
+
}
|
|
1470
|
+
printError(`Stream error: ${streamResult.error}`);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
// Record the streaming session (request + accumulated streamed messages)
|
|
1474
|
+
if (sessionManager) {
|
|
1475
|
+
const ctxId = streamContextId;
|
|
1476
|
+
sessionManager.recordMessage(ctxId, requestMessage, true, rpcId);
|
|
1477
|
+
for (const msg of streamedMessages) {
|
|
1478
|
+
sessionManager.recordMessage(ctxId, msg, false, rpcId);
|
|
1479
|
+
}
|
|
1480
|
+
const eventsStore = sessionManager.getEventsStore();
|
|
1481
|
+
const sessionId = sessionManager.getOrCreateSession(ctxId);
|
|
1482
|
+
eventsStore.completeRpcCall(sessionId, rpcId, streamResult.ok);
|
|
1483
|
+
}
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
// Non-streaming mode (existing code unchanged)
|
|
1487
|
+
const result = await client.sendMessage(message);
|
|
1488
|
+
if (!result.ok) {
|
|
1489
|
+
// Record error
|
|
1490
|
+
if (sessionManager) {
|
|
1491
|
+
sessionManager.recordError(undefined, rpcId, result.error || 'Unknown error');
|
|
1492
|
+
}
|
|
1493
|
+
printError(`A2A error: ${result.error}`);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
// Determine contextId from response (or undefined if no response)
|
|
1497
|
+
const contextId = result.message?.contextId ?? result.task?.contextId;
|
|
1498
|
+
// Record request first with the same contextId as the response
|
|
1499
|
+
if (sessionManager) {
|
|
1500
|
+
sessionManager.recordMessage(contextId, requestMessage, true, rpcId);
|
|
1501
|
+
// Record response
|
|
1502
|
+
if (result.message) {
|
|
1503
|
+
sessionManager.recordMessage(contextId, result.message, false, rpcId);
|
|
1504
|
+
}
|
|
1505
|
+
else if (result.task) {
|
|
1506
|
+
sessionManager.recordTask(contextId, result.task, rpcId);
|
|
1507
|
+
}
|
|
1508
|
+
else {
|
|
1509
|
+
// No response but success - complete RPC
|
|
1510
|
+
const eventsStore = sessionManager.getEventsStore();
|
|
1511
|
+
const sessionId = sessionManager.getOrCreateSession(undefined);
|
|
1512
|
+
eventsStore.completeRpcCall(sessionId, rpcId, true);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
// Display response - check message first, then task
|
|
1516
|
+
if (result.message) {
|
|
1517
|
+
const msg = result.message;
|
|
1518
|
+
if (Array.isArray(msg.parts)) {
|
|
1519
|
+
for (const part of msg.parts) {
|
|
1520
|
+
if ('text' in part && part.text) {
|
|
1521
|
+
console.log(`${botPrefix} ${part.text}`);
|
|
1522
|
+
}
|
|
1523
|
+
else if ('data' in part) {
|
|
1524
|
+
console.log('📎', JSON.stringify(part.data, null, 2));
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
else if (result.task) {
|
|
1530
|
+
const task = result.task;
|
|
1531
|
+
const state = task.status ?? 'unknown';
|
|
1532
|
+
if (state === 'completed' && Array.isArray(task.artifacts)) {
|
|
1533
|
+
// Show artifacts for completed tasks
|
|
1534
|
+
for (const artifact of task.artifacts) {
|
|
1535
|
+
if (artifact.parts) {
|
|
1536
|
+
for (const part of artifact.parts) {
|
|
1537
|
+
if ('text' in part && part.text) {
|
|
1538
|
+
console.log(`${botPrefix} ${part.text}`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
else if (task.messages && task.messages.length > 0) {
|
|
1545
|
+
// Show latest agent message
|
|
1546
|
+
const agentMsgs = task.messages.filter(m => m.role === 'assistant');
|
|
1547
|
+
const lastMsg = agentMsgs[agentMsgs.length - 1];
|
|
1548
|
+
if (lastMsg && Array.isArray(lastMsg.parts)) {
|
|
1549
|
+
for (const part of lastMsg.parts) {
|
|
1550
|
+
if ('text' in part && part.text) {
|
|
1551
|
+
console.log(`${botPrefix} ${part.text}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
if (state !== 'completed') {
|
|
1556
|
+
printInfo(`Task ${task.id} (state: ${state})`);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
else {
|
|
1560
|
+
printInfo(`Task ${task.id} (state: ${state})`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
else {
|
|
1564
|
+
printInfo('(no response)');
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
catch (err) {
|
|
1568
|
+
printError(`Send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
// ==================== A2A Session Display Functions ====================
|
|
1572
|
+
/**
|
|
1573
|
+
* List A2A sessions for an agent
|
|
1574
|
+
*/
|
|
1575
|
+
async function listA2ASessions(configDir, targetId, _isLong, isJson, idsOnly) {
|
|
1576
|
+
const eventsStore = new EventsStore(configDir);
|
|
1577
|
+
const sessions = eventsStore.getA2ASessions(targetId, 50);
|
|
1578
|
+
if (sessions.length === 0) {
|
|
1579
|
+
printInfo(`No A2A sessions for agent: ${targetId}`);
|
|
1580
|
+
printInfo('Start a conversation with: send <message>');
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (isJson) {
|
|
1584
|
+
const data = sessions.map(s => ({
|
|
1585
|
+
id: s.session_id,
|
|
1586
|
+
prefix: shortenSessionId(s.session_id),
|
|
1587
|
+
message_count: s.message_count,
|
|
1588
|
+
last_activity: s.last_activity,
|
|
1589
|
+
}));
|
|
1590
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
if (idsOnly) {
|
|
1594
|
+
sessions.forEach(s => console.log(shortenSessionId(s.session_id)));
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const isTTY = process.stdout.isTTY;
|
|
1598
|
+
console.log();
|
|
1599
|
+
// Header
|
|
1600
|
+
console.log(dimText('Session ID', isTTY).padEnd(isTTY ? 19 : 12) + ' ' +
|
|
1601
|
+
dimText('Messages', isTTY).padEnd(9) + ' ' +
|
|
1602
|
+
dimText('Last Activity', isTTY));
|
|
1603
|
+
console.log(dimText('-'.repeat(40), isTTY));
|
|
1604
|
+
// Rows
|
|
1605
|
+
sessions.forEach(s => {
|
|
1606
|
+
const prefix = shortenSessionId(s.session_id);
|
|
1607
|
+
const activity = s.last_activity ? formatRelativeTime(s.last_activity) : '-';
|
|
1608
|
+
console.log(prefix.padEnd(isTTY ? 16 : 10) + ' ' +
|
|
1609
|
+
String(s.message_count).padEnd(9) + ' ' +
|
|
1610
|
+
activity);
|
|
1611
|
+
});
|
|
1612
|
+
console.log();
|
|
1613
|
+
printInfo('Hint: cd <session> to enter, show <session> for details');
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* List A2A messages for a session
|
|
1617
|
+
*/
|
|
1618
|
+
async function listA2AMessages(configDir, sessionId, _isLong, isJson, idsOnly) {
|
|
1619
|
+
const eventsStore = new EventsStore(configDir);
|
|
1620
|
+
const messages = eventsStore.getA2AMessages(sessionId, 100);
|
|
1621
|
+
if (messages.length === 0) {
|
|
1622
|
+
printInfo('No messages in this session');
|
|
1623
|
+
printInfo('Start a conversation with: send <message>');
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (isJson) {
|
|
1627
|
+
const data = messages.map(m => ({
|
|
1628
|
+
id: m.id,
|
|
1629
|
+
role: m.role,
|
|
1630
|
+
content: m.content,
|
|
1631
|
+
timestamp: m.timestamp,
|
|
1632
|
+
}));
|
|
1633
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (idsOnly) {
|
|
1637
|
+
messages.forEach(m => console.log(m.id));
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const isTTY = process.stdout.isTTY;
|
|
1641
|
+
console.log();
|
|
1642
|
+
// Header
|
|
1643
|
+
console.log(dimText('#', isTTY).padEnd(4) + ' ' +
|
|
1644
|
+
dimText('Role', isTTY).padEnd(12) + ' ' +
|
|
1645
|
+
dimText('Content', isTTY).padEnd(28) + ' ' +
|
|
1646
|
+
dimText('Time', isTTY));
|
|
1647
|
+
console.log(dimText('-'.repeat(60), isTTY));
|
|
1648
|
+
// Rows
|
|
1649
|
+
messages.forEach(m => {
|
|
1650
|
+
const roleColor = isTTY && m.role === 'assistant' ? '\x1b[36m' : '';
|
|
1651
|
+
const roleReset = isTTY && m.role === 'assistant' ? '\x1b[0m' : '';
|
|
1652
|
+
const roleDisplay = `${roleColor}${m.role}${roleReset}`;
|
|
1653
|
+
const contentPreview = m.content.length > 25 ? m.content.slice(0, 25) + '...' : m.content;
|
|
1654
|
+
const time = m.timestamp ? formatRelativeTime(m.timestamp) : '-';
|
|
1655
|
+
console.log(String(m.id).padEnd(4) + ' ' +
|
|
1656
|
+
roleDisplay.padEnd(isTTY ? 21 : 12) + ' ' +
|
|
1657
|
+
contentPreview.padEnd(28) + ' ' +
|
|
1658
|
+
time);
|
|
1659
|
+
});
|
|
1660
|
+
console.log();
|
|
1661
|
+
printInfo('Hint: show <id> to view details, cd .. to go back');
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Show A2A message details
|
|
1665
|
+
*/
|
|
1666
|
+
async function showA2AMessage(configDir, sessionId, messageIndex, isJson) {
|
|
1667
|
+
const eventsStore = new EventsStore(configDir);
|
|
1668
|
+
const messages = eventsStore.getA2AMessages(sessionId, 100);
|
|
1669
|
+
const index = parseInt(messageIndex, 10);
|
|
1670
|
+
if (isNaN(index) || index < 1 || index > messages.length) {
|
|
1671
|
+
printError(`Message not found: ${messageIndex}`);
|
|
1672
|
+
printInfo(`Valid range: 1-${messages.length}`);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
const message = messages[index - 1];
|
|
1676
|
+
if (isJson) {
|
|
1677
|
+
console.log(JSON.stringify({
|
|
1678
|
+
id: message.id,
|
|
1679
|
+
role: message.role,
|
|
1680
|
+
content: message.content,
|
|
1681
|
+
timestamp: message.timestamp,
|
|
1682
|
+
}, null, 2));
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const isTTY = process.stdout.isTTY;
|
|
1686
|
+
console.log();
|
|
1687
|
+
// Role with color
|
|
1688
|
+
const roleColor = isTTY && message.role === 'assistant' ? '\x1b[36m' : '';
|
|
1689
|
+
const roleReset = isTTY && message.role === 'assistant' ? '\x1b[0m' : '';
|
|
1690
|
+
const roleEmoji = message.role === 'assistant' ? '🤖' : '👤';
|
|
1691
|
+
console.log(`${roleEmoji} ${roleColor}${message.role}${roleReset}`);
|
|
1692
|
+
console.log(dimText('─'.repeat(60), isTTY));
|
|
1693
|
+
console.log(message.content);
|
|
1694
|
+
console.log(dimText('─'.repeat(60), isTTY));
|
|
1695
|
+
console.log(dimText(`Timestamp: ${message.timestamp}`, isTTY));
|
|
1696
|
+
console.log();
|
|
1697
|
+
}
|
|
1698
|
+
// History command display constants
|
|
1699
|
+
const HISTORY_LINE_WIDTH = 70;
|
|
1700
|
+
// ANSI color codes add 9 chars (e.g., '\x1b[36m' + '\x1b[0m'), so TTY needs +9 for role padding
|
|
1701
|
+
const ROLE_PAD_TTY = 21;
|
|
1702
|
+
const ROLE_PAD_PLAIN = 12;
|
|
1703
|
+
/**
|
|
1704
|
+
* Handle history command - show A2A message history or Task events with filtering
|
|
1705
|
+
*
|
|
1706
|
+
* Usage (messages):
|
|
1707
|
+
* history - Show all messages
|
|
1708
|
+
* history -n 20 - Show last 20 messages
|
|
1709
|
+
* history --role user - Show only user messages
|
|
1710
|
+
* history --search <query> - Search messages by text
|
|
1711
|
+
*
|
|
1712
|
+
* Usage (tasks - Phase 2.4):
|
|
1713
|
+
* history --task - Show recent task events (last 20)
|
|
1714
|
+
* history --task <id> - Show events for a specific task
|
|
1715
|
+
*
|
|
1716
|
+
* @param args - Command arguments
|
|
1717
|
+
* @param context - Shell context
|
|
1718
|
+
* @param configPath - Configuration path
|
|
1719
|
+
*/
|
|
1720
|
+
export async function handleHistory(args, context, configPath) {
|
|
1721
|
+
// Handle help flag first
|
|
1722
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
1723
|
+
console.log(`
|
|
1724
|
+
Usage: history [options]
|
|
1725
|
+
|
|
1726
|
+
Show A2A message history or Task events for the current context.
|
|
1727
|
+
|
|
1728
|
+
Message History (default):
|
|
1729
|
+
At session level: shows messages in current session.
|
|
1730
|
+
At connector level: shows messages across all sessions.
|
|
1731
|
+
|
|
1732
|
+
Task History (Phase 2.4):
|
|
1733
|
+
history --task Show recent task events (last 20)
|
|
1734
|
+
history --task <id> Show events for a specific task
|
|
1735
|
+
|
|
1736
|
+
Options (messages):
|
|
1737
|
+
-n <count> Show last N messages (default: 100, max: 10000)
|
|
1738
|
+
--role <role> Filter by role: 'user' or 'assistant'
|
|
1739
|
+
-s, --search <query> Search messages (case-insensitive)
|
|
1740
|
+
-h, --help Show this help
|
|
1741
|
+
|
|
1742
|
+
Examples:
|
|
1743
|
+
history Show all messages
|
|
1744
|
+
history -n 20 Show last 20 messages
|
|
1745
|
+
history --role user Show only user messages
|
|
1746
|
+
history -s d20 Search for 'd20' in messages
|
|
1747
|
+
history --task Show recent task events
|
|
1748
|
+
history --task abc123 Show events for task abc123
|
|
1749
|
+
`);
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const level = getContextLevel(context);
|
|
1753
|
+
if (level === 'root') {
|
|
1754
|
+
printError('Not in a session. Use: cd <agent-id>/<session-id> first');
|
|
1755
|
+
printInfo('Tip: history -h for usage');
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (!context.connector) {
|
|
1759
|
+
printError('No target in context');
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
// Default and maximum limit to prevent DoS
|
|
1763
|
+
const DEFAULT_LIMIT = 100;
|
|
1764
|
+
const MAX_LIMIT = 10000;
|
|
1765
|
+
// Parse arguments
|
|
1766
|
+
let limit = DEFAULT_LIMIT;
|
|
1767
|
+
let roleFilter = null;
|
|
1768
|
+
let searchQuery = null;
|
|
1769
|
+
let taskId = null; // Phase 2.4: --task <id>
|
|
1770
|
+
for (let i = 0; i < args.length; i++) {
|
|
1771
|
+
const arg = args[i];
|
|
1772
|
+
if (arg === '-n' && i + 1 < args.length) {
|
|
1773
|
+
const limitStr = args[i + 1];
|
|
1774
|
+
const parsedLimit = parseInt(limitStr, 10);
|
|
1775
|
+
if (!isNaN(parsedLimit) && parsedLimit > 0) {
|
|
1776
|
+
limit = parsedLimit;
|
|
1777
|
+
i++;
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
printError(`Invalid limit: ${limitStr}`);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
else if (arg === '--role' && i + 1 < args.length) {
|
|
1785
|
+
const role = args[i + 1];
|
|
1786
|
+
if (role === 'user' || role === 'assistant') {
|
|
1787
|
+
roleFilter = role;
|
|
1788
|
+
i++;
|
|
1789
|
+
}
|
|
1790
|
+
else {
|
|
1791
|
+
printError(`Invalid role: ${role}. Use 'user' or 'assistant'`);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
else if ((arg === '--search' || arg === '-s') && i + 1 < args.length) {
|
|
1796
|
+
searchQuery = args[i + 1];
|
|
1797
|
+
i++;
|
|
1798
|
+
}
|
|
1799
|
+
else if (arg === '--task') {
|
|
1800
|
+
// Phase 2.4: --task option for task event history
|
|
1801
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
1802
|
+
taskId = args[i + 1];
|
|
1803
|
+
i++;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
else if (arg.startsWith('-')) {
|
|
1807
|
+
// Unknown option - warn user
|
|
1808
|
+
printError(`Unknown option: ${arg}`);
|
|
1809
|
+
printInfo('Use: history -h for usage');
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
// Cap limit to prevent DoS
|
|
1814
|
+
if (limit > MAX_LIMIT) {
|
|
1815
|
+
printInfo(`Limit capped to ${MAX_LIMIT}`);
|
|
1816
|
+
limit = MAX_LIMIT;
|
|
1817
|
+
}
|
|
1818
|
+
const configDir = configPath.replace(/\/[^/]+$/, '');
|
|
1819
|
+
const eventsStore = new EventsStore(configDir);
|
|
1820
|
+
// ==================== Phase 2.4.1: Task Event History ====================
|
|
1821
|
+
if (taskId !== null) {
|
|
1822
|
+
// history --task <id>: Show timeline for a specific task
|
|
1823
|
+
const taskEvents = eventsStore.getTaskEventsByTaskId(taskId);
|
|
1824
|
+
if (taskEvents.length === 0) {
|
|
1825
|
+
printInfo(`No task events found for: ${taskId}`);
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
// Filter: remove duplicate status updates (only show transitions)
|
|
1829
|
+
const filteredEvents = [];
|
|
1830
|
+
let lastStatus = null;
|
|
1831
|
+
for (const event of taskEvents) {
|
|
1832
|
+
const payload = JSON.parse(event.payload_json);
|
|
1833
|
+
// Always show non-updated events (created, completed, failed, canceled, client errors)
|
|
1834
|
+
if (event.event_kind !== 'a2a:task:updated') {
|
|
1835
|
+
filteredEvents.push(event);
|
|
1836
|
+
lastStatus = payload.status;
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
// For updated events, only show if status changed
|
|
1840
|
+
if (payload.status !== lastStatus) {
|
|
1841
|
+
filteredEvents.push(event);
|
|
1842
|
+
lastStatus = payload.status;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
const isTTY = process.stdout.isTTY;
|
|
1846
|
+
console.log();
|
|
1847
|
+
// Header
|
|
1848
|
+
console.log(dimText('#', isTTY).padEnd(4) + ' ' +
|
|
1849
|
+
dimText('Time', isTTY).padEnd(10) + ' ' +
|
|
1850
|
+
dimText('Category', isTTY).padEnd(14) + ' ' +
|
|
1851
|
+
dimText('Status', isTTY).padEnd(12) + ' ' +
|
|
1852
|
+
dimText('Details', isTTY));
|
|
1853
|
+
console.log(dimText('-'.repeat(70), isTTY));
|
|
1854
|
+
// Normalize event kind to category
|
|
1855
|
+
const getCategory = (kind) => {
|
|
1856
|
+
if (kind === 'a2a:task:created')
|
|
1857
|
+
return 'created';
|
|
1858
|
+
if (kind === 'a2a:task:updated')
|
|
1859
|
+
return 'status';
|
|
1860
|
+
if (kind === 'a2a:task:completed' || kind === 'a2a:task:failed' || kind === 'a2a:task:canceled')
|
|
1861
|
+
return 'terminal';
|
|
1862
|
+
if (kind.includes('timeout') || kind.includes('error'))
|
|
1863
|
+
return 'client_error';
|
|
1864
|
+
return kind.replace('a2a:task:', '');
|
|
1865
|
+
};
|
|
1866
|
+
// Rows
|
|
1867
|
+
filteredEvents.forEach((event, idx) => {
|
|
1868
|
+
const payload = JSON.parse(event.payload_json);
|
|
1869
|
+
const timeStr = event.ts ? event.ts.slice(11, 19) : '--:--:--';
|
|
1870
|
+
const category = getCategory(event.event_kind);
|
|
1871
|
+
// Color-code category
|
|
1872
|
+
let categoryStr = category;
|
|
1873
|
+
let statusStr = payload.status || '';
|
|
1874
|
+
if (isTTY) {
|
|
1875
|
+
switch (category) {
|
|
1876
|
+
case 'terminal':
|
|
1877
|
+
if (payload.status === 'completed') {
|
|
1878
|
+
categoryStr = '\x1b[32m' + category + '\x1b[0m'; // green
|
|
1879
|
+
statusStr = '\x1b[32m' + statusStr + '\x1b[0m';
|
|
1880
|
+
}
|
|
1881
|
+
else {
|
|
1882
|
+
categoryStr = '\x1b[31m' + category + '\x1b[0m'; // red
|
|
1883
|
+
statusStr = '\x1b[31m' + statusStr + '\x1b[0m';
|
|
1884
|
+
}
|
|
1885
|
+
break;
|
|
1886
|
+
case 'client_error':
|
|
1887
|
+
categoryStr = '\x1b[31m' + category + '\x1b[0m'; // red
|
|
1888
|
+
break;
|
|
1889
|
+
case 'status':
|
|
1890
|
+
categoryStr = '\x1b[33m' + category + '\x1b[0m'; // yellow
|
|
1891
|
+
break;
|
|
1892
|
+
default:
|
|
1893
|
+
break;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
// Build details string
|
|
1897
|
+
let details = '';
|
|
1898
|
+
if (payload.error) {
|
|
1899
|
+
details = payload.error;
|
|
1900
|
+
}
|
|
1901
|
+
if (payload.messages && Array.isArray(payload.messages) && payload.messages.length > 0) {
|
|
1902
|
+
details = details ? `${details} | ${payload.messages.length} msgs` : `${payload.messages.length} msgs`;
|
|
1903
|
+
}
|
|
1904
|
+
console.log(String(idx + 1).padEnd(4) + ' ' +
|
|
1905
|
+
timeStr.padEnd(10) + ' ' +
|
|
1906
|
+
categoryStr.padEnd(isTTY ? 23 : 14) + ' ' +
|
|
1907
|
+
statusStr.padEnd(isTTY ? 21 : 12) + ' ' +
|
|
1908
|
+
details);
|
|
1909
|
+
});
|
|
1910
|
+
console.log();
|
|
1911
|
+
const skipped = taskEvents.length - filteredEvents.length;
|
|
1912
|
+
if (skipped > 0) {
|
|
1913
|
+
printInfo(`Showing ${filteredEvents.length} event${filteredEvents.length !== 1 ? 's' : ''} (${skipped} duplicate status updates hidden)`);
|
|
1914
|
+
}
|
|
1915
|
+
else {
|
|
1916
|
+
printInfo(`Showing ${filteredEvents.length} event${filteredEvents.length !== 1 ? 's' : ''}`);
|
|
1917
|
+
}
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
// Check for --task flag without taskId (show task summary list)
|
|
1921
|
+
if (args.includes('--task')) {
|
|
1922
|
+
const taskEvents = eventsStore.getRecentTaskEvents(500); // Get more events to aggregate
|
|
1923
|
+
if (taskEvents.length === 0) {
|
|
1924
|
+
printInfo('No task events found');
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
// Aggregate events by taskId
|
|
1928
|
+
const taskSummary = new Map();
|
|
1929
|
+
for (const event of taskEvents) {
|
|
1930
|
+
const payload = JSON.parse(event.payload_json);
|
|
1931
|
+
const existing = taskSummary.get(event.task_id);
|
|
1932
|
+
if (!existing) {
|
|
1933
|
+
taskSummary.set(event.task_id, {
|
|
1934
|
+
taskId: event.task_id,
|
|
1935
|
+
status: payload.status || 'unknown',
|
|
1936
|
+
eventCount: 1,
|
|
1937
|
+
lastTs: event.ts,
|
|
1938
|
+
hasError: event.event_kind.includes('failed') || event.event_kind.includes('timeout') || event.event_kind.includes('error'),
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
existing.eventCount++;
|
|
1943
|
+
existing.status = payload.status || existing.status;
|
|
1944
|
+
if (event.ts > existing.lastTs) {
|
|
1945
|
+
existing.lastTs = event.ts;
|
|
1946
|
+
}
|
|
1947
|
+
if (event.event_kind.includes('failed') || event.event_kind.includes('timeout') || event.event_kind.includes('error')) {
|
|
1948
|
+
existing.hasError = true;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
// Sort by lastTs descending and limit
|
|
1953
|
+
const sortedTasks = Array.from(taskSummary.values())
|
|
1954
|
+
.sort((a, b) => b.lastTs.localeCompare(a.lastTs))
|
|
1955
|
+
.slice(0, limit);
|
|
1956
|
+
const isTTY = process.stdout.isTTY;
|
|
1957
|
+
console.log();
|
|
1958
|
+
// Header
|
|
1959
|
+
console.log(dimText('Task ID', isTTY).padEnd(14) + ' ' +
|
|
1960
|
+
dimText('Status', isTTY).padEnd(12) + ' ' +
|
|
1961
|
+
dimText('Events', isTTY).padEnd(8) + ' ' +
|
|
1962
|
+
dimText('Last Activity', isTTY));
|
|
1963
|
+
console.log(dimText('-'.repeat(60), isTTY));
|
|
1964
|
+
// Rows
|
|
1965
|
+
for (const task of sortedTasks) {
|
|
1966
|
+
const taskIdShort = task.taskId.slice(0, 12) + '...';
|
|
1967
|
+
// Color-code status
|
|
1968
|
+
let statusStr = task.status;
|
|
1969
|
+
if (isTTY) {
|
|
1970
|
+
if (task.status === 'completed') {
|
|
1971
|
+
statusStr = '\x1b[32m' + task.status + '\x1b[0m'; // green
|
|
1972
|
+
}
|
|
1973
|
+
else if (task.hasError || task.status === 'failed' || task.status === 'canceled') {
|
|
1974
|
+
statusStr = '\x1b[31m' + task.status + '\x1b[0m'; // red
|
|
1975
|
+
}
|
|
1976
|
+
else if (task.status === 'working') {
|
|
1977
|
+
statusStr = '\x1b[33m' + task.status + '\x1b[0m'; // yellow
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
// Format time as relative
|
|
1981
|
+
const lastDate = new Date(task.lastTs);
|
|
1982
|
+
const now = new Date();
|
|
1983
|
+
const diffMs = now.getTime() - lastDate.getTime();
|
|
1984
|
+
const diffMin = Math.floor(diffMs / 60000);
|
|
1985
|
+
let timeAgo;
|
|
1986
|
+
if (diffMin < 1) {
|
|
1987
|
+
timeAgo = 'just now';
|
|
1988
|
+
}
|
|
1989
|
+
else if (diffMin < 60) {
|
|
1990
|
+
timeAgo = `${diffMin}m ago`;
|
|
1991
|
+
}
|
|
1992
|
+
else if (diffMin < 1440) {
|
|
1993
|
+
timeAgo = `${Math.floor(diffMin / 60)}h ago`;
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
timeAgo = `${Math.floor(diffMin / 1440)}d ago`;
|
|
1997
|
+
}
|
|
1998
|
+
console.log(taskIdShort.padEnd(14) + ' ' +
|
|
1999
|
+
statusStr.padEnd(isTTY ? 21 : 12) + ' ' + // Extra padding for ANSI codes
|
|
2000
|
+
String(task.eventCount).padEnd(8) + ' ' +
|
|
2001
|
+
timeAgo);
|
|
2002
|
+
}
|
|
2003
|
+
console.log();
|
|
2004
|
+
printInfo(`Showing ${sortedTasks.length} task${sortedTasks.length !== 1 ? 's' : ''}`);
|
|
2005
|
+
printInfo('Use: history --task <id> for task timeline');
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
// ====================================================================
|
|
2009
|
+
// Connector level: search across all sessions for this target
|
|
2010
|
+
if (level === 'connector') {
|
|
2011
|
+
// Get messages across all sessions for this target
|
|
2012
|
+
const messages = eventsStore.getA2AMessagesForTarget(context.connector, limit * 2);
|
|
2013
|
+
// Apply filters
|
|
2014
|
+
let filteredMessages = messages;
|
|
2015
|
+
if (roleFilter) {
|
|
2016
|
+
filteredMessages = filteredMessages.filter(m => m.role === roleFilter);
|
|
2017
|
+
}
|
|
2018
|
+
if (searchQuery) {
|
|
2019
|
+
const lowerQuery = searchQuery.toLowerCase();
|
|
2020
|
+
filteredMessages = filteredMessages.filter(m => m.content.toLowerCase().includes(lowerQuery));
|
|
2021
|
+
}
|
|
2022
|
+
// Apply limit after filtering (get newest N)
|
|
2023
|
+
if (filteredMessages.length > limit) {
|
|
2024
|
+
filteredMessages = filteredMessages.slice(0, limit);
|
|
2025
|
+
}
|
|
2026
|
+
// Reverse to display in chronological order (oldest first)
|
|
2027
|
+
filteredMessages = filteredMessages.reverse();
|
|
2028
|
+
if (filteredMessages.length === 0) {
|
|
2029
|
+
printInfo('No messages match the criteria');
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const isTTY = process.stdout.isTTY;
|
|
2033
|
+
console.log();
|
|
2034
|
+
// Header (with Session column for connector level)
|
|
2035
|
+
console.log(dimText('#', isTTY).padEnd(4) + ' ' +
|
|
2036
|
+
dimText('Session', isTTY).padEnd(isTTY ? 16 : 10) + ' ' +
|
|
2037
|
+
dimText('Time', isTTY).padEnd(10) + ' ' +
|
|
2038
|
+
dimText('Role', isTTY).padEnd(12) + ' ' +
|
|
2039
|
+
dimText('Content', isTTY));
|
|
2040
|
+
console.log(dimText('-'.repeat(HISTORY_LINE_WIDTH + 10), isTTY));
|
|
2041
|
+
// Rows
|
|
2042
|
+
filteredMessages.forEach(m => {
|
|
2043
|
+
const sessionPrefix = shortenSessionId(m.sessionId);
|
|
2044
|
+
const timeStr = m.timestamp ? m.timestamp.slice(11, 19) : '--:--:--';
|
|
2045
|
+
const roleColor = isTTY && m.role === 'assistant' ? '\x1b[36m' : '';
|
|
2046
|
+
const roleReset = isTTY && m.role === 'assistant' ? '\x1b[0m' : '';
|
|
2047
|
+
const roleDisplay = `${roleColor}${m.role}${roleReset}`;
|
|
2048
|
+
console.log(String(m.id).padEnd(4) + ' ' +
|
|
2049
|
+
sessionPrefix.padEnd(isTTY ? 16 : 10) + ' ' +
|
|
2050
|
+
timeStr.padEnd(10) + ' ' +
|
|
2051
|
+
roleDisplay.padEnd(isTTY ? ROLE_PAD_TTY : ROLE_PAD_PLAIN) + ' ' +
|
|
2052
|
+
m.content);
|
|
2053
|
+
});
|
|
2054
|
+
console.log();
|
|
2055
|
+
printInfo(`Showing ${filteredMessages.length} message${filteredMessages.length !== 1 ? 's' : ''} across ${new Set(filteredMessages.map(m => m.sessionId)).size} session${new Set(filteredMessages.map(m => m.sessionId)).size !== 1 ? 's' : ''}`);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
// Session level
|
|
2059
|
+
if (!context.session) {
|
|
2060
|
+
printError('Not in a session. Use: cd <session-id> to enter a session');
|
|
2061
|
+
printInfo('Tip: history -h for usage');
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
if (context.proto !== 'a2a') {
|
|
2065
|
+
printError('history is only available for A2A sessions');
|
|
2066
|
+
printInfo('Use: ls or rpc to view this session');
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
// Get messages with higher limit for filtering
|
|
2070
|
+
const messages = eventsStore.getA2AMessages(context.session, limit * 2);
|
|
2071
|
+
// Apply filters
|
|
2072
|
+
let filteredMessages = messages;
|
|
2073
|
+
if (roleFilter) {
|
|
2074
|
+
filteredMessages = filteredMessages.filter(m => m.role === roleFilter);
|
|
2075
|
+
}
|
|
2076
|
+
if (searchQuery) {
|
|
2077
|
+
const lowerQuery = searchQuery.toLowerCase();
|
|
2078
|
+
filteredMessages = filteredMessages.filter(m => m.content.toLowerCase().includes(lowerQuery));
|
|
2079
|
+
}
|
|
2080
|
+
// Apply limit after filtering
|
|
2081
|
+
if (filteredMessages.length > limit) {
|
|
2082
|
+
filteredMessages = filteredMessages.slice(-limit);
|
|
2083
|
+
}
|
|
2084
|
+
if (filteredMessages.length === 0) {
|
|
2085
|
+
printInfo('No messages match the criteria');
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const isTTY = process.stdout.isTTY;
|
|
2089
|
+
console.log();
|
|
2090
|
+
// Header
|
|
2091
|
+
console.log(dimText('#', isTTY).padEnd(4) + ' ' +
|
|
2092
|
+
dimText('Time', isTTY).padEnd(10) + ' ' +
|
|
2093
|
+
dimText('Role', isTTY).padEnd(12) + ' ' +
|
|
2094
|
+
dimText('Content', isTTY));
|
|
2095
|
+
console.log(dimText('-'.repeat(HISTORY_LINE_WIDTH), isTTY));
|
|
2096
|
+
// Rows
|
|
2097
|
+
filteredMessages.forEach(m => {
|
|
2098
|
+
const timeStr = m.timestamp ? m.timestamp.slice(11, 19) : '--:--:--';
|
|
2099
|
+
const roleColor = isTTY && m.role === 'assistant' ? '\x1b[36m' : '';
|
|
2100
|
+
const roleReset = isTTY && m.role === 'assistant' ? '\x1b[0m' : '';
|
|
2101
|
+
const roleDisplay = `${roleColor}${m.role}${roleReset}`;
|
|
2102
|
+
console.log(String(m.id).padEnd(4) + ' ' +
|
|
2103
|
+
timeStr.padEnd(10) + ' ' +
|
|
2104
|
+
roleDisplay.padEnd(isTTY ? ROLE_PAD_TTY : ROLE_PAD_PLAIN) + ' ' +
|
|
2105
|
+
m.content);
|
|
2106
|
+
});
|
|
2107
|
+
console.log();
|
|
2108
|
+
printInfo(`Showing ${filteredMessages.length} message${filteredMessages.length !== 1 ? 's' : ''}`);
|
|
2109
|
+
}
|
|
1161
2110
|
//# sourceMappingURL=router-commands.js.map
|