cli-jaw 1.7.32 → 1.7.33

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 (58) hide show
  1. package/dist/bin/cli-jaw.js +11 -0
  2. package/dist/bin/cli-jaw.js.map +1 -1
  3. package/dist/bin/star-prompt.js +96 -0
  4. package/dist/bin/star-prompt.js.map +1 -0
  5. package/dist/src/agent/args.js +0 -4
  6. package/dist/src/agent/args.js.map +1 -1
  7. package/dist/src/agent/events.js +328 -54
  8. package/dist/src/agent/events.js.map +1 -1
  9. package/dist/src/agent/spawn.js +9 -6
  10. package/dist/src/agent/spawn.js.map +1 -1
  11. package/dist/src/cli/command-context.js +1 -0
  12. package/dist/src/cli/command-context.js.map +1 -1
  13. package/dist/src/cli/commands.js +2 -1
  14. package/dist/src/cli/commands.js.map +1 -1
  15. package/dist/src/cli/handlers.js +20 -0
  16. package/dist/src/cli/handlers.js.map +1 -1
  17. package/dist/src/cli/registry.js +6 -19
  18. package/dist/src/cli/registry.js.map +1 -1
  19. package/dist/src/core/config.js +1 -0
  20. package/dist/src/core/config.js.map +1 -1
  21. package/package.json +1 -1
  22. package/public/css/tool-ui.css +20 -1
  23. package/public/dist/assets/constants-IeOVgtYz.js +1 -0
  24. package/public/dist/assets/{employees-CpgcoYx1.js → employees-Do9d6Xi5.js} +1 -1
  25. package/public/dist/assets/{index-DVAk6-ox.css → index-qALA03H1.css} +1 -1
  26. package/public/dist/assets/{index-DlhVtNGh.js → index-yGExjgR_.js} +4 -4
  27. package/public/dist/assets/{memory-jVeY3J9Z.js → memory-DeZSzBAb.js} +1 -1
  28. package/public/dist/assets/memory-Dpe-qPbZ.js +1 -0
  29. package/public/dist/assets/render-CQnnZ-_i.js +30 -0
  30. package/public/dist/assets/{settings-DN37FmEm.js → settings-C8bSXG3q.js} +1 -1
  31. package/public/dist/assets/settings-COrhSfDh.js +1 -0
  32. package/public/dist/assets/skills-BO0V4aHG.js +1 -0
  33. package/public/dist/assets/{skills-C-Hd0Khs.js → skills-Ci5t_dsV.js} +1 -1
  34. package/public/dist/assets/{slash-commands-DKD_PVHP.js → slash-commands-0RvnZU9z.js} +1 -1
  35. package/public/dist/assets/slash-commands-DbUvFtCk.js +1 -0
  36. package/public/dist/assets/ui-Cxk1_e0b.js +1 -0
  37. package/public/dist/assets/ui-IWxpAzJ7.js +131 -0
  38. package/public/dist/assets/vendor-icons-Bs4t7RP2.js +1 -0
  39. package/public/dist/assets/ws-FsYmCE65.js +14 -0
  40. package/public/dist/index.html +2 -2
  41. package/public/js/constants.ts +5 -18
  42. package/public/js/features/process-block.ts +10 -1
  43. package/public/js/features/settings-types.ts +1 -1
  44. package/public/js/features/tool-ui.ts +2 -0
  45. package/public/js/icons.ts +15 -0
  46. package/public/js/ui.ts +80 -18
  47. package/public/js/virtual-scroll.ts +38 -8
  48. package/public/js/ws.ts +7 -3
  49. package/public/dist/assets/constants-2eOyiA6N.js +0 -1
  50. package/public/dist/assets/memory-CnXTT6wp.js +0 -1
  51. package/public/dist/assets/render-CtsdCdtV.js +0 -30
  52. package/public/dist/assets/settings-DidDB9Rr.js +0 -1
  53. package/public/dist/assets/skills-BsXI_IT2.js +0 -1
  54. package/public/dist/assets/slash-commands-DTi0pAMr.js +0 -1
  55. package/public/dist/assets/ui-BUkXAGyS.js +0 -1
  56. package/public/dist/assets/ui-N2Up5Bik.js +0 -130
  57. package/public/dist/assets/vendor-icons-C6LXvgi0.js +0 -1
  58. package/public/dist/assets/ws-Dgn9Mqgb.js +0 -14
@@ -67,6 +67,68 @@ function clipText(text, max) {
67
67
  function buildPreview(text, max = 80) {
68
68
  return clipText(toSingleLine(text), max);
69
69
  }
70
+ function appendDetail(...parts) {
71
+ return parts.map(p => String(p || '').trim()).filter(Boolean).join('\n');
72
+ }
73
+ function formatJsonDetail(label, value) {
74
+ if (value == null)
75
+ return '';
76
+ try {
77
+ return `${label}: ${typeof value === 'string' ? value : JSON.stringify(value, null, 2)}`;
78
+ }
79
+ catch {
80
+ return `${label}: ${String(value)}`;
81
+ }
82
+ }
83
+ function formatAssistantTextSegment(ctx, text) {
84
+ const raw = String(text || '');
85
+ if (!raw)
86
+ return '';
87
+ if (!ctx.outputTextStarted) {
88
+ ctx.outputTextStarted = true;
89
+ return raw;
90
+ }
91
+ if (/\s$/.test(ctx.fullText) || /^\s/.test(raw) || /^[,.;:!?)]/.test(raw) || /^-\S/.test(raw))
92
+ return raw;
93
+ return raw.startsWith('- ') || raw.startsWith('* ')
94
+ ? `\n${raw}`
95
+ : `\n- ${raw}`;
96
+ }
97
+ function appendAssistantTextSegment(ctx, text) {
98
+ const segment = formatAssistantTextSegment(ctx, text);
99
+ if (!segment)
100
+ return '';
101
+ ctx.fullText += segment;
102
+ return segment;
103
+ }
104
+ function emitGeminiThought(ctx, agentLabel, empTag, text) {
105
+ const detail = String(text || '').trim();
106
+ if (!detail)
107
+ return;
108
+ const tool = {
109
+ icon: '💭',
110
+ label: buildPreview(detail, 80) || 'thinking...',
111
+ toolType: 'thinking',
112
+ detail,
113
+ };
114
+ ctx.toolLog.push(tool);
115
+ syncLiveTools(ctx);
116
+ broadcast('agent_tool', { agentId: agentLabel, ...tool, ...empTag });
117
+ }
118
+ function extractGeminiThoughtText(content) {
119
+ if (typeof content === 'string')
120
+ return content;
121
+ if (Array.isArray(content)) {
122
+ return content
123
+ .filter((p) => p?.type === 'thought' || p?.type === 'thinking')
124
+ .map((p) => String(p.thought || p.text || p.content || ''))
125
+ .join('');
126
+ }
127
+ if (content && typeof content === 'object') {
128
+ return String(content.thought || content.text || content.content || '');
129
+ }
130
+ return '';
131
+ }
70
132
  function toIndentedPreview(text, max = 200) {
71
133
  const raw = String(text || '').trim();
72
134
  if (!raw)
@@ -84,6 +146,22 @@ function isOpencodeToolFailure(part) {
84
146
  || status === 'denied'
85
147
  || status === 'cancelled';
86
148
  }
149
+ function cleanOpencodeTaskResult(output) {
150
+ const raw = String(output || '').trim();
151
+ if (!raw)
152
+ return '';
153
+ const match = raw.match(/<task_result>([\s\S]*?)<\/task_result>/);
154
+ return (match?.[1] || raw).trim();
155
+ }
156
+ function formatOpenCodeTaskDetail(part) {
157
+ const state = part?.state || {};
158
+ const input = state.input || {};
159
+ const meta = state.metadata || {};
160
+ const model = meta.model
161
+ ? [meta.model.providerID, meta.model.modelID].filter(Boolean).join('/')
162
+ : '';
163
+ return appendDetail(input.prompt ? `prompt: ${clipText(String(input.prompt), 300)}` : '', model ? `model: ${model}` : '', meta.sessionId ? `child_session: ${meta.sessionId}` : '', cleanOpencodeTaskResult(state.output) ? `result: ${cleanOpencodeTaskResult(state.output)}` : '');
164
+ }
87
165
  function finalizeOpencodePendingTools(ctx, agentLabel, empTag) {
88
166
  const pendingRefs = ctx.opencodePendingToolRefs || [];
89
167
  if (!pendingRefs.length)
@@ -110,6 +188,11 @@ export function extractSessionId(cli, event) {
110
188
  }
111
189
  export function extractOutputChunk(cli, event, ctx) {
112
190
  if (cli === 'gemini') {
191
+ if (ctx?.pendingOutputChunk) {
192
+ const chunk = ctx.pendingOutputChunk;
193
+ ctx.pendingOutputChunk = '';
194
+ return chunk;
195
+ }
113
196
  // [#107] Skip thought/thinking events (future-proofing for when Gemini CLI adds them)
114
197
  if (event.type === 'thought' || event.thought === true)
115
198
  return '';
@@ -133,6 +216,11 @@ export function extractOutputChunk(cli, event, ctx) {
133
216
  }
134
217
  // [P0-1.5] Codex: emit agent_message text as live chunk
135
218
  if (cli === 'codex') {
219
+ if (ctx?.pendingOutputChunk) {
220
+ const chunk = ctx.pendingOutputChunk;
221
+ ctx.pendingOutputChunk = '';
222
+ return chunk;
223
+ }
136
224
  if (event.type === 'item.completed' && event.item?.type === 'agent_message') {
137
225
  return String(event.item.text || '');
138
226
  }
@@ -326,10 +414,10 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
326
414
  if (event.type === 'assistant' && event.message?.content) {
327
415
  for (const block of event.message.content) {
328
416
  if (block.type === 'text') {
329
- ctx.fullText += block.text;
417
+ const segment = appendAssistantTextSegment(ctx, block.text);
330
418
  const scope = liveScopeOf(ctx);
331
419
  if (scope)
332
- appendLiveRunText(scope, block.text);
420
+ appendLiveRunText(scope, segment);
333
421
  }
334
422
  }
335
423
  }
@@ -383,23 +471,24 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
383
471
  if (event.type === 'item.completed') {
384
472
  if (event.item?.type === 'agent_message') {
385
473
  const text = String(event.item.text || '');
386
- ctx.fullText += text;
474
+ const segment = appendAssistantTextSegment(ctx, text);
475
+ ctx.pendingOutputChunk = (ctx.pendingOutputChunk || '') + segment;
387
476
  // [spark-visibility] Spark and other lightweight codex models often
388
477
  // emit only an agent_message (no reasoning/command_execution), so the
389
478
  // toolLog would be empty and the run would be invisible in the UI.
390
479
  // Surface the final message as a 💬 entry so every codex run shows at
391
480
  // least one toolLog step. Dedup is handled by seenToolKeys via stepRef.
392
- if (text.trim()) {
481
+ if (segment.trim()) {
393
482
  const itemId = event.item.id || '';
394
483
  const tool = {
395
484
  icon: '💬',
396
- label: buildPreview(text, 80) || 'message',
485
+ label: buildPreview(segment, 80) || 'message',
397
486
  toolType: 'tool',
398
- detail: text,
487
+ detail: segment,
399
488
  stepRef: itemId ? `codex:item:${itemId}` : undefined,
400
489
  status: 'done',
401
490
  };
402
- const key = tool.stepRef || `codex:msg:${ctx.toolLog.length}:${text.slice(0, 30)}`;
491
+ const key = tool.stepRef || `codex:msg:${ctx.toolLog.length}:${segment.slice(0, 30)}`;
403
492
  if (!ctx.seenToolKeys || !ctx.seenToolKeys.has(key)) {
404
493
  if (ctx.seenToolKeys)
405
494
  ctx.seenToolKeys.add(key);
@@ -409,8 +498,15 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
409
498
  }
410
499
  }
411
500
  }
412
- if (event.item?.type === 'collab_tool_call') {
413
- ctx.hasActiveSubAgent = (event.item.status === 'in_progress');
501
+ if (event.item?.type === 'collab_tool_call'
502
+ && ['spawn_agent', 'wait'].includes(String(event.item.tool || event.item.name || ''))) {
503
+ ctx.hasActiveSubAgent = false;
504
+ }
505
+ }
506
+ else if (event.type === 'item.started') {
507
+ if (event.item?.type === 'collab_tool_call'
508
+ && ['spawn_agent', 'wait'].includes(String(event.item.tool || event.item.name || ''))) {
509
+ ctx.hasActiveSubAgent = true;
414
510
  }
415
511
  }
416
512
  else if (event.type === 'turn.completed' && event.usage) {
@@ -449,20 +545,30 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
449
545
  if (event.type === 'init' && event.model) {
450
546
  ctx.model = event.model;
451
547
  }
452
- // [#107] Skip thought/thinking events entirely don't accumulate to fullText
548
+ // [#107/#121] Thought content never enters fullText; optional visibility uses process steps.
453
549
  if (event.type === 'thought' || event.thought === true) {
454
- pushTrace(ctx, `[${agentLabel}] gemini thought (hidden)`);
550
+ if (ctx.showReasoning) {
551
+ emitGeminiThought(ctx, agentLabel, empTag, event.content || event.thought || event.text);
552
+ pushTrace(ctx, `[${agentLabel}] gemini thought (visible)`);
553
+ }
554
+ else {
555
+ pushTrace(ctx, `[${agentLabel}] gemini thought (hidden)`);
556
+ }
455
557
  break;
456
558
  }
457
559
  if (event.type === 'message' && event.role === 'assistant') {
458
560
  // [#107] If content is an array (ACP-style), extract only text parts
459
561
  if (Array.isArray(event.content)) {
562
+ if (ctx.showReasoning) {
563
+ emitGeminiThought(ctx, agentLabel, empTag, extractGeminiThoughtText(event.content));
564
+ }
460
565
  const textOnly = event.content
461
566
  .filter((p) => p?.type === 'text')
462
567
  .map((p) => String(p?.text || ''))
463
568
  .join('');
464
569
  if (textOnly) {
465
- ctx.fullText += textOnly;
570
+ const segment = appendAssistantTextSegment(ctx, textOnly);
571
+ ctx.pendingOutputChunk = (ctx.pendingOutputChunk || '') + segment;
466
572
  pushTrace(ctx, `[${agentLabel}] gemini text (filtered)`);
467
573
  }
468
574
  break;
@@ -471,7 +577,8 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
471
577
  if (event.delta) {
472
578
  pushTrace(ctx, `[${agentLabel}] gemini delta text`);
473
579
  }
474
- ctx.fullText += event.content || '';
580
+ const segment = appendAssistantTextSegment(ctx, event.content || '');
581
+ ctx.pendingOutputChunk = (ctx.pendingOutputChunk || '') + segment;
475
582
  }
476
583
  else if (event.type === 'result') {
477
584
  ctx.geminiResultSeen = true;
@@ -542,25 +649,21 @@ export function extractFromEvent(cli, event, ctx, agentLabel, empTag = {}) {
542
649
  const shouldCommitText = stepText && (event.part.reason !== 'tool-calls'
543
650
  || (!!ctx.opencodeTextAfterLastTool && !!ctx.opencodeHadToolErrorInStep));
544
651
  if (shouldCommitText) {
545
- ctx.fullText += stepText;
546
- ctx.pendingOutputChunk = (ctx.pendingOutputChunk || '') + stepText;
652
+ const segment = appendAssistantTextSegment(ctx, stepText);
653
+ ctx.pendingOutputChunk = (ctx.pendingOutputChunk || '') + segment;
547
654
  }
548
655
  else if (stepText) {
549
- if (!ctx.opencodeTextAfterLastTool) {
550
- const thinkingTool = {
551
- icon: '💭',
552
- label: buildPreview(stepText, 80) || 'thinking...',
553
- toolType: 'thinking',
554
- detail: stepText,
555
- };
556
- ctx.toolLog.push(thinkingTool);
557
- syncLiveTools(ctx);
558
- broadcast('agent_tool', { agentId: agentLabel, ...thinkingTool, ...empTag });
559
- pushTrace(ctx, `[${agentLabel}] opencode pre-tool thinking (${stepText.length} chars)`);
560
- }
561
- else {
562
- pushTrace(ctx, `[${agentLabel}] opencode buffered text discarded before tool call (${stepText.length} chars)`);
563
- }
656
+ const thinkingTool = {
657
+ icon: '💭',
658
+ label: buildPreview(stepText, 80) || 'thinking...',
659
+ toolType: 'thinking',
660
+ detail: stepText,
661
+ };
662
+ ctx.toolLog.push(thinkingTool);
663
+ syncLiveTools(ctx);
664
+ broadcast('agent_tool', { agentId: agentLabel, ...thinkingTool, ...empTag });
665
+ const phase = ctx.opencodeTextAfterLastTool ? 'post-tool' : 'pre-tool';
666
+ pushTrace(ctx, `[${agentLabel}] opencode ${phase} intermediate text (${stepText.length} chars)`);
564
667
  }
565
668
  finalizeOpencodePendingTools(ctx, agentLabel, empTag);
566
669
  ctx.opencodeStepText = '';
@@ -708,8 +811,8 @@ function pushToolLabel(labels, label, cli, event, ctx) {
708
811
  function extractToolLabels(cli, event, ctx = null) {
709
812
  const item = event.item || event.part || event;
710
813
  const labels = [];
711
- if (cli === 'codex' && event.type === 'item.completed' && item) {
712
- if (item.type === 'web_search') {
814
+ if (cli === 'codex' && (event.type === 'item.started' || event.type === 'item.completed') && item) {
815
+ if (event.type === 'item.completed' && item.type === 'web_search') {
713
816
  const action = item.action?.type || '';
714
817
  if (action === 'search') {
715
818
  const query = item.query || item.action?.query || 'search';
@@ -729,11 +832,11 @@ function extractToolLabels(cli, event, ctx = null) {
729
832
  labels.push({ icon: '🔍', label: buildPreview(query, 60), toolType: 'search', detail: query });
730
833
  }
731
834
  }
732
- if (item.type === 'reasoning') {
835
+ if (event.type === 'item.completed' && item.type === 'reasoning') {
733
836
  const detail = String(item.text || '').replace(/\*+/g, '').trim();
734
837
  labels.push({ icon: '💭', label: buildPreview(detail, 60) || 'thinking...', toolType: 'thinking', detail });
735
838
  }
736
- if (item.type === 'command_execution') {
839
+ if (event.type === 'item.completed' && item.type === 'command_execution') {
737
840
  const command = String(item.command || 'exec');
738
841
  const output = item.aggregated_output ? String(item.aggregated_output) : '';
739
842
  const detail = output ? `$ ${command}\n${output}` : command;
@@ -753,14 +856,19 @@ function extractToolLabels(cli, event, ctx = null) {
753
856
  });
754
857
  }
755
858
  if (item.type === 'collab_tool_call') {
756
- const name = item.name || 'sub-agent';
757
- const status = item.status || '';
758
- if (status === 'in_progress') {
759
- labels.push({ icon: '🔀', label: `waiting: ${name}`, toolType: 'tool' });
760
- }
761
- else {
762
- labels.push({ icon: '✅', label: `sub-agent: ${name}`, toolType: 'tool', status: 'done' });
763
- }
859
+ const tool = String(item.tool || item.name || 'subagent');
860
+ const ref = `codex:collab:${item.id || tool}`;
861
+ const isStarted = event.type === 'item.started' || item.status === 'in_progress';
862
+ const receiverIds = Array.isArray(item.receiver_thread_ids) ? item.receiver_thread_ids.join(', ') : '';
863
+ const detail = appendDetail(item.sender_thread_id ? `sender: ${item.sender_thread_id}` : '', receiverIds ? `receivers: ${receiverIds}` : '', formatJsonDetail('agents', item.agents_states), item.prompt ? `prompt: ${clipText(String(item.prompt), 300)}` : '');
864
+ labels.push({
865
+ icon: isStarted ? '🤖' : '',
866
+ label: isStarted ? `${tool}...` : `${tool} done`,
867
+ toolType: 'subagent',
868
+ stepRef: ref,
869
+ status: isStarted ? 'running' : 'done',
870
+ ...(detail ? { detail } : {}),
871
+ });
764
872
  }
765
873
  }
766
874
  // [P0-1.3] Codex item.started: emit running label (paired with 1.4 stepRef)
@@ -775,6 +883,40 @@ function extractToolLabels(cli, event, ctx = null) {
775
883
  if (event.type === 'system') {
776
884
  const status = String(event.status || '');
777
885
  const subtype = String(event.subtype || event.event || '');
886
+ if (subtype === 'task_started') {
887
+ const taskId = event.task_id || event.id || event.tool_use_id || 'unknown';
888
+ const description = event.description || event.input?.description || event.task_type || 'subagent';
889
+ const detail = appendDetail(event.task_type ? `type: ${event.task_type}` : '', event.tool_use_id ? `tool_use_id: ${event.tool_use_id}` : '', event.prompt ? `prompt: ${clipText(String(event.prompt), 300)}` : '');
890
+ pushToolLabel(labels, {
891
+ icon: '🤖',
892
+ label: `subagent: ${buildPreview(description, 60)}`,
893
+ toolType: 'subagent',
894
+ stepRef: `claude:task:${taskId}`,
895
+ status: 'running',
896
+ ...(detail ? { detail } : {}),
897
+ }, cli, event, ctx);
898
+ }
899
+ if (subtype === 'task_notification') {
900
+ const taskId = event.task_id || event.id || event.tool_use_id || 'unknown';
901
+ const rawStatus = String(event.status || 'completed');
902
+ const failed = ['failed', 'error', 'cancelled', 'canceled'].includes(rawStatus);
903
+ const description = event.description || event.summary || event.task_type || 'subagent';
904
+ const usage = event.usage || {};
905
+ const usageDetail = [
906
+ usage.total_tokens != null ? `${usage.total_tokens} tok` : '',
907
+ usage.tool_uses != null ? `${usage.tool_uses} tools` : '',
908
+ usage.duration_ms != null ? `${(Number(usage.duration_ms) / 1000).toFixed(1)}s` : '',
909
+ ].filter(Boolean).join(' · ');
910
+ const detail = appendDetail(event.summary ? `summary: ${event.summary}` : '', event.output_file ? `output_file: ${event.output_file}` : '', usageDetail);
911
+ pushToolLabel(labels, {
912
+ icon: failed ? '❌' : '✅',
913
+ label: `subagent: ${buildPreview(description, 60)}`,
914
+ toolType: 'subagent',
915
+ stepRef: `claude:task:${taskId}`,
916
+ status: failed ? 'error' : 'done',
917
+ ...(detail ? { detail } : {}),
918
+ }, cli, event, ctx);
919
+ }
778
920
  if (status === 'compacting' || subtype === 'compacting') {
779
921
  pushToolLabel(labels, { icon: '🗜️', label: 'compacting...', toolType: 'tool' }, cli, event, ctx);
780
922
  }
@@ -788,14 +930,30 @@ function extractToolLabels(cli, event, ctx = null) {
788
930
  if (ctx)
789
931
  ctx.hasClaudeStreamEvents = true;
790
932
  const cb = event.event.content_block;
791
- if (cb?.type === 'tool_use')
792
- pushToolLabel(labels, { icon: '🔧', label: cb.name || 'tool', toolType: 'tool', stepRef: cb.id ? `claude:tooluse:${cb.id}` : undefined }, cli, event, ctx);
933
+ if (cb?.type === 'tool_use') {
934
+ const isAgent = cb.name === 'Agent';
935
+ pushToolLabel(labels, {
936
+ icon: isAgent ? '🤖' : '🔧',
937
+ label: isAgent ? 'subagent' : (cb.name || 'tool'),
938
+ toolType: isAgent ? 'subagent' : 'tool',
939
+ stepRef: cb.id ? `claude:tooluse:${cb.id}` : undefined,
940
+ }, cli, event, ctx);
941
+ }
793
942
  // thinking: don't emit placeholder — buffer in extractFromEvent will emit with real content
794
943
  }
795
944
  if (event.type === 'assistant' && event.message?.content && !ctx?.hasClaudeStreamEvents) {
796
945
  for (const block of event.message.content) {
797
- if (block.type === 'tool_use')
798
- pushToolLabel(labels, { icon: '🔧', label: block.name || 'tool', toolType: 'tool', stepRef: block.id ? `claude:tooluse:${block.id}` : undefined }, cli, event, ctx);
946
+ if (block.type === 'tool_use') {
947
+ const isAgent = block.name === 'Agent';
948
+ const description = block.input?.description || block.input?.subagent_type || 'subagent';
949
+ pushToolLabel(labels, {
950
+ icon: isAgent ? '🤖' : '🔧',
951
+ label: isAgent ? `subagent: ${buildPreview(description, 60)}` : (block.name || 'tool'),
952
+ toolType: isAgent ? 'subagent' : 'tool',
953
+ stepRef: block.id ? `claude:tooluse:${block.id}` : undefined,
954
+ ...(isAgent && block.input?.prompt ? { detail: `prompt: ${clipText(String(block.input.prompt), 300)}` } : {}),
955
+ }, cli, event, ctx);
956
+ }
799
957
  if (block.type === 'thinking') {
800
958
  const text = (block.thinking || '').trim();
801
959
  pushToolLabel(labels, { icon: '💭', label: buildPreview(text, 80) || 'thinking...', toolType: 'thinking', detail: text }, cli, event, ctx);
@@ -829,6 +987,40 @@ function extractToolLabels(cli, event, ctx = null) {
829
987
  }
830
988
  }
831
989
  if (cli === 'opencode') {
990
+ const isTaskToolUse = event.type === 'tool_use' && event.part?.tool === 'task';
991
+ const isTaskToolResult = event.type === 'tool_result'
992
+ && event.part?.callID
993
+ && ctx?.opencodeTaskCallIds?.has(event.part.callID);
994
+ if (isTaskToolUse || isTaskToolResult) {
995
+ const part = event.part;
996
+ const callID = part.callID || part.id || 'task';
997
+ if (isTaskToolResult && !part.state)
998
+ return labels;
999
+ if (isTaskToolUse && ctx) {
1000
+ if (!ctx.opencodeTaskCallIds)
1001
+ ctx.opencodeTaskCallIds = new Set();
1002
+ ctx.opencodeTaskCallIds.add(callID);
1003
+ }
1004
+ const state = part.state || {};
1005
+ const input = state.input || {};
1006
+ const status = String(state.status || (event.type === 'tool_result' ? 'completed' : 'completed'));
1007
+ const failed = isOpencodeToolFailure(part) || ['error', 'failed', 'cancelled', 'canceled'].includes(status);
1008
+ const subagentType = input.subagent_type || 'general';
1009
+ const description = input.description || state.title || part.tool || 'task';
1010
+ const resultText = event.type === 'tool_result'
1011
+ ? extractText(part.content || part.output || state.output)
1012
+ : '';
1013
+ const detail = appendDetail(formatOpenCodeTaskDetail(part), resultText ? `result: ${resultText}` : '');
1014
+ labels.push({
1015
+ icon: failed ? '❌' : (status === 'running' || status === 'in_progress' ? '🤖' : '✅'),
1016
+ label: `subagent[${subagentType}]: ${buildPreview(description, 60)}`,
1017
+ toolType: 'subagent',
1018
+ stepRef: `opencode:call:${callID}`,
1019
+ ...(detail ? { detail } : {}),
1020
+ status: failed ? 'error' : (status === 'running' || status === 'in_progress' ? 'running' : 'done'),
1021
+ });
1022
+ return labels;
1023
+ }
832
1024
  if (event.type === 'tool_use' && event.part) {
833
1025
  const ref = event.part.callID
834
1026
  ? `opencode:call:${event.part.callID}`
@@ -933,7 +1125,7 @@ function extractText(content) {
933
1125
  }
934
1126
  return '';
935
1127
  }
936
- export function extractFromAcpUpdate(params) {
1128
+ export function extractFromAcpUpdate(params, ctx = null) {
937
1129
  const update = params?.update;
938
1130
  if (!update)
939
1131
  return null;
@@ -952,20 +1144,35 @@ export function extractFromAcpUpdate(params) {
952
1144
  }
953
1145
  case 'tool_call': {
954
1146
  const toolName = update.name || 'tool';
1147
+ const rawInput = update.rawInput || update.input || {};
1148
+ const isSubagentTask = rawInput?.agent_type === 'task' || rawInput?.agentType === 'task';
1149
+ const displayLabel = isSubagentTask
1150
+ ? `subagent: ${update.title || rawInput.description || rawInput.name || toolName}`
1151
+ : update.title || toolName;
1152
+ if (isSubagentTask && update.toolCallId && ctx) {
1153
+ if (!ctx.acpSubagentToolCallIds)
1154
+ ctx.acpSubagentToolCallIds = new Set();
1155
+ if (!ctx.acpSubagentLabels)
1156
+ ctx.acpSubagentLabels = new Map();
1157
+ ctx.acpSubagentToolCallIds.add(update.toolCallId);
1158
+ ctx.acpSubagentLabels.set(update.toolCallId, displayLabel);
1159
+ }
955
1160
  const fullInput = update.input != null
956
1161
  ? (typeof update.input === 'object' ? JSON.stringify(update.input, null, 2) : String(update.input))
957
- : '';
1162
+ : update.rawInput != null
1163
+ ? (typeof update.rawInput === 'object' ? JSON.stringify(update.rawInput, null, 2) : String(update.rawInput))
1164
+ : '';
958
1165
  // [P1-2.10] Semantic icon from tool kind/title
959
1166
  const kindIcon = toolKindIcon(update.kind);
960
- const displayLabel = update.title || toolName;
961
1167
  // [P0-1.11] Use toolCallId for unique stepRef
962
1168
  return {
963
1169
  tool: {
964
- icon: kindIcon || '🔧',
1170
+ icon: isSubagentTask ? '🤖' : (kindIcon || '🔧'),
965
1171
  label: displayLabel,
966
- toolType: 'tool',
1172
+ toolType: isSubagentTask ? 'subagent' : 'tool',
967
1173
  detail: fullInput,
968
1174
  stepRef: `acp:callid:${update.toolCallId || update.id || toolName}`,
1175
+ ...(isSubagentTask ? { status: 'running' } : {}),
969
1176
  },
970
1177
  };
971
1178
  }
@@ -979,14 +1186,17 @@ export function extractFromAcpUpdate(params) {
979
1186
  failed: { icon: '❌', status: 'error' },
980
1187
  };
981
1188
  const mapped = statusMap[update.status] || { icon: '❔', status: update.status || 'unknown' };
1189
+ const toolCallId = update.toolCallId || update.id || update.name || 'done';
1190
+ const isSubagentTask = !!(toolCallId && ctx?.acpSubagentToolCallIds?.has(toolCallId));
1191
+ const subagentLabel = toolCallId ? ctx?.acpSubagentLabels?.get(toolCallId) : '';
982
1192
  // [P1-2.9] Extract content from tool result
983
1193
  const resultText = update.content ? extractText(update.content) : '';
984
1194
  return {
985
1195
  tool: {
986
1196
  icon: mapped.icon,
987
- label: update.name || update.id || 'done',
988
- toolType: 'tool',
989
- stepRef: `acp:callid:${update.toolCallId || update.id || update.name || 'done'}`,
1197
+ label: isSubagentTask ? (subagentLabel || `subagent: ${update.name || update.title || 'task'}`) : update.name || update.id || 'done',
1198
+ toolType: isSubagentTask ? 'subagent' : 'tool',
1199
+ stepRef: `acp:callid:${toolCallId}`,
990
1200
  status: mapped.status,
991
1201
  ...(resultText ? { detail: buildPreview(resultText, 200) } : {}),
992
1202
  },
@@ -1036,4 +1246,68 @@ export function extractFromAcpUpdate(params) {
1036
1246
  return null;
1037
1247
  }
1038
1248
  }
1249
+ export function extractFromAcpSubagent(event) {
1250
+ if (!event?.type || !String(event.type).startsWith('subagent.'))
1251
+ return null;
1252
+ const data = event.data || {};
1253
+ const display = data.agentDisplayName || data.agentName || 'subagent';
1254
+ const agentName = data.agentName || display;
1255
+ switch (event.type) {
1256
+ case 'subagent.selected':
1257
+ return {
1258
+ tool: {
1259
+ icon: '🎯',
1260
+ label: `selected: ${display}`,
1261
+ toolType: 'subagent',
1262
+ stepRef: `acp:subagent:selection:${agentName}`,
1263
+ status: 'done',
1264
+ detail: `tools: ${Array.isArray(data.tools) ? data.tools.join(', ') : 'all'}`,
1265
+ },
1266
+ };
1267
+ case 'subagent.deselected':
1268
+ return {
1269
+ tool: {
1270
+ icon: '⏭',
1271
+ label: `deselected: ${display}`,
1272
+ toolType: 'subagent',
1273
+ stepRef: `acp:subagent:selection:${agentName}`,
1274
+ status: 'done',
1275
+ },
1276
+ };
1277
+ case 'subagent.started':
1278
+ return {
1279
+ tool: {
1280
+ icon: '🤖',
1281
+ label: `subagent: ${display}`,
1282
+ toolType: 'subagent',
1283
+ stepRef: `acp:subagent:${data.toolCallId || agentName}`,
1284
+ status: 'running',
1285
+ ...(data.agentDescription ? { detail: data.agentDescription } : {}),
1286
+ },
1287
+ };
1288
+ case 'subagent.completed':
1289
+ return {
1290
+ tool: {
1291
+ icon: '✅',
1292
+ label: `subagent: ${display}`,
1293
+ toolType: 'subagent',
1294
+ stepRef: `acp:subagent:${data.toolCallId || agentName}`,
1295
+ status: 'done',
1296
+ },
1297
+ };
1298
+ case 'subagent.failed':
1299
+ return {
1300
+ tool: {
1301
+ icon: '❌',
1302
+ label: `subagent: ${display}`,
1303
+ toolType: 'subagent',
1304
+ stepRef: `acp:subagent:${data.toolCallId || agentName}`,
1305
+ status: 'error',
1306
+ detail: `error: ${data.error || ''}`,
1307
+ },
1308
+ };
1309
+ default:
1310
+ return null;
1311
+ }
1312
+ }
1039
1313
  //# sourceMappingURL=events.js.map