Skip to content

Commit 0df1ca2

Browse files
committed
feat: subaget visualization in ai panel
1 parent 648f109 commit 0df1ca2

2 files changed

Lines changed: 171 additions & 75 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 170 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -378,12 +378,18 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
378378
let accumulatedText = "";
379379
let lastStreamTime = 0;
380380

381-
// Tool input tracking
381+
// Tool input tracking (parent-level)
382382
let activeToolName = null;
383383
let activeToolIndex = null;
384384
let activeToolInputJson = "";
385385
let lastToolStreamTime = 0;
386386

387+
// Sub-agent tool tracking
388+
let subagentToolName = null;
389+
let subagentToolIndex = null;
390+
let subagentToolInputJson = "";
391+
let lastSubagentToolStreamTime = 0;
392+
387393
// Trace counters (logged at tool/query completion, not per-delta)
388394
let toolDeltaCount = 0;
389395
let toolStreamSendCount = 0;
@@ -406,94 +412,184 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
406412
// Handle streaming events
407413
if (message.type === "stream_event") {
408414
const event = message.event;
415+
const isSubagent = !!message.parent_tool_use_id;
416+
417+
if (isSubagent) {
418+
// --- Sub-agent events ---
419+
420+
// Sub-agent tool use start
421+
if (event.type === "content_block_start" &&
422+
event.content_block?.type === "tool_use") {
423+
subagentToolName = event.content_block.name;
424+
subagentToolIndex = event.index;
425+
subagentToolInputJson = "";
426+
toolCounter++;
427+
lastSubagentToolStreamTime = 0;
428+
_log("Subagent tool start:", subagentToolName, "#" + toolCounter);
429+
nodeConnector.triggerPeer("aiProgress", {
430+
requestId: requestId,
431+
toolName: subagentToolName,
432+
toolId: toolCounter,
433+
phase: "tool_use"
434+
});
435+
}
409436

410-
// Tool use start — send initial indicator
411-
if (event.type === "content_block_start" &&
412-
event.content_block?.type === "tool_use") {
413-
activeToolName = event.content_block.name;
414-
activeToolIndex = event.index;
415-
activeToolInputJson = "";
416-
toolCounter++;
417-
toolDeltaCount = 0;
418-
toolStreamSendCount = 0;
419-
lastToolStreamTime = 0;
420-
_log("Tool start:", activeToolName, "#" + toolCounter);
421-
nodeConnector.triggerPeer("aiProgress", {
422-
requestId: requestId,
423-
toolName: activeToolName,
424-
toolId: toolCounter,
425-
phase: "tool_use"
426-
});
427-
}
437+
// Sub-agent tool input streaming
438+
if (event.type === "content_block_delta" &&
439+
event.delta?.type === "input_json_delta" &&
440+
event.index === subagentToolIndex) {
441+
subagentToolInputJson += event.delta.partial_json;
442+
const now = Date.now();
443+
if (subagentToolInputJson &&
444+
now - lastSubagentToolStreamTime >= TEXT_STREAM_THROTTLE_MS) {
445+
lastSubagentToolStreamTime = now;
446+
nodeConnector.triggerPeer("aiToolStream", {
447+
requestId: requestId,
448+
toolId: toolCounter,
449+
toolName: subagentToolName,
450+
partialJson: subagentToolInputJson
451+
});
452+
}
453+
}
428454

429-
// Accumulate tool input JSON and stream preview
430-
if (event.type === "content_block_delta" &&
431-
event.delta?.type === "input_json_delta" &&
432-
event.index === activeToolIndex) {
433-
activeToolInputJson += event.delta.partial_json;
434-
toolDeltaCount++;
435-
const now = Date.now();
436-
if (activeToolInputJson &&
437-
now - lastToolStreamTime >= TEXT_STREAM_THROTTLE_MS) {
438-
lastToolStreamTime = now;
439-
toolStreamSendCount++;
440-
nodeConnector.triggerPeer("aiToolStream", {
455+
// Sub-agent tool block complete
456+
if (event.type === "content_block_stop" &&
457+
event.index === subagentToolIndex &&
458+
subagentToolName) {
459+
if (subagentToolInputJson) {
460+
nodeConnector.triggerPeer("aiToolStream", {
461+
requestId: requestId,
462+
toolId: toolCounter,
463+
toolName: subagentToolName,
464+
partialJson: subagentToolInputJson
465+
});
466+
}
467+
let toolInput = {};
468+
try {
469+
toolInput = JSON.parse(subagentToolInputJson);
470+
} catch (e) {
471+
// ignore parse errors
472+
}
473+
_log("Subagent tool done:", subagentToolName, "#" + toolCounter,
474+
"json=" + subagentToolInputJson.length + "ch");
475+
nodeConnector.triggerPeer("aiToolInfo", {
441476
requestId: requestId,
477+
toolName: subagentToolName,
442478
toolId: toolCounter,
443-
toolName: activeToolName,
444-
partialJson: activeToolInputJson
479+
toolInput: toolInput
445480
});
481+
subagentToolName = null;
482+
subagentToolIndex = null;
483+
subagentToolInputJson = "";
446484
}
447-
}
448485

449-
// Tool block complete — flush final stream preview and send details
450-
if (event.type === "content_block_stop" &&
451-
event.index === activeToolIndex &&
452-
activeToolName) {
453-
// Final flush of tool stream (bypasses throttle)
454-
if (activeToolInputJson) {
455-
toolStreamSendCount++;
456-
nodeConnector.triggerPeer("aiToolStream", {
486+
// Sub-agent text deltas — stream as regular text
487+
if (event.type === "content_block_delta" &&
488+
event.delta?.type === "text_delta") {
489+
accumulatedText += event.delta.text;
490+
textDeltaCount++;
491+
const now = Date.now();
492+
if (now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS) {
493+
lastStreamTime = now;
494+
textStreamSendCount++;
495+
nodeConnector.triggerPeer("aiTextStream", {
496+
requestId: requestId,
497+
text: accumulatedText
498+
});
499+
accumulatedText = "";
500+
}
501+
}
502+
} else {
503+
// --- Parent-level events (unchanged) ---
504+
505+
// Tool use start — send initial indicator
506+
if (event.type === "content_block_start" &&
507+
event.content_block?.type === "tool_use") {
508+
activeToolName = event.content_block.name;
509+
activeToolIndex = event.index;
510+
activeToolInputJson = "";
511+
toolCounter++;
512+
toolDeltaCount = 0;
513+
toolStreamSendCount = 0;
514+
lastToolStreamTime = 0;
515+
_log("Tool start:", activeToolName, "#" + toolCounter);
516+
nodeConnector.triggerPeer("aiProgress", {
457517
requestId: requestId,
458-
toolId: toolCounter,
459518
toolName: activeToolName,
460-
partialJson: activeToolInputJson
519+
toolId: toolCounter,
520+
phase: "tool_use"
461521
});
462522
}
463-
let toolInput = {};
464-
try {
465-
toolInput = JSON.parse(activeToolInputJson);
466-
} catch (e) {
467-
// ignore parse errors
523+
524+
// Accumulate tool input JSON and stream preview
525+
if (event.type === "content_block_delta" &&
526+
event.delta?.type === "input_json_delta" &&
527+
event.index === activeToolIndex) {
528+
activeToolInputJson += event.delta.partial_json;
529+
toolDeltaCount++;
530+
const now = Date.now();
531+
if (activeToolInputJson &&
532+
now - lastToolStreamTime >= TEXT_STREAM_THROTTLE_MS) {
533+
lastToolStreamTime = now;
534+
toolStreamSendCount++;
535+
nodeConnector.triggerPeer("aiToolStream", {
536+
requestId: requestId,
537+
toolId: toolCounter,
538+
toolName: activeToolName,
539+
partialJson: activeToolInputJson
540+
});
541+
}
468542
}
469-
_log("Tool done:", activeToolName, "#" + toolCounter,
470-
"deltas=" + toolDeltaCount, "sent=" + toolStreamSendCount,
471-
"json=" + activeToolInputJson.length + "ch");
472-
nodeConnector.triggerPeer("aiToolInfo", {
473-
requestId: requestId,
474-
toolName: activeToolName,
475-
toolId: toolCounter,
476-
toolInput: toolInput
477-
});
478-
activeToolName = null;
479-
activeToolIndex = null;
480-
activeToolInputJson = "";
481-
}
482543

483-
// Stream text deltas (throttled)
484-
if (event.type === "content_block_delta" &&
485-
event.delta?.type === "text_delta") {
486-
accumulatedText += event.delta.text;
487-
textDeltaCount++;
488-
const now = Date.now();
489-
if (now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS) {
490-
lastStreamTime = now;
491-
textStreamSendCount++;
492-
nodeConnector.triggerPeer("aiTextStream", {
544+
// Tool block complete — flush final stream preview and send details
545+
if (event.type === "content_block_stop" &&
546+
event.index === activeToolIndex &&
547+
activeToolName) {
548+
// Final flush of tool stream (bypasses throttle)
549+
if (activeToolInputJson) {
550+
toolStreamSendCount++;
551+
nodeConnector.triggerPeer("aiToolStream", {
552+
requestId: requestId,
553+
toolId: toolCounter,
554+
toolName: activeToolName,
555+
partialJson: activeToolInputJson
556+
});
557+
}
558+
let toolInput = {};
559+
try {
560+
toolInput = JSON.parse(activeToolInputJson);
561+
} catch (e) {
562+
// ignore parse errors
563+
}
564+
_log("Tool done:", activeToolName, "#" + toolCounter,
565+
"deltas=" + toolDeltaCount, "sent=" + toolStreamSendCount,
566+
"json=" + activeToolInputJson.length + "ch");
567+
nodeConnector.triggerPeer("aiToolInfo", {
493568
requestId: requestId,
494-
text: accumulatedText
569+
toolName: activeToolName,
570+
toolId: toolCounter,
571+
toolInput: toolInput
495572
});
496-
accumulatedText = "";
573+
activeToolName = null;
574+
activeToolIndex = null;
575+
activeToolInputJson = "";
576+
}
577+
578+
// Stream text deltas (throttled)
579+
if (event.type === "content_block_delta" &&
580+
event.delta?.type === "text_delta") {
581+
accumulatedText += event.delta.text;
582+
textDeltaCount++;
583+
const now = Date.now();
584+
if (now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS) {
585+
lastStreamTime = now;
586+
textStreamSendCount++;
587+
nodeConnector.triggerPeer("aiTextStream", {
588+
requestId: requestId,
589+
text: accumulatedText
590+
});
591+
accumulatedText = "";
592+
}
497593
}
498594
}
499595
}

src/nls/root/strings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1825,7 +1825,7 @@ define({
18251825
"AI_CHAT_TOOL_SCREENSHOT_OF": "Screenshot of {0}",
18261826
"AI_CHAT_TOOL_SCREENSHOT_LIVE_PREVIEW": "live preview",
18271827
"AI_CHAT_TOOL_SCREENSHOT_FULL_PAGE": "full page",
1828-
"AI_CHAT_TOOL_LIVE_PREVIEW_JS": "Live Preview JS",
1828+
"AI_CHAT_TOOL_LIVE_PREVIEW_JS": "Inspecting preview",
18291829
"AI_CHAT_TOOL_CONTROL_EDITOR": "Editor",
18301830
"AI_CHAT_TOOL_TASKS": "Tasks",
18311831
"AI_CHAT_TOOL_TASKS_SUMMARY": "{0} of {1} tasks done",

0 commit comments

Comments
 (0)