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.
Files changed (199) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +2 -0
  3. package/dist/a2a/agent-card.d.ts +2 -0
  4. package/dist/a2a/agent-card.d.ts.map +1 -1
  5. package/dist/a2a/agent-card.js +2 -2
  6. package/dist/a2a/agent-card.js.map +1 -1
  7. package/dist/a2a/client.d.ts +74 -12
  8. package/dist/a2a/client.d.ts.map +1 -1
  9. package/dist/a2a/client.js +228 -29
  10. package/dist/a2a/client.js.map +1 -1
  11. package/dist/a2a/normalizer.d.ts +4 -0
  12. package/dist/a2a/normalizer.d.ts.map +1 -1
  13. package/dist/a2a/normalizer.js +7 -4
  14. package/dist/a2a/normalizer.js.map +1 -1
  15. package/dist/a2a/session-manager.d.ts +81 -0
  16. package/dist/a2a/session-manager.d.ts.map +1 -0
  17. package/dist/a2a/session-manager.js +176 -0
  18. package/dist/a2a/session-manager.js.map +1 -0
  19. package/dist/a2a/types.d.ts +60 -0
  20. package/dist/a2a/types.d.ts.map +1 -1
  21. package/dist/cli.d.ts +2 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +6 -3
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/agent.d.ts.map +1 -1
  26. package/dist/commands/agent.js +35 -10
  27. package/dist/commands/agent.js.map +1 -1
  28. package/dist/commands/analyze.d.ts.map +1 -1
  29. package/dist/commands/analyze.js +12 -10
  30. package/dist/commands/analyze.js.map +1 -1
  31. package/dist/commands/connectors.js +2 -2
  32. package/dist/commands/connectors.js.map +1 -1
  33. package/dist/commands/index.d.ts +1 -0
  34. package/dist/commands/index.d.ts.map +1 -1
  35. package/dist/commands/index.js +1 -0
  36. package/dist/commands/index.js.map +1 -1
  37. package/dist/commands/plans.js +1 -1
  38. package/dist/commands/plans.js.map +1 -1
  39. package/dist/commands/record.js +5 -4
  40. package/dist/commands/record.js.map +1 -1
  41. package/dist/commands/rpc.d.ts.map +1 -1
  42. package/dist/commands/rpc.js +90 -28
  43. package/dist/commands/rpc.js.map +1 -1
  44. package/dist/commands/scan.d.ts.map +1 -1
  45. package/dist/commands/scan.js +8 -10
  46. package/dist/commands/scan.js.map +1 -1
  47. package/dist/commands/secrets.d.ts.map +1 -1
  48. package/dist/commands/secrets.js +11 -10
  49. package/dist/commands/secrets.js.map +1 -1
  50. package/dist/commands/sessions.js +2 -2
  51. package/dist/commands/sessions.js.map +1 -1
  52. package/dist/commands/summary.d.ts.map +1 -1
  53. package/dist/commands/summary.js +4 -2
  54. package/dist/commands/summary.js.map +1 -1
  55. package/dist/commands/task.d.ts +14 -0
  56. package/dist/commands/task.d.ts.map +1 -0
  57. package/dist/commands/task.js +520 -0
  58. package/dist/commands/task.js.map +1 -0
  59. package/dist/db/connection.d.ts.map +1 -1
  60. package/dist/db/connection.js +56 -1
  61. package/dist/db/connection.js.map +1 -1
  62. package/dist/db/events-store.d.ts +307 -8
  63. package/dist/db/events-store.d.ts.map +1 -1
  64. package/dist/db/events-store.js +620 -26
  65. package/dist/db/events-store.js.map +1 -1
  66. package/dist/db/proofs-store.d.ts +8 -1
  67. package/dist/db/proofs-store.d.ts.map +1 -1
  68. package/dist/db/proofs-store.js +18 -8
  69. package/dist/db/proofs-store.js.map +1 -1
  70. package/dist/db/schema.d.ts +15 -3
  71. package/dist/db/schema.d.ts.map +1 -1
  72. package/dist/db/schema.js +150 -5
  73. package/dist/db/schema.js.map +1 -1
  74. package/dist/db/tool-analysis.d.ts +15 -3
  75. package/dist/db/tool-analysis.d.ts.map +1 -1
  76. package/dist/db/tool-analysis.js +35 -17
  77. package/dist/db/tool-analysis.js.map +1 -1
  78. package/dist/db/types.d.ts +64 -1
  79. package/dist/db/types.d.ts.map +1 -1
  80. package/dist/filter/fields.d.ts.map +1 -1
  81. package/dist/filter/fields.js +22 -0
  82. package/dist/filter/fields.js.map +1 -1
  83. package/dist/filter/parser.js +2 -2
  84. package/dist/filter/parser.js.map +1 -1
  85. package/dist/filter/types.d.ts +1 -1
  86. package/dist/filter/types.d.ts.map +1 -1
  87. package/dist/html/analytics.test.ts +682 -0
  88. package/dist/html/analytics.ts +499 -0
  89. package/dist/html/browser.ts +39 -0
  90. package/dist/html/index.ts +97 -0
  91. package/dist/html/rpc-inspector.test.ts +529 -0
  92. package/dist/html/rpc-inspector.ts +1700 -0
  93. package/dist/html/templates.js +4 -4
  94. package/dist/html/templates.js.map +1 -1
  95. package/dist/html/templates.test.ts +861 -0
  96. package/dist/html/templates.ts +3163 -0
  97. package/dist/html/trace-viewer.html +624 -0
  98. package/dist/html/types.d.ts +3 -3
  99. package/dist/html/types.d.ts.map +1 -1
  100. package/dist/html/types.ts +491 -0
  101. package/dist/html/utils.ts +107 -0
  102. package/dist/monitor/data/connectors.d.ts.map +1 -1
  103. package/dist/monitor/data/connectors.js +113 -8
  104. package/dist/monitor/data/connectors.js.map +1 -1
  105. package/dist/monitor/data/popl.js +2 -2
  106. package/dist/monitor/data/popl.js.map +1 -1
  107. package/dist/monitor/routes/api.js +2 -2
  108. package/dist/monitor/routes/api.js.map +1 -1
  109. package/dist/monitor/routes/connectors.js +15 -15
  110. package/dist/monitor/routes/connectors.js.map +1 -1
  111. package/dist/monitor/routes/popl.js +5 -5
  112. package/dist/monitor/routes/popl.js.map +1 -1
  113. package/dist/monitor/templates/components.js +2 -2
  114. package/dist/monitor/templates/components.js.map +1 -1
  115. package/dist/monitor/templates/popl.js +4 -4
  116. package/dist/monitor/templates/popl.js.map +1 -1
  117. package/dist/monitor/types.d.ts +2 -2
  118. package/dist/monitor/types.d.ts.map +1 -1
  119. package/dist/proxy/bridge-utils.d.ts +41 -0
  120. package/dist/proxy/bridge-utils.d.ts.map +1 -0
  121. package/dist/proxy/bridge-utils.js +60 -0
  122. package/dist/proxy/bridge-utils.js.map +1 -0
  123. package/dist/proxy/ipc-client.d.ts.map +1 -1
  124. package/dist/proxy/ipc-client.js +1 -2
  125. package/dist/proxy/ipc-client.js.map +1 -1
  126. package/dist/proxy/ipc-server.d.ts.map +1 -1
  127. package/dist/proxy/ipc-server.js +4 -2
  128. package/dist/proxy/ipc-server.js.map +1 -1
  129. package/dist/proxy/mcp-server.d.ts +31 -0
  130. package/dist/proxy/mcp-server.d.ts.map +1 -1
  131. package/dist/proxy/mcp-server.js +393 -4
  132. package/dist/proxy/mcp-server.js.map +1 -1
  133. package/dist/proxy/types.d.ts +95 -0
  134. package/dist/proxy/types.d.ts.map +1 -1
  135. package/dist/secrets/management.d.ts +2 -2
  136. package/dist/secrets/management.d.ts.map +1 -1
  137. package/dist/secrets/management.js +7 -7
  138. package/dist/secrets/management.js.map +1 -1
  139. package/dist/shell/completer.d.ts.map +1 -1
  140. package/dist/shell/completer.js +16 -0
  141. package/dist/shell/completer.js.map +1 -1
  142. package/dist/shell/context-applicator.d.ts.map +1 -1
  143. package/dist/shell/context-applicator.js +32 -0
  144. package/dist/shell/context-applicator.js.map +1 -1
  145. package/dist/shell/filter-mappers.d.ts +5 -1
  146. package/dist/shell/filter-mappers.d.ts.map +1 -1
  147. package/dist/shell/filter-mappers.js +12 -0
  148. package/dist/shell/filter-mappers.js.map +1 -1
  149. package/dist/shell/find-command.js +13 -13
  150. package/dist/shell/find-command.js.map +1 -1
  151. package/dist/shell/inscribe-commands.js +5 -5
  152. package/dist/shell/inscribe-commands.js.map +1 -1
  153. package/dist/shell/pager/less-pager.d.ts +1 -1
  154. package/dist/shell/pager/less-pager.d.ts.map +1 -1
  155. package/dist/shell/pager/less-pager.js +5 -2
  156. package/dist/shell/pager/less-pager.js.map +1 -1
  157. package/dist/shell/pager/more-pager.d.ts +1 -1
  158. package/dist/shell/pager/more-pager.d.ts.map +1 -1
  159. package/dist/shell/pager/more-pager.js +3 -2
  160. package/dist/shell/pager/more-pager.js.map +1 -1
  161. package/dist/shell/pager/renderer.d.ts.map +1 -1
  162. package/dist/shell/pager/renderer.js +66 -15
  163. package/dist/shell/pager/renderer.js.map +1 -1
  164. package/dist/shell/pager/types.d.ts +5 -2
  165. package/dist/shell/pager/types.d.ts.map +1 -1
  166. package/dist/shell/pager/utils.d.ts +5 -2
  167. package/dist/shell/pager/utils.d.ts.map +1 -1
  168. package/dist/shell/pager/utils.js +14 -17
  169. package/dist/shell/pager/utils.js.map +1 -1
  170. package/dist/shell/pipeline-types.d.ts +12 -4
  171. package/dist/shell/pipeline-types.d.ts.map +1 -1
  172. package/dist/shell/ref-commands.js +7 -7
  173. package/dist/shell/ref-commands.js.map +1 -1
  174. package/dist/shell/ref-resolver.d.ts +15 -15
  175. package/dist/shell/ref-resolver.d.ts.map +1 -1
  176. package/dist/shell/ref-resolver.js +34 -20
  177. package/dist/shell/ref-resolver.js.map +1 -1
  178. package/dist/shell/repl.d.ts +25 -0
  179. package/dist/shell/repl.d.ts.map +1 -1
  180. package/dist/shell/repl.js +285 -51
  181. package/dist/shell/repl.js.map +1 -1
  182. package/dist/shell/router-commands.d.ts +30 -0
  183. package/dist/shell/router-commands.d.ts.map +1 -1
  184. package/dist/shell/router-commands.js +1011 -62
  185. package/dist/shell/router-commands.js.map +1 -1
  186. package/dist/shell/selector.d.ts +1 -1
  187. package/dist/shell/selector.d.ts.map +1 -1
  188. package/dist/shell/selector.js +1 -1
  189. package/dist/shell/selector.js.map +1 -1
  190. package/dist/shell/types.d.ts.map +1 -1
  191. package/dist/shell/types.js +3 -1
  192. package/dist/shell/types.js.map +1 -1
  193. package/dist/shell/where-command.d.ts.map +1 -1
  194. package/dist/shell/where-command.js +19 -3
  195. package/dist/shell/where-command.js.map +1 -1
  196. package/dist/utils/output.d.ts.map +1 -1
  197. package/dist/utils/output.js +7 -1
  198. package/dist/utils/output.js.map +1 -1
  199. 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.* or agent.* methods
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, connectorId) {
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 = connectorId;
122
+ context.connector = targetId;
116
123
  context.proto = detectProto(store, matches[0].session_id);
117
- setCurrentSession(matches[0].session_id, connectorId);
118
- printSuccess(`→ /${connectorId}/${shortenSessionId(matches[0].session_id)}`);
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.connector_id })));
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 = connectorId;
134
+ context.connector = targetId;
128
135
  context.proto = detectProto(store, selected);
129
- setCurrentSession(selected, connectorId);
130
- printSuccess(`→ /${connectorId}/${shortenSessionId(selected)}`);
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 connector)
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.connector) {
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.connector;
297
+ context.connector = ref.target;
291
298
  context.session = ref.session;
292
299
  context.proto = detectProto(store, ref.session);
293
- setCurrentSession(ref.session, ref.connector);
294
- printSuccess(`→ /${ref.connector}/${shortenSessionId(ref.session)}`);
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.connector) {
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.connector;
311
+ context.connector = ref.target;
305
312
  context.session = ref.session;
306
313
  context.proto = detectProto(store, ref.session);
307
- setCurrentSession(ref.session, ref.connector);
308
- printSuccess(`→ /${ref.connector}/${shortenSessionId(ref.session)}`);
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.connector) {
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.connector;
324
+ context.connector = ref.target;
318
325
  context.session = undefined;
319
- context.proto = detectConnectorProto(store, ref.connector);
320
- setCurrentSession('', ref.connector);
321
- printSuccess(`→ /${ref.connector}`);
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 /connector or cd /connector/session');
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 /connector or cd /connector/session');
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 /connector or cd /connector/session');
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
- const connector = connectors.find(c => c.id === absConnector || c.id.startsWith(absConnector));
376
+ let connector = connectors.find(c => c.id === absConnector || c.id.startsWith(absConnector));
370
377
  if (!connector) {
371
- printError(`Connector not found: ${absConnector}`);
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 connectors: ${available.join(', ')}`);
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
- const connector = connectors.find(c => c.id === absConnectorId || c.id.startsWith(absConnectorId));
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
- printError(`Connector not found: ${absConnectorId}`);
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 connectors: ${available.join(', ')}`);
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(`Connector not found: ${connectorPart}`);
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 both sources (deduplicated)
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(`Connector not found: ${arg}`);
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
- await listSessions(store, context.connector, isLong, isJson, idsOnly);
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
- await listRpcs(store, context.session, isLong, isJson, idsOnly, executeCommand);
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 (run: plans run basic-mcp)');
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 <connector> to enter, show <connector> for details`);
783
+ printInfo(`Hint: cd <target> to enter, show <target> for details`);
704
784
  }
705
785
  /**
706
- * List sessions for a connector (router-style table)
786
+ * List sessions for a target (router-style table)
707
787
  */
708
- async function listSessions(store, connectorId, _isLong, isJson, idsOnly) {
709
- const sessions = store.getSessions(connectorId, 50);
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 connector: ${connectorId}`);
712
- printInfo('Run: plans run basic-mcp');
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.connector) {
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.connector, ...(isJson ? ['--json'] : []), ...htmlOptions]);
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
- // Show connector details with HTML support
874
- await executeCommand(['connectors', 'show', '--id', target, ...(isJson ? ['--json'] : []), ...htmlOptions]);
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 with HTML support (connector-level)
906
- await executeCommand(['connectors', 'show', '--id', context.connector, ...(isJson ? ['--json'] : []), ...htmlOptions]);
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
- // Show RPC details
919
- await executeCommand(['rpc', 'show', '--session', context.session, '--id', rpcTarget, ...(isJson ? ['--json'] : []), ...htmlOptions]);
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 connector (internal helper)
1281
+ * Get session rows for a target (internal helper)
1136
1282
  */
1137
- function getSessionRowsInternal(store, connectorId) {
1138
- const sessions = store.getSessions(connectorId, PIPELINE_SESSION_LIMIT);
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: connectorId,
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