@townco/agent 0.1.119 → 0.1.121
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/http.js +2 -28
- 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/e2b-sandbox-manager.d.ts +3 -0
- package/dist/runner/e2b-sandbox-manager.js +48 -19
- package/dist/runner/langchain/index.js +25 -111
- package/dist/runner/langchain/tools/e2b.d.ts +3 -3
- package/dist/runner/langchain/tools/e2b.js +37 -21
- package/dist/runner/langchain/tools/subagent-connections.d.ts +6 -0
- package/dist/runner/langchain/tools/subagent.js +201 -127
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
|
@@ -2,14 +2,81 @@ 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 Anthropic from "@anthropic-ai/sdk";
|
|
5
6
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
6
7
|
import { createLogger } from "@townco/core";
|
|
7
8
|
import { z } from "zod";
|
|
8
|
-
import { SUBAGENT_MODE_KEY, } from "../../../acp-server/adapter.js";
|
|
9
|
+
import { AgentAcpAdapter, SUBAGENT_MODE_KEY, } from "../../../acp-server/adapter.js";
|
|
9
10
|
import { makeRunnerFromDefinition } from "../../index.js";
|
|
10
|
-
import { bindGeneratorToSessionContext, getAbortSignal, } from "../../session-context.js";
|
|
11
|
+
import { bindGeneratorToSessionContext, getAbortSignal, getSessionContext, } from "../../session-context.js";
|
|
11
12
|
import { emitSubagentMessages, hashQuery, } from "./subagent-connections.js";
|
|
12
13
|
const logger = createLogger("subagent-tool", "debug");
|
|
14
|
+
/**
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
async function generateStatusMessage(recentContent, toolCalls) {
|
|
22
|
+
try {
|
|
23
|
+
const activeTool = toolCalls.find((tc) => tc.status === "in_progress");
|
|
24
|
+
// Use Anthropic SDK directly to bypass LangChain callbacks entirely
|
|
25
|
+
const anthropic = new Anthropic({
|
|
26
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
27
|
+
});
|
|
28
|
+
const prompt = `Summarize the current activity in 5-7 words for a progress indicator:
|
|
29
|
+
|
|
30
|
+
Recent output: ${recentContent.slice(-500)}
|
|
31
|
+
${activeTool ? `Active tool: ${activeTool.prettyName || activeTool.title}` : ""}
|
|
32
|
+
|
|
33
|
+
Requirements:
|
|
34
|
+
- Use present continuous tense (e.g., "Searching for...", "Analyzing...")
|
|
35
|
+
- Be specific but concise
|
|
36
|
+
- Focus on user-visible progress
|
|
37
|
+
- Return ONLY the status, no explanation
|
|
38
|
+
|
|
39
|
+
Status:`;
|
|
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);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.warn("Failed to generate status message", { error });
|
|
57
|
+
return extractHeuristicStatus(recentContent, toolCalls);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Heuristic status extraction (fallback when LLM fails)
|
|
62
|
+
*/
|
|
63
|
+
function extractHeuristicStatus(content, toolCalls) {
|
|
64
|
+
// Priority 1: Active tool
|
|
65
|
+
const activeTool = toolCalls.find((tc) => tc.status === "in_progress");
|
|
66
|
+
if (activeTool) {
|
|
67
|
+
return `${activeTool.prettyName || activeTool.title}...`;
|
|
68
|
+
}
|
|
69
|
+
// Priority 2: First complete sentence from recent content
|
|
70
|
+
if (content.length > 50) {
|
|
71
|
+
const lastAdded = content.slice(-200);
|
|
72
|
+
const firstSentence = lastAdded.match(/[^.!?]+[.!?]/)?.[0];
|
|
73
|
+
if (firstSentence && firstSentence.length > 10) {
|
|
74
|
+
return firstSentence.trim().slice(0, 80);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Priority 3: Generic fallback
|
|
78
|
+
return "Processing...";
|
|
79
|
+
}
|
|
13
80
|
/**
|
|
14
81
|
* Helper to derive favicon URL from a domain
|
|
15
82
|
*/
|
|
@@ -311,98 +378,36 @@ assistant: "I'm going to use the Task tool to launch the greeting-responder agen
|
|
|
311
378
|
.enum(agentNames)
|
|
312
379
|
.describe("The name of the subagent to use"),
|
|
313
380
|
query: z.string().describe("The query or task to send to the subagent"),
|
|
381
|
+
taskName: z
|
|
382
|
+
.string()
|
|
383
|
+
.describe("A concise 3-5 word name describing this specific task (e.g., 'Searching for React patterns', 'Analyzing API responses'). IMPORTANT: Be specific and action-oriented!"),
|
|
314
384
|
}),
|
|
315
385
|
// Expose subagent configs for metadata extraction by the adapter
|
|
316
386
|
subagentConfigs,
|
|
317
387
|
fn: async (input) => {
|
|
318
|
-
const { agentName, query } = input;
|
|
388
|
+
const { agentName, query, taskName } = input;
|
|
319
389
|
const agent = agentMap.get(agentName);
|
|
320
390
|
if (!agent) {
|
|
321
391
|
throw new Error(`Unknown agent: ${agentName}`);
|
|
322
392
|
}
|
|
323
|
-
return await querySubagent(agentName, agent.agentPath, agent.agentDir, query);
|
|
393
|
+
return await querySubagent(agentName, agent.agentPath, agent.agentDir, query, taskName);
|
|
324
394
|
},
|
|
325
395
|
};
|
|
326
396
|
}
|
|
327
397
|
/**
|
|
328
|
-
*
|
|
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.
|
|
329
401
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (parentAbortSignal?.aborted) {
|
|
335
|
-
throw new Error(`Subagent query cancelled before starting (agent: ${agentName})`);
|
|
336
|
-
}
|
|
337
|
-
// Validate that the agent exists
|
|
338
|
-
try {
|
|
339
|
-
await fs.access(agentPath);
|
|
340
|
-
}
|
|
341
|
-
catch (_error) {
|
|
342
|
-
throw new Error(`Subagent '${agentName}' not found at ${agentPath}. Make sure the agent exists and has an index.ts file.`);
|
|
343
|
-
}
|
|
344
|
-
// Load agent definition dynamically
|
|
345
|
-
const agentModule = await import(agentPath);
|
|
346
|
-
const agentDefinition = agentModule.default || agentModule.agent;
|
|
347
|
-
// Create runner instance
|
|
348
|
-
const runner = makeRunnerFromDefinition(agentDefinition);
|
|
349
|
-
// Generate unique session ID for isolation
|
|
350
|
-
const subagentSessionId = crypto.randomUUID();
|
|
351
|
-
// Setup session paths
|
|
352
|
-
const sessionDir = path.join(agentWorkingDirectory, ".sessions", subagentSessionId);
|
|
353
|
-
const artifactsDir = path.join(sessionDir, "artifacts");
|
|
354
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
355
|
-
// Prepare OTEL context for distributed tracing
|
|
356
|
-
const activeCtx = context.active();
|
|
357
|
-
const activeSpan = trace.getSpan(activeCtx);
|
|
358
|
-
const otelCarrier = {};
|
|
359
|
-
if (activeSpan) {
|
|
360
|
-
propagation.inject(trace.setSpan(activeCtx, activeSpan), otelCarrier);
|
|
361
|
-
}
|
|
362
|
-
// Create invoke request
|
|
363
|
-
const invokeRequest = {
|
|
364
|
-
sessionId: subagentSessionId,
|
|
365
|
-
messageId: crypto.randomUUID(),
|
|
366
|
-
prompt: [{ type: "text", text: query }],
|
|
367
|
-
agentDir: agentWorkingDirectory,
|
|
368
|
-
contextMessages: [],
|
|
369
|
-
...(parentAbortSignal ? { abortSignal: parentAbortSignal } : {}),
|
|
370
|
-
sessionMeta: {
|
|
371
|
-
[SUBAGENT_MODE_KEY]: true,
|
|
372
|
-
...(Object.keys(otelCarrier).length > 0
|
|
373
|
-
? { otelTraceContext: otelCarrier }
|
|
374
|
-
: {}),
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
// Bind session context and invoke
|
|
378
|
-
let generator = runner.invoke(invokeRequest);
|
|
379
|
-
generator = bindGeneratorToSessionContext({ sessionId: subagentSessionId, sessionDir, artifactsDir }, generator);
|
|
380
|
-
// Consume stream, accumulate results, and emit incremental updates
|
|
381
|
-
let responseText = "";
|
|
382
|
-
const collectedSources = [];
|
|
383
|
-
const sourceCounter = { value: 0 }; // Mutable counter for source ID generation
|
|
384
|
-
const currentMessage = {
|
|
385
|
-
id: `subagent-${Date.now()}`,
|
|
386
|
-
content: "",
|
|
387
|
-
contentBlocks: [],
|
|
388
|
-
toolCalls: [],
|
|
389
|
-
};
|
|
390
|
-
const toolCallMap = new Map();
|
|
391
|
-
const toolNameMap = new Map(); // Map toolCallId -> toolName
|
|
392
|
-
const queryHash = hashQuery(query);
|
|
393
|
-
logger.info("[DEBUG] Starting subagent generator loop", {
|
|
394
|
-
agentName,
|
|
395
|
-
queryHash,
|
|
396
|
-
sessionId: subagentSessionId,
|
|
397
|
-
});
|
|
398
|
-
try {
|
|
399
|
-
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;
|
|
400
406
|
let shouldEmit = false;
|
|
401
407
|
// Handle agent_message_chunk
|
|
402
408
|
if (update.sessionUpdate === "agent_message_chunk") {
|
|
403
409
|
const content = update.content;
|
|
404
410
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
405
|
-
responseText += content.text;
|
|
406
411
|
currentMessage.content += content.text;
|
|
407
412
|
const lastBlock = currentMessage.contentBlocks[currentMessage.contentBlocks.length - 1];
|
|
408
413
|
if (lastBlock && lastBlock.type === "text") {
|
|
@@ -414,15 +419,13 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
414
419
|
text: content.text,
|
|
415
420
|
});
|
|
416
421
|
}
|
|
417
|
-
shouldEmit = true;
|
|
422
|
+
shouldEmit = true;
|
|
418
423
|
}
|
|
419
424
|
}
|
|
420
425
|
// Handle tool_call
|
|
421
426
|
if (update.sessionUpdate === "tool_call" && update.toolCallId) {
|
|
422
427
|
const meta = update._meta;
|
|
423
|
-
|
|
424
|
-
const rawInput = update
|
|
425
|
-
.rawInput;
|
|
428
|
+
const rawInput = update.rawInput;
|
|
426
429
|
const toolCall = {
|
|
427
430
|
id: update.toolCallId,
|
|
428
431
|
title: update.title || "Tool call",
|
|
@@ -433,12 +436,11 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
433
436
|
};
|
|
434
437
|
currentMessage.toolCalls.push(toolCall);
|
|
435
438
|
toolCallMap.set(update.toolCallId, currentMessage.toolCalls.length - 1);
|
|
436
|
-
// Store tool name for source extraction when output arrives
|
|
437
439
|
if (update.title) {
|
|
438
440
|
toolNameMap.set(update.toolCallId, update.title);
|
|
439
441
|
}
|
|
440
442
|
currentMessage.contentBlocks.push({ type: "tool_call", toolCall });
|
|
441
|
-
shouldEmit = true;
|
|
443
|
+
shouldEmit = true;
|
|
442
444
|
}
|
|
443
445
|
// Handle tool_call_update
|
|
444
446
|
if (update.sessionUpdate === "tool_call_update" && update.toolCallId) {
|
|
@@ -452,25 +454,22 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
452
454
|
if (block && update.status) {
|
|
453
455
|
block.toolCall.status = update.status;
|
|
454
456
|
}
|
|
455
|
-
shouldEmit = true;
|
|
457
|
+
shouldEmit = true;
|
|
456
458
|
}
|
|
457
459
|
}
|
|
458
|
-
// Handle tool_output
|
|
459
|
-
// (rawOutput will be included in final emit when subagent completes)
|
|
460
|
+
// Handle tool_output
|
|
460
461
|
if (update.sessionUpdate === "tool_output" && update.toolCallId) {
|
|
461
|
-
const outputUpdate = update;
|
|
462
462
|
const idx = toolCallMap.get(update.toolCallId);
|
|
463
463
|
if (idx !== undefined && currentMessage.toolCalls[idx]) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
464
|
+
const rawOutput = update.rawOutput;
|
|
465
|
+
if (rawOutput) {
|
|
466
|
+
currentMessage.toolCalls[idx].rawOutput = rawOutput;
|
|
467
467
|
const block = currentMessage.contentBlocks.find((b) => b.type === "tool_call" && b.toolCall.id === update.toolCallId);
|
|
468
468
|
if (block) {
|
|
469
|
-
block.toolCall.rawOutput =
|
|
469
|
+
block.toolCall.rawOutput = rawOutput;
|
|
470
470
|
}
|
|
471
|
-
// Extract citation sources from tool output (WebSearch, WebFetch, library tools)
|
|
472
471
|
const toolName = toolNameMap.get(update.toolCallId) || "";
|
|
473
|
-
const extractedSources = extractSourcesFromToolOutput(toolName,
|
|
472
|
+
const extractedSources = extractSourcesFromToolOutput(toolName, rawOutput, update.toolCallId, sourceCounter);
|
|
474
473
|
if (extractedSources.length > 0) {
|
|
475
474
|
collectedSources.push(...extractedSources);
|
|
476
475
|
logger.info("Extracted sources from subagent tool output", {
|
|
@@ -478,46 +477,21 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
478
477
|
toolCallId: update.toolCallId,
|
|
479
478
|
sourcesCount: extractedSources.length,
|
|
480
479
|
});
|
|
481
|
-
shouldEmit = true;
|
|
480
|
+
shouldEmit = true;
|
|
482
481
|
}
|
|
483
482
|
}
|
|
484
|
-
// NOTE: Don't set shouldEmit for rawOutput alone - rawOutput is large and will be
|
|
485
|
-
// included in the final emit when the subagent completes
|
|
486
483
|
}
|
|
487
484
|
}
|
|
488
|
-
// Handle sources
|
|
489
|
-
if ("sources"
|
|
490
|
-
|
|
491
|
-
const sources = update
|
|
492
|
-
.sources;
|
|
485
|
+
// Handle sources from ACP
|
|
486
|
+
if (update.sessionUpdate === "sources" && Array.isArray(update.sources)) {
|
|
487
|
+
const sources = update.sources;
|
|
493
488
|
for (const source of sources) {
|
|
494
|
-
|
|
495
|
-
id: source.id,
|
|
496
|
-
url: source.url,
|
|
497
|
-
title: source.title,
|
|
498
|
-
toolCallId: source.toolCallId,
|
|
499
|
-
};
|
|
500
|
-
if (source.snippet)
|
|
501
|
-
citationSource.snippet = source.snippet;
|
|
502
|
-
if (source.favicon)
|
|
503
|
-
citationSource.favicon = source.favicon;
|
|
504
|
-
if (source.sourceName)
|
|
505
|
-
citationSource.sourceName = source.sourceName;
|
|
506
|
-
collectedSources.push(citationSource);
|
|
489
|
+
collectedSources.push(source);
|
|
507
490
|
}
|
|
508
|
-
shouldEmit = true;
|
|
491
|
+
shouldEmit = true;
|
|
509
492
|
}
|
|
510
|
-
// Emit incremental update
|
|
493
|
+
// Emit incremental update
|
|
511
494
|
if (shouldEmit) {
|
|
512
|
-
logger.debug("[SUBAGENT-ACCUMULATION] Emitting incremental update", {
|
|
513
|
-
agentName,
|
|
514
|
-
queryHash,
|
|
515
|
-
contentLength: currentMessage.content.length,
|
|
516
|
-
contentBlocksCount: currentMessage.contentBlocks.length,
|
|
517
|
-
toolCallsCount: currentMessage.toolCalls.length,
|
|
518
|
-
});
|
|
519
|
-
// Strip rawOutput from streamed messages to avoid OOM in browser
|
|
520
|
-
// rawOutput is preserved in currentMessage for final session save
|
|
521
495
|
const streamMessage = {
|
|
522
496
|
...currentMessage,
|
|
523
497
|
toolCalls: currentMessage.toolCalls.map((tc) => {
|
|
@@ -534,8 +508,108 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
534
508
|
};
|
|
535
509
|
emitSubagentMessages(queryHash, [streamMessage]);
|
|
536
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;
|
|
537
594
|
}
|
|
538
|
-
|
|
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;
|
|
601
|
+
}
|
|
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", {
|
|
539
613
|
agentName,
|
|
540
614
|
queryHash,
|
|
541
615
|
sessionId: subagentSessionId,
|
|
@@ -553,7 +627,6 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
553
627
|
emitSubagentMessages(queryHash, [currentMessage], true);
|
|
554
628
|
}
|
|
555
629
|
else {
|
|
556
|
-
// Even if no messages, emit completion sentinel
|
|
557
630
|
logger.info("[DEBUG] Emitting empty completion flag", {
|
|
558
631
|
agentName,
|
|
559
632
|
queryHash,
|
|
@@ -567,8 +640,9 @@ async function querySubagent(agentName, agentPath, agentWorkingDirectory, query)
|
|
|
567
640
|
queryHash,
|
|
568
641
|
sessionId: subagentSessionId,
|
|
569
642
|
});
|
|
643
|
+
// Return accumulated result from streaming connection
|
|
570
644
|
return {
|
|
571
|
-
text:
|
|
645
|
+
text: currentMessage.content,
|
|
572
646
|
sources: collectedSources,
|
|
573
647
|
};
|
|
574
648
|
}
|