@townco/agent 0.1.120 → 0.1.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-server/adapter.js +19 -1
- package/dist/acp-server/session-storage.d.ts +7 -1
- package/dist/acp-server/session-storage.js +11 -1
- package/dist/runner/agent-runner.d.ts +2 -1
- package/dist/runner/langchain/index.js +25 -111
- package/dist/runner/langchain/tools/subagent.js +151 -188
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/dist/runner/langchain/tools/artifacts.d.ts +0 -68
- package/dist/runner/langchain/tools/artifacts.js +0 -474
- package/dist/runner/langchain/tools/conversation_search.d.ts +0 -22
- package/dist/runner/langchain/tools/conversation_search.js +0 -137
- package/dist/runner/langchain/tools/generate_image.d.ts +0 -47
- package/dist/runner/langchain/tools/generate_image.js +0 -175
- package/dist/runner/langchain/tools/port-utils.d.ts +0 -8
- package/dist/runner/langchain/tools/port-utils.js +0 -35
|
@@ -2,25 +2,28 @@ import * as crypto from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import
|
|
5
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
6
6
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
import { createLogger } from "@townco/core";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { SUBAGENT_MODE_KEY, } from "../../../acp-server/adapter.js";
|
|
9
|
+
import { AgentAcpAdapter, SUBAGENT_MODE_KEY, } from "../../../acp-server/adapter.js";
|
|
10
10
|
import { makeRunnerFromDefinition } from "../../index.js";
|
|
11
|
-
import { bindGeneratorToSessionContext, getAbortSignal, } from "../../session-context.js";
|
|
11
|
+
import { bindGeneratorToSessionContext, getAbortSignal, getSessionContext, } from "../../session-context.js";
|
|
12
12
|
import { emitSubagentMessages, hashQuery, } from "./subagent-connections.js";
|
|
13
13
|
const logger = createLogger("subagent-tool", "debug");
|
|
14
14
|
/**
|
|
15
15
|
* Generate status message using Haiku (fast, cheap model)
|
|
16
|
+
*
|
|
17
|
+
* Important: Uses Anthropic SDK directly (not LangChain) to completely isolate
|
|
18
|
+
* from the LangChain streaming context and prevent status text from leaking
|
|
19
|
+
* into the subagent's content stream.
|
|
16
20
|
*/
|
|
17
21
|
async function generateStatusMessage(recentContent, toolCalls) {
|
|
18
22
|
try {
|
|
19
23
|
const activeTool = toolCalls.find((tc) => tc.status === "in_progress");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
maxTokens: 30,
|
|
24
|
+
// Use Anthropic SDK directly to bypass LangChain callbacks entirely
|
|
25
|
+
const anthropic = new Anthropic({
|
|
26
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
24
27
|
});
|
|
25
28
|
const prompt = `Summarize the current activity in 5-7 words for a progress indicator:
|
|
26
29
|
|
|
@@ -34,9 +37,20 @@ Requirements:
|
|
|
34
37
|
- Return ONLY the status, no explanation
|
|
35
38
|
|
|
36
39
|
Status:`;
|
|
37
|
-
const response = await
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const response = await anthropic.messages.create({
|
|
41
|
+
model: "claude-haiku-4-5-20251001",
|
|
42
|
+
max_tokens: 30,
|
|
43
|
+
temperature: 0.3,
|
|
44
|
+
messages: [
|
|
45
|
+
{
|
|
46
|
+
role: "user",
|
|
47
|
+
content: prompt,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
const textContent = response.content.find((block) => block.type === "text");
|
|
52
|
+
const status = textContent?.type === "text" ? textContent.text.trim().slice(0, 80) : "";
|
|
53
|
+
return status || extractHeuristicStatus(recentContent, toolCalls);
|
|
40
54
|
}
|
|
41
55
|
catch (error) {
|
|
42
56
|
logger.warn("Failed to generate status message", { error });
|
|
@@ -381,116 +395,19 @@ assistant: "I'm going to use the Task tool to launch the greeting-responder agen
|
|
|
381
395
|
};
|
|
382
396
|
}
|
|
383
397
|
/**
|
|
384
|
-
*
|
|
398
|
+
* Creates an ACP connection that forwards streaming updates to emitSubagentMessages.
|
|
399
|
+
* This allows subagents to run through the adapter layer (getting full session persistence
|
|
400
|
+
* and hook execution) while still providing real-time streaming updates to the parent.
|
|
385
401
|
*/
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (parentAbortSignal?.aborted) {
|
|
391
|
-
throw new Error(`Subagent query cancelled before starting (agent: ${agentName})`);
|
|
392
|
-
}
|
|
393
|
-
// Validate that the agent exists
|
|
394
|
-
try {
|
|
395
|
-
await fs.access(agentPath);
|
|
396
|
-
}
|
|
397
|
-
catch (_error) {
|
|
398
|
-
throw new Error(`Subagent '${agentName}' not found at ${agentPath}. Make sure the agent exists and has an index.ts file.`);
|
|
399
|
-
}
|
|
400
|
-
// Load agent definition dynamically
|
|
401
|
-
const agentModule = await import(agentPath);
|
|
402
|
-
const agentDefinition = agentModule.default || agentModule.agent;
|
|
403
|
-
// Create runner instance
|
|
404
|
-
const runner = makeRunnerFromDefinition(agentDefinition);
|
|
405
|
-
// Generate unique session ID for isolation
|
|
406
|
-
const subagentSessionId = crypto.randomUUID();
|
|
407
|
-
// Setup session paths
|
|
408
|
-
const sessionDir = path.join(agentWorkingDirectory, ".sessions", subagentSessionId);
|
|
409
|
-
const artifactsDir = path.join(sessionDir, "artifacts");
|
|
410
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
411
|
-
// Prepare OTEL context for distributed tracing
|
|
412
|
-
const activeCtx = context.active();
|
|
413
|
-
const activeSpan = trace.getSpan(activeCtx);
|
|
414
|
-
const otelCarrier = {};
|
|
415
|
-
if (activeSpan) {
|
|
416
|
-
propagation.inject(trace.setSpan(activeCtx, activeSpan), otelCarrier);
|
|
417
|
-
// Set span attributes for observability
|
|
418
|
-
activeSpan.setAttributes({
|
|
419
|
-
"subagent.semantic_name": taskName,
|
|
420
|
-
"subagent.agent_definition": agentName,
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
// Create invoke request
|
|
424
|
-
const invokeRequest = {
|
|
425
|
-
sessionId: subagentSessionId,
|
|
426
|
-
messageId: crypto.randomUUID(),
|
|
427
|
-
prompt: [{ type: "text", text: query }],
|
|
428
|
-
agentDir: agentWorkingDirectory,
|
|
429
|
-
contextMessages: [],
|
|
430
|
-
...(parentAbortSignal ? { abortSignal: parentAbortSignal } : {}),
|
|
431
|
-
sessionMeta: {
|
|
432
|
-
[SUBAGENT_MODE_KEY]: true,
|
|
433
|
-
...(Object.keys(otelCarrier).length > 0
|
|
434
|
-
? { otelTraceContext: otelCarrier }
|
|
435
|
-
: {}),
|
|
436
|
-
},
|
|
437
|
-
};
|
|
438
|
-
// Bind session context and invoke
|
|
439
|
-
let generator = runner.invoke(invokeRequest);
|
|
440
|
-
generator = bindGeneratorToSessionContext({ sessionId: subagentSessionId, sessionDir, artifactsDir }, generator);
|
|
441
|
-
// Consume stream, accumulate results, and emit incremental updates
|
|
442
|
-
let responseText = "";
|
|
443
|
-
const collectedSources = [];
|
|
444
|
-
const sourceCounter = { value: 0 }; // Mutable counter for source ID generation
|
|
445
|
-
const currentMessage = {
|
|
446
|
-
id: `subagent-${Date.now()}`,
|
|
447
|
-
content: "",
|
|
448
|
-
contentBlocks: [],
|
|
449
|
-
toolCalls: [],
|
|
450
|
-
_meta: {
|
|
451
|
-
semanticName: taskName,
|
|
452
|
-
agentDefinitionName: agentName,
|
|
453
|
-
statusGenerating: true,
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
const toolCallMap = new Map();
|
|
457
|
-
const toolNameMap = new Map(); // Map toolCallId -> toolName
|
|
458
|
-
const queryHash = hashQuery(query);
|
|
459
|
-
// Emit initial message with semantic name immediately
|
|
460
|
-
emitSubagentMessages(queryHash, [currentMessage]);
|
|
461
|
-
// Track status updates for periodic generation
|
|
462
|
-
let lastStatusUpdate = Date.now();
|
|
463
|
-
let statusUpdateInProgress = false;
|
|
464
|
-
const STATUS_UPDATE_INTERVAL = 5000; // 5 seconds
|
|
465
|
-
// Fire async initial status generation (don't await)
|
|
466
|
-
generateStatusMessage(query, [])
|
|
467
|
-
.then((status) => {
|
|
468
|
-
if (currentMessage._meta) {
|
|
469
|
-
currentMessage._meta.currentActivity = status;
|
|
470
|
-
currentMessage._meta.statusGenerating = false;
|
|
471
|
-
}
|
|
472
|
-
// Emit update with status
|
|
473
|
-
emitSubagentMessages(queryHash, [currentMessage]);
|
|
474
|
-
})
|
|
475
|
-
.catch((error) => {
|
|
476
|
-
logger.warn("Initial status generation failed", { error });
|
|
477
|
-
if (currentMessage._meta) {
|
|
478
|
-
currentMessage._meta.statusGenerating = false;
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
logger.info("[DEBUG] Starting subagent generator loop", {
|
|
482
|
-
agentName,
|
|
483
|
-
queryHash,
|
|
484
|
-
sessionId: subagentSessionId,
|
|
485
|
-
});
|
|
486
|
-
try {
|
|
487
|
-
for await (const update of generator) {
|
|
402
|
+
function createStreamingConnection(queryHash, currentMessage, toolCallMap, toolNameMap, collectedSources, sourceCounter) {
|
|
403
|
+
return {
|
|
404
|
+
sessionUpdate: (notification) => {
|
|
405
|
+
const update = notification.update;
|
|
488
406
|
let shouldEmit = false;
|
|
489
407
|
// Handle agent_message_chunk
|
|
490
408
|
if (update.sessionUpdate === "agent_message_chunk") {
|
|
491
409
|
const content = update.content;
|
|
492
410
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
493
|
-
responseText += content.text;
|
|
494
411
|
currentMessage.content += content.text;
|
|
495
412
|
const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
|
|
496
413
|
if (lastBlock && lastBlock.type === "text") {
|
|
@@ -502,15 +419,13 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
502
419
|
text: content.text,
|
|
503
420
|
});
|
|
504
421
|
}
|
|
505
|
-
shouldEmit = true;
|
|
422
|
+
shouldEmit = true;
|
|
506
423
|
}
|
|
507
424
|
}
|
|
508
425
|
// Handle tool_call
|
|
509
426
|
if (update.sessionUpdate === "tool_call" && update.toolCallId) {
|
|
510
427
|
const meta = update._meta;
|
|
511
|
-
|
|
512
|
-
const rawInput = update
|
|
513
|
-
.rawInput;
|
|
428
|
+
const rawInput = update.rawInput;
|
|
514
429
|
const toolCall = {
|
|
515
430
|
id: update.toolCallId,
|
|
516
431
|
title: update.title || "Tool call",
|
|
@@ -521,12 +436,11 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
521
436
|
};
|
|
522
437
|
currentMessage.toolCalls.push(toolCall);
|
|
523
438
|
toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
|
|
524
|
-
// Store tool name for source extraction when output arrives
|
|
525
439
|
if (update.title) {
|
|
526
440
|
toolNameMap.set(update.toolCallId, update.title);
|
|
527
441
|
}
|
|
528
442
|
currentMessage.contentBlocks.push({ type: "tool_call", toolCall });
|
|
529
|
-
shouldEmit = true;
|
|
443
|
+
shouldEmit = true;
|
|
530
444
|
}
|
|
531
445
|
// Handle tool_call_update
|
|
532
446
|
if (update.sessionUpdate === "tool_call_update" && update.toolCallId) {
|
|
@@ -540,25 +454,22 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
540
454
|
if (block && update.status) {
|
|
541
455
|
block.toolCall.status = update.status;
|
|
542
456
|
}
|
|
543
|
-
shouldEmit = true;
|
|
457
|
+
shouldEmit = true;
|
|
544
458
|
}
|
|
545
459
|
}
|
|
546
|
-
// Handle tool_output
|
|
547
|
-
// (rawOutput will be included in final emit when subagent completes)
|
|
460
|
+
// Handle tool_output
|
|
548
461
|
if (update.sessionUpdate === "tool_output" && update.toolCallId) {
|
|
549
|
-
const outputUpdate = update;
|
|
550
462
|
const idx = toolCallMap.get(update.toolCallId);
|
|
551
463
|
if (idx !== undefined && currentMessage.toolCalls[idx]) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
464
|
+
const rawOutput = update.rawOutput;
|
|
465
|
+
if (rawOutput) {
|
|
466
|
+
currentMessage.toolCalls[idx].rawOutput = rawOutput;
|
|
555
467
|
const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" && b.toolCall.id === update.toolCallId);
|
|
556
468
|
if (block) {
|
|
557
|
-
block.toolCall.rawOutput =
|
|
469
|
+
block.toolCall.rawOutput = rawOutput;
|
|
558
470
|
}
|
|
559
|
-
// Extract citation sources from tool output (WebSearch, WebFetch, library tools)
|
|
560
471
|
const toolName = toolNameMap.get(update.toolCallId) || "";
|
|
561
|
-
const extractedSources = extractSourcesFromToolOutput(toolName,
|
|
472
|
+
const extractedSources = extractSourcesFromToolOutput(toolName, rawOutput, update.toolCallId, sourceCounter);
|
|
562
473
|
if (extractedSources.length > 0) {
|
|
563
474
|
collectedSources.push(...extractedSources);
|
|
564
475
|
logger.info("Extracted sources from subagent tool output", {
|
|
@@ -566,69 +477,21 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
566
477
|
toolCallId: update.toolCallId,
|
|
567
478
|
sourcesCount: extractedSources.length,
|
|
568
479
|
});
|
|
569
|
-
shouldEmit = true;
|
|
480
|
+
shouldEmit = true;
|
|
570
481
|
}
|
|
571
482
|
}
|
|
572
|
-
// NOTE: Don't set shouldEmit for rawOutput alone - rawOutput is large and will be
|
|
573
|
-
// included in the final emit when the subagent completes
|
|
574
483
|
}
|
|
575
484
|
}
|
|
576
|
-
// Handle sources
|
|
577
|
-
if ("sources"
|
|
578
|
-
|
|
579
|
-
const sources = update
|
|
580
|
-
.sources;
|
|
485
|
+
// Handle sources from ACP
|
|
486
|
+
if (update.sessionUpdate === "sources" && Array.isArray(update.sources)) {
|
|
487
|
+
const sources = update.sources;
|
|
581
488
|
for (const source of sources) {
|
|
582
|
-
|
|
583
|
-
id: source.id,
|
|
584
|
-
url: source.url,
|
|
585
|
-
title: source.title,
|
|
586
|
-
toolCallId: source.toolCallId,
|
|
587
|
-
};
|
|
588
|
-
if (source.snippet)
|
|
589
|
-
citationSource.snippet = source.snippet;
|
|
590
|
-
if (source.favicon)
|
|
591
|
-
citationSource.favicon = source.favicon;
|
|
592
|
-
if (source.sourceName)
|
|
593
|
-
citationSource.sourceName = source.sourceName;
|
|
594
|
-
collectedSources.push(citationSource);
|
|
489
|
+
collectedSources.push(source);
|
|
595
490
|
}
|
|
596
|
-
shouldEmit = true;
|
|
597
|
-
}
|
|
598
|
-
// Periodic status update check
|
|
599
|
-
const now = Date.now();
|
|
600
|
-
const shouldUpdateStatus = !statusUpdateInProgress &&
|
|
601
|
-
now - lastStatusUpdate > STATUS_UPDATE_INTERVAL &&
|
|
602
|
-
currentMessage.content.length > 100; // Only if there's meaningful content
|
|
603
|
-
if (shouldUpdateStatus) {
|
|
604
|
-
statusUpdateInProgress = true;
|
|
605
|
-
lastStatusUpdate = now;
|
|
606
|
-
// Fire async status update (don't await)
|
|
607
|
-
generateStatusMessage(currentMessage.content, currentMessage.toolCalls)
|
|
608
|
-
.then((status) => {
|
|
609
|
-
if (currentMessage._meta) {
|
|
610
|
-
currentMessage._meta.currentActivity = status;
|
|
611
|
-
}
|
|
612
|
-
// Emit update
|
|
613
|
-
emitSubagentMessages(queryHash, [currentMessage]);
|
|
614
|
-
statusUpdateInProgress = false;
|
|
615
|
-
})
|
|
616
|
-
.catch((error) => {
|
|
617
|
-
logger.warn("Status update failed", { error });
|
|
618
|
-
statusUpdateInProgress = false;
|
|
619
|
-
});
|
|
491
|
+
shouldEmit = true;
|
|
620
492
|
}
|
|
621
|
-
// Emit incremental update
|
|
493
|
+
// Emit incremental update
|
|
622
494
|
if (shouldEmit) {
|
|
623
|
-
logger.debug("[SUBAGENT-ACCUMULATION] Emitting incremental update", {
|
|
624
|
-
agentName,
|
|
625
|
-
queryHash,
|
|
626
|
-
contentLength: currentMessage.content.length,
|
|
627
|
-
contentBlocksCount: currentMessage.contentBlocks.length,
|
|
628
|
-
toolCallsCount: currentMessage.toolCalls.length,
|
|
629
|
-
});
|
|
630
|
-
// Strip rawOutput from streamed messages to avoid OOM in browser
|
|
631
|
-
// rawOutput is preserved in currentMessage for final session save
|
|
632
495
|
const streamMessage = {
|
|
633
496
|
...currentMessage,
|
|
634
497
|
toolCalls: currentMessage.toolCalls.map((tc) => {
|
|
@@ -645,8 +508,108 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
645
508
|
};
|
|
646
509
|
emitSubagentMessages(queryHash, [streamMessage]);
|
|
647
510
|
}
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Internal function that runs a subagent in-process and queries it.
|
|
516
|
+
*/
|
|
517
|
+
async function querySubagent(agentName, agentPath, agentWorkingDirectory, query, taskName) {
|
|
518
|
+
// Get the abort signal from context (set by parent agent's cancellation)
|
|
519
|
+
const parentAbortSignal = getAbortSignal();
|
|
520
|
+
// Check if already cancelled before starting
|
|
521
|
+
if (parentAbortSignal?.aborted) {
|
|
522
|
+
throw new Error(`Subagent query cancelled before starting (agent: ${agentName})`);
|
|
523
|
+
}
|
|
524
|
+
// Validate that the agent exists
|
|
525
|
+
try {
|
|
526
|
+
await fs.access(agentPath);
|
|
527
|
+
}
|
|
528
|
+
catch (_error) {
|
|
529
|
+
throw new Error(`Subagent '${agentName}' not found at ${agentPath}. Make sure the agent exists and has an index.ts file.`);
|
|
530
|
+
}
|
|
531
|
+
// Load agent definition dynamically
|
|
532
|
+
const agentModule = await import(agentPath);
|
|
533
|
+
const agentDefinition = agentModule.default || agentModule.agent;
|
|
534
|
+
// Create runner instance
|
|
535
|
+
const runner = makeRunnerFromDefinition(agentDefinition);
|
|
536
|
+
// Generate unique session ID for isolation
|
|
537
|
+
const subagentSessionId = crypto.randomUUID();
|
|
538
|
+
// Prepare OTEL context for distributed tracing
|
|
539
|
+
const activeCtx = context.active();
|
|
540
|
+
const activeSpan = trace.getSpan(activeCtx);
|
|
541
|
+
const otelCarrier = {};
|
|
542
|
+
if (activeSpan) {
|
|
543
|
+
propagation.inject(trace.setSpan(activeCtx, activeSpan), otelCarrier);
|
|
544
|
+
// Set span attributes for observability
|
|
545
|
+
activeSpan.setAttributes({
|
|
546
|
+
"subagent.semantic_name": taskName,
|
|
547
|
+
"subagent.agent_definition": agentName,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// Setup state for streaming connection
|
|
551
|
+
const collectedSources = [];
|
|
552
|
+
const sourceCounter = { value: 0 };
|
|
553
|
+
const currentMessage = {
|
|
554
|
+
id: `subagent-${Date.now()}`,
|
|
555
|
+
content: "",
|
|
556
|
+
contentBlocks: [],
|
|
557
|
+
toolCalls: [],
|
|
558
|
+
_meta: {
|
|
559
|
+
semanticName: taskName,
|
|
560
|
+
agentDefinitionName: agentName,
|
|
561
|
+
statusGenerating: true,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
const toolCallMap = new Map();
|
|
565
|
+
const toolNameMap = new Map();
|
|
566
|
+
const queryHash = hashQuery(query);
|
|
567
|
+
// Create streaming connection that forwards updates to emitSubagentMessages
|
|
568
|
+
const connection = createStreamingConnection(queryHash, currentMessage, toolCallMap, toolNameMap, collectedSources, sourceCounter);
|
|
569
|
+
// Create adapter with session storage (subagents get full session persistence)
|
|
570
|
+
const adapter = new AgentAcpAdapter(runner, connection, agentWorkingDirectory, agentName);
|
|
571
|
+
// Get parent session context for linking
|
|
572
|
+
const parentSessionContext = getSessionContext();
|
|
573
|
+
// Prepare prompt request (ACP format)
|
|
574
|
+
const promptRequest = {
|
|
575
|
+
sessionId: subagentSessionId,
|
|
576
|
+
prompt: [{ type: "text", text: query }],
|
|
577
|
+
_meta: {
|
|
578
|
+
isSubagentSession: true, // For UI filtering
|
|
579
|
+
parentSessionId: parentSessionContext?.sessionId, // For session linking
|
|
580
|
+
[SUBAGENT_MODE_KEY]: true, // 🚨 CRITICAL: Prevents nested subagents via tool filtering
|
|
581
|
+
...(Object.keys(otelCarrier).length > 0
|
|
582
|
+
? { otelTraceContext: otelCarrier }
|
|
583
|
+
: {}),
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
// Emit initial message with semantic name immediately
|
|
587
|
+
emitSubagentMessages(queryHash, [currentMessage]);
|
|
588
|
+
// Fire async initial status generation (don't await)
|
|
589
|
+
generateStatusMessage(query, [])
|
|
590
|
+
.then((status) => {
|
|
591
|
+
if (currentMessage._meta) {
|
|
592
|
+
currentMessage._meta.currentActivity = status;
|
|
593
|
+
currentMessage._meta.statusGenerating = false;
|
|
594
|
+
}
|
|
595
|
+
emitSubagentMessages(queryHash, [currentMessage]);
|
|
596
|
+
})
|
|
597
|
+
.catch((error) => {
|
|
598
|
+
logger.warn("Initial status generation failed", { error });
|
|
599
|
+
if (currentMessage._meta) {
|
|
600
|
+
currentMessage._meta.statusGenerating = false;
|
|
648
601
|
}
|
|
649
|
-
|
|
602
|
+
});
|
|
603
|
+
logger.info("[DEBUG] Starting subagent via adapter", {
|
|
604
|
+
agentName,
|
|
605
|
+
queryHash,
|
|
606
|
+
sessionId: subagentSessionId,
|
|
607
|
+
});
|
|
608
|
+
try {
|
|
609
|
+
// Invoke through adapter (gets full session tracking + hook execution)
|
|
610
|
+
// The adapter will call connection.sessionUpdate() for streaming updates
|
|
611
|
+
const response = await adapter.prompt(promptRequest);
|
|
612
|
+
logger.info("[DEBUG] Subagent adapter.prompt() completed", {
|
|
650
613
|
agentName,
|
|
651
614
|
queryHash,
|
|
652
615
|
sessionId: subagentSessionId,
|
|
@@ -664,7 +627,6 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
664
627
|
emitSubagentMessages(queryHash, [currentMessage], true);
|
|
665
628
|
}
|
|
666
629
|
else {
|
|
667
|
-
// Even if no messages, emit completion sentinel
|
|
668
630
|
logger.info("[DEBUG] Emitting empty completion flag", {
|
|
669
631
|
agentName,
|
|
670
632
|
queryHash,
|
|
@@ -678,8 +640,9 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query,
|
|
|
678
640
|
queryHash,
|
|
679
641
|
sessionId: subagentSessionId,
|
|
680
642
|
});
|
|
643
|
+
// Return accumulated result from streaming connection
|
|
681
644
|
return {
|
|
682
|
-
text:
|
|
645
|
+
text: currentMessage.content,
|
|
683
646
|
sources: collectedSources,
|
|
684
647
|
};
|
|
685
648
|
}
|