@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
|
@@ -676,12 +676,23 @@ export class AgentAcpAdapter {
|
|
|
676
676
|
sources: session.sources,
|
|
677
677
|
nextId: session.sourceCounter + 1,
|
|
678
678
|
};
|
|
679
|
-
|
|
679
|
+
// Pass session metadata for subagent sessions
|
|
680
|
+
const metadata = {
|
|
681
|
+
...(session.parentSessionId && {
|
|
682
|
+
parentSessionId: session.parentSessionId,
|
|
683
|
+
}),
|
|
684
|
+
...(session.isSubagentSession && {
|
|
685
|
+
isSubagentSession: session.isSubagentSession,
|
|
686
|
+
}),
|
|
687
|
+
};
|
|
688
|
+
await this.storage.saveSession(sessionId, session.messages, session.context, citations, Object.keys(metadata).length > 0 ? metadata : undefined);
|
|
680
689
|
logger.debug("Saved session to disk", {
|
|
681
690
|
sessionId,
|
|
682
691
|
messageCount: session.messages.length,
|
|
683
692
|
contextCount: session.context.length,
|
|
684
693
|
citationsCount: session.sources.length,
|
|
694
|
+
isSubagentSession: session.isSubagentSession,
|
|
695
|
+
parentSessionId: session.parentSessionId,
|
|
685
696
|
});
|
|
686
697
|
}
|
|
687
698
|
catch (error) {
|
|
@@ -1046,6 +1057,8 @@ export class AgentAcpAdapter {
|
|
|
1046
1057
|
// If session not found (e.g., after server restart), create a new one
|
|
1047
1058
|
if (!session) {
|
|
1048
1059
|
logger.info(`Session ${params.sessionId} not found, creating new session`);
|
|
1060
|
+
const parentSessionId = params._meta?.parentSessionId;
|
|
1061
|
+
const isSubagentSession = params._meta?.isSubagentSession === true;
|
|
1049
1062
|
session = {
|
|
1050
1063
|
pendingPrompt: null,
|
|
1051
1064
|
messages: [],
|
|
@@ -1054,6 +1067,9 @@ export class AgentAcpAdapter {
|
|
|
1054
1067
|
isCancelled: false,
|
|
1055
1068
|
sourceCounter: 0,
|
|
1056
1069
|
sources: [],
|
|
1070
|
+
// Extract subagent metadata from _meta (only add if defined)
|
|
1071
|
+
...(parentSessionId && { parentSessionId }),
|
|
1072
|
+
...(isSubagentSession && { isSubagentSession }),
|
|
1057
1073
|
};
|
|
1058
1074
|
this.sessions.set(params.sessionId, session);
|
|
1059
1075
|
}
|
|
@@ -1299,6 +1315,8 @@ export class AgentAcpAdapter {
|
|
|
1299
1315
|
...(this.agentDir ? { agentDir: this.agentDir } : {}),
|
|
1300
1316
|
// Pass resolved context messages to agent
|
|
1301
1317
|
contextMessages,
|
|
1318
|
+
// Pass context entries for hook execution (compaction tracking)
|
|
1319
|
+
contextEntries: session.context,
|
|
1302
1320
|
// Pass abort signal for cancellation
|
|
1303
1321
|
abortSignal: session.pendingPrompt?.signal,
|
|
1304
1322
|
// Pass emitUpdate callback for file change events
|
package/dist/acp-server/http.js
CHANGED
|
@@ -259,19 +259,7 @@ export function createAcpHttpApp(agent, agentDir, agentName) {
|
|
|
259
259
|
const stream = sseStreams.get(msgSessionId);
|
|
260
260
|
if (stream) {
|
|
261
261
|
try {
|
|
262
|
-
//
|
|
263
|
-
const isSourcesMsg = rawMsg &&
|
|
264
|
-
typeof rawMsg === "object" &&
|
|
265
|
-
"params" in rawMsg &&
|
|
266
|
-
rawMsg.params?.update?.sessionUpdate === "sources";
|
|
267
|
-
if (isSourcesMsg) {
|
|
268
|
-
const sourcesCount = rawMsg.params?.update?.sources?.length;
|
|
269
|
-
logger.info("🔶 SENDING LARGE SOURCES VIA DIRECT SSE", {
|
|
270
|
-
sessionId: msgSessionId,
|
|
271
|
-
sourcesCount,
|
|
272
|
-
compressedSize,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
262
|
+
// Send large messages directly via SSE
|
|
275
263
|
await stream.writeSSE({
|
|
276
264
|
event: "message",
|
|
277
265
|
data: JSON.stringify(rawMsg),
|
|
@@ -811,21 +799,7 @@ export function createAcpHttpApp(agent, agentDir, agentName) {
|
|
|
811
799
|
return;
|
|
812
800
|
}
|
|
813
801
|
}
|
|
814
|
-
|
|
815
|
-
const isSourcesMsg = json &&
|
|
816
|
-
typeof json === "object" &&
|
|
817
|
-
"params" in json &&
|
|
818
|
-
json.params?.update?.sessionUpdate === "sources";
|
|
819
|
-
if (isSourcesMsg) {
|
|
820
|
-
logger.info("🔶 SENDING SOURCES VIA SSE", {
|
|
821
|
-
sessionId,
|
|
822
|
-
channel,
|
|
823
|
-
sourcesCount: json.params?.update?.sources?.length,
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
logger.trace("Sending SSE message", { sessionId, channel });
|
|
828
|
-
}
|
|
802
|
+
logger.trace("Sending SSE message", { sessionId, channel });
|
|
829
803
|
await stream.writeSSE({
|
|
830
804
|
event: "message",
|
|
831
805
|
data: JSON.stringify(json),
|
|
@@ -142,6 +142,10 @@ export interface SessionMetadata {
|
|
|
142
142
|
agentName: string;
|
|
143
143
|
/** E2B sandbox ID for persistent sandbox reconnection */
|
|
144
144
|
sandboxId?: string | undefined;
|
|
145
|
+
/** Parent session ID for subagent sessions (enables session linking) */
|
|
146
|
+
parentSessionId?: string | undefined;
|
|
147
|
+
/** Flag indicating this is a subagent session (for UI filtering) */
|
|
148
|
+
isSubagentSession?: boolean | undefined;
|
|
145
149
|
}
|
|
146
150
|
/**
|
|
147
151
|
* Complete session data stored in JSON files
|
|
@@ -207,7 +211,7 @@ export declare class SessionStorage {
|
|
|
207
211
|
* Save a session to disk
|
|
208
212
|
* Uses atomic write (write to temp file, then rename)
|
|
209
213
|
*/
|
|
210
|
-
saveSession(sessionId: string, messages: SessionMessage[], context: ContextEntry[], citations?: CitationStorage): Promise<void>;
|
|
214
|
+
saveSession(sessionId: string, messages: SessionMessage[], context: ContextEntry[], citations?: CitationStorage, extraMetadata?: Partial<SessionMetadata>): Promise<void>;
|
|
211
215
|
/**
|
|
212
216
|
* Load a session from disk
|
|
213
217
|
*/
|
|
@@ -242,6 +246,8 @@ export declare class SessionStorage {
|
|
|
242
246
|
updatedAt: string;
|
|
243
247
|
messageCount: number;
|
|
244
248
|
firstUserMessage?: string;
|
|
249
|
+
parentSessionId?: string;
|
|
250
|
+
isSubagentSession?: boolean;
|
|
245
251
|
}>>;
|
|
246
252
|
/**
|
|
247
253
|
* Get the directory for storing large content files for a session (artifacts folder)
|
|
@@ -135,6 +135,8 @@ const sessionMetadataSchema = z.object({
|
|
|
135
135
|
updatedAt: z.string(),
|
|
136
136
|
agentName: z.string(),
|
|
137
137
|
sandboxId: z.string().optional(),
|
|
138
|
+
parentSessionId: z.string().optional(),
|
|
139
|
+
isSubagentSession: z.boolean().optional(),
|
|
138
140
|
});
|
|
139
141
|
// Citation schemas - matches SourceSchema from packages/ui/src/core/schemas/source.ts
|
|
140
142
|
const persistedCitationSourceSchema = z.object({
|
|
@@ -200,7 +202,7 @@ export class SessionStorage {
|
|
|
200
202
|
* Save a session to disk
|
|
201
203
|
* Uses atomic write (write to temp file, then rename)
|
|
202
204
|
*/
|
|
203
|
-
async saveSession(sessionId, messages, context, citations) {
|
|
205
|
+
async saveSession(sessionId, messages, context, citations, extraMetadata) {
|
|
204
206
|
// Debug: log subagent data being saved
|
|
205
207
|
const messagesWithSubagents = messages.filter((msg) => msg.content.some((block) => block.type === "tool_call" &&
|
|
206
208
|
"subagentMessages" in block &&
|
|
@@ -239,6 +241,7 @@ export class SessionStorage {
|
|
|
239
241
|
createdAt: existingSession?.metadata.createdAt || now,
|
|
240
242
|
updatedAt: now,
|
|
241
243
|
agentName: this.agentName,
|
|
244
|
+
...extraMetadata,
|
|
242
245
|
},
|
|
243
246
|
...(citations && { citations }),
|
|
244
247
|
};
|
|
@@ -386,6 +389,13 @@ export class SessionStorage {
|
|
|
386
389
|
if (firstUserText && "text" in firstUserText) {
|
|
387
390
|
entry.firstUserMessage = firstUserText.text.slice(0, 100);
|
|
388
391
|
}
|
|
392
|
+
// Include sub-agent metadata if present
|
|
393
|
+
if (session.metadata.parentSessionId) {
|
|
394
|
+
entry.parentSessionId = session.metadata.parentSessionId;
|
|
395
|
+
}
|
|
396
|
+
if (session.metadata.isSubagentSession) {
|
|
397
|
+
entry.isSubagentSession = session.metadata.isSubagentSession;
|
|
398
|
+
}
|
|
389
399
|
sessions.push(entry);
|
|
390
400
|
}
|
|
391
401
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PromptRequest, PromptResponse, SessionNotification } from "@agentclientprotocol/sdk";
|
|
2
2
|
import type { Span } from "@opentelemetry/api";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import type { ContentBlock } from "../acp-server/session-storage.js";
|
|
4
|
+
import type { ContentBlock, ContextEntry } from "../acp-server/session-storage.js";
|
|
5
5
|
export declare const zAgentRunnerParams: z.ZodObject<{
|
|
6
6
|
displayName: z.ZodOptional<z.ZodString>;
|
|
7
7
|
version: z.ZodOptional<z.ZodString>;
|
|
@@ -99,6 +99,7 @@ export type InvokeRequest = Omit<PromptRequest, "_meta"> & {
|
|
|
99
99
|
agentDir?: string;
|
|
100
100
|
sessionMeta?: Record<string, unknown>;
|
|
101
101
|
contextMessages?: SessionMessage[];
|
|
102
|
+
contextEntries?: ContextEntry[];
|
|
102
103
|
configOverrides?: ConfigOverrides;
|
|
103
104
|
/** Abort signal for cancellation - tools can listen for this to stop early */
|
|
104
105
|
abortSignal?: AbortSignal;
|
|
@@ -3,6 +3,9 @@ import type { Sandbox } from "@e2b/code-interpreter";
|
|
|
3
3
|
* Get or create an E2B sandbox for the current session.
|
|
4
4
|
* Sandboxes are session-scoped and reused across tool calls.
|
|
5
5
|
* Attempts to reconnect to persisted sandboxes when resuming sessions.
|
|
6
|
+
*
|
|
7
|
+
* Uses promise caching to prevent race conditions when multiple tools
|
|
8
|
+
* are called in parallel - all concurrent calls will share the same sandbox.
|
|
6
9
|
*/
|
|
7
10
|
export declare function getSessionSandbox(apiKey: string): Promise<Sandbox>;
|
|
8
11
|
/**
|
|
@@ -8,6 +8,8 @@ const sessionSandboxes = new Map();
|
|
|
8
8
|
const sandboxActivity = new Map();
|
|
9
9
|
// Map sessionId -> cleanup timeout handle
|
|
10
10
|
const cleanupTimeouts = new Map();
|
|
11
|
+
// Map sessionId -> Promise<Sandbox> for in-flight creations (prevents race condition)
|
|
12
|
+
const sandboxCreationPromises = new Map();
|
|
11
13
|
// Sandbox timeout in milliseconds (default: 15 minutes)
|
|
12
14
|
const SANDBOX_TIMEOUT_MS = 15 * 60 * 1000;
|
|
13
15
|
/**
|
|
@@ -58,24 +60,10 @@ function getSessionStorage() {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* Attempts to reconnect to persisted sandboxes when resuming sessions.
|
|
63
|
+
* Internal function to create or reconnect to a sandbox for a session.
|
|
64
|
+
* This should only be called from getSessionSandbox() with proper promise tracking.
|
|
64
65
|
*/
|
|
65
|
-
|
|
66
|
-
if (!hasSessionContext()) {
|
|
67
|
-
throw new Error("E2B tools require session context");
|
|
68
|
-
}
|
|
69
|
-
const { sessionId } = getSessionContext();
|
|
70
|
-
// Check for existing in-memory sandbox
|
|
71
|
-
let sandbox = sessionSandboxes.get(sessionId);
|
|
72
|
-
if (sandbox) {
|
|
73
|
-
// Update activity timestamp and reschedule cleanup
|
|
74
|
-
sandboxActivity.set(sessionId, Date.now());
|
|
75
|
-
rescheduleCleanup(sessionId);
|
|
76
|
-
logger.debug("Reusing existing sandbox", { sessionId });
|
|
77
|
-
return sandbox;
|
|
78
|
-
}
|
|
66
|
+
async function createSandboxForSession(sessionId, apiKey) {
|
|
79
67
|
// Try to reconnect to persisted sandbox
|
|
80
68
|
const storage = getSessionStorage();
|
|
81
69
|
const persistedSandboxId = storage?.getSandboxId(sessionId);
|
|
@@ -86,7 +74,9 @@ export async function getSessionSandbox(apiKey) {
|
|
|
86
74
|
});
|
|
87
75
|
try {
|
|
88
76
|
const { Sandbox: SandboxClass } = await import("@e2b/code-interpreter");
|
|
89
|
-
sandbox = await SandboxClass.connect(persistedSandboxId, {
|
|
77
|
+
const sandbox = await SandboxClass.connect(persistedSandboxId, {
|
|
78
|
+
apiKey,
|
|
79
|
+
});
|
|
90
80
|
logger.info("Successfully reconnected to sandbox", {
|
|
91
81
|
sessionId,
|
|
92
82
|
sandboxId: persistedSandboxId,
|
|
@@ -131,7 +121,7 @@ export async function getSessionSandbox(apiKey) {
|
|
|
131
121
|
config.template = templateId;
|
|
132
122
|
logger.info("Using custom E2B template", { templateId });
|
|
133
123
|
}
|
|
134
|
-
sandbox = await SandboxClass.create(config);
|
|
124
|
+
const sandbox = await SandboxClass.create(config);
|
|
135
125
|
logger.info("Created new sandbox", {
|
|
136
126
|
sessionId,
|
|
137
127
|
sandboxId: sandbox.sandboxId,
|
|
@@ -167,6 +157,45 @@ logging.basicConfig(
|
|
|
167
157
|
scheduleCleanup(sessionId);
|
|
168
158
|
return sandbox;
|
|
169
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Get or create an E2B sandbox for the current session.
|
|
162
|
+
* Sandboxes are session-scoped and reused across tool calls.
|
|
163
|
+
* Attempts to reconnect to persisted sandboxes when resuming sessions.
|
|
164
|
+
*
|
|
165
|
+
* Uses promise caching to prevent race conditions when multiple tools
|
|
166
|
+
* are called in parallel - all concurrent calls will share the same sandbox.
|
|
167
|
+
*/
|
|
168
|
+
export async function getSessionSandbox(apiKey) {
|
|
169
|
+
if (!hasSessionContext()) {
|
|
170
|
+
throw new Error("E2B tools require session context");
|
|
171
|
+
}
|
|
172
|
+
const { sessionId } = getSessionContext();
|
|
173
|
+
// Check for existing in-memory sandbox
|
|
174
|
+
const existingSandbox = sessionSandboxes.get(sessionId);
|
|
175
|
+
if (existingSandbox) {
|
|
176
|
+
// Update activity timestamp and reschedule cleanup
|
|
177
|
+
sandboxActivity.set(sessionId, Date.now());
|
|
178
|
+
rescheduleCleanup(sessionId);
|
|
179
|
+
logger.debug("Reusing existing sandbox", { sessionId });
|
|
180
|
+
return existingSandbox;
|
|
181
|
+
}
|
|
182
|
+
// Check for in-flight creation (prevents race condition when tools run in parallel)
|
|
183
|
+
const existingPromise = sandboxCreationPromises.get(sessionId);
|
|
184
|
+
if (existingPromise) {
|
|
185
|
+
logger.debug("Waiting for in-flight sandbox creation", { sessionId });
|
|
186
|
+
return existingPromise;
|
|
187
|
+
}
|
|
188
|
+
// Create new sandbox with promise tracking
|
|
189
|
+
const creationPromise = createSandboxForSession(sessionId, apiKey);
|
|
190
|
+
sandboxCreationPromises.set(sessionId, creationPromise);
|
|
191
|
+
try {
|
|
192
|
+
const sandbox = await creationPromise;
|
|
193
|
+
return sandbox;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
sandboxCreationPromises.delete(sessionId);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
170
199
|
/**
|
|
171
200
|
* Explicitly destroy a session's sandbox (called on session end).
|
|
172
201
|
* Also clears the persisted sandboxId from storage.
|
|
@@ -412,122 +412,36 @@ export class LangchainAgent {
|
|
|
412
412
|
baseContextTokens,
|
|
413
413
|
modelContextWindow,
|
|
414
414
|
});
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const noSession = req.sessionMeta?.[SUBAGENT_MODE_KEY] === true; // Subagents don't have session storage
|
|
419
|
-
// Track cumulative tool output tokens in this turn for proper context calculation
|
|
420
|
-
let cumulativeToolOutputTokens = 0;
|
|
415
|
+
// Hook execution removed from tool wrapper - hooks are now executed only at the adapter layer
|
|
416
|
+
// The adapter has proper MidTurnRestartError handling that can restart the turn
|
|
417
|
+
// Executing hooks here in the runner was causing restart signals to be caught as tool failures
|
|
421
418
|
// Counter for subagent calls - used to create unique source ID ranges
|
|
422
419
|
// Each subagent call gets a unique offset (1000, 2000, 3000, etc.)
|
|
423
420
|
// to ensure sources never conflict with parent's sources (typically < 100)
|
|
424
421
|
let subagentCallCounter = 0;
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
cumulativeToolOutputTokens += outputTokens;
|
|
441
|
-
return result;
|
|
442
|
-
}
|
|
443
|
-
_logger.info("Tool wrapper: compacting large tool result", {
|
|
444
|
-
toolName: originalTool.name,
|
|
445
|
-
originalTokens: outputTokens,
|
|
446
|
-
cumulativeToolOutputTokens,
|
|
447
|
-
});
|
|
448
|
-
// Calculate current context including all tool outputs so far in this turn
|
|
449
|
-
// Uses accurate baseContextTokens calculated earlier (system prompt, tool overhead, MCP overhead, message history)
|
|
450
|
-
// This ensures we account for multiple large tool calls in the same turn
|
|
451
|
-
const currentTokens = baseContextTokens + cumulativeToolOutputTokens;
|
|
452
|
-
// Build proper hook context with all required fields
|
|
453
|
-
const hookContext = {
|
|
454
|
-
session: {
|
|
455
|
-
messages: req.contextMessages || [],
|
|
456
|
-
context: [],
|
|
457
|
-
requestParams: {
|
|
458
|
-
hookSettings: hooks.find((h) => h.type === "tool_response")
|
|
459
|
-
?.setting,
|
|
460
|
-
},
|
|
461
|
-
},
|
|
462
|
-
currentTokens,
|
|
463
|
-
maxTokens: modelContextWindow,
|
|
464
|
-
percentage: (currentTokens / modelContextWindow) * 100,
|
|
465
|
-
model: this.definition.model,
|
|
466
|
-
agent: this.definition,
|
|
467
|
-
toolResponse: {
|
|
468
|
-
toolCallId: "pending",
|
|
469
|
-
toolName: originalTool.name,
|
|
470
|
-
toolInput: input,
|
|
471
|
-
rawOutput,
|
|
472
|
-
outputTokens,
|
|
473
|
-
},
|
|
474
|
-
};
|
|
475
|
-
// Call the tool response compactor directly
|
|
476
|
-
const hookResult = await toolResponseCompactor(hookContext);
|
|
477
|
-
// Extract modified output from metadata
|
|
478
|
-
if (hookResult?.metadata?.modifiedOutput) {
|
|
479
|
-
const modifiedOutput = hookResult.metadata
|
|
480
|
-
.modifiedOutput;
|
|
481
|
-
const compactedTokens = countToolResultTokens(modifiedOutput);
|
|
482
|
-
// Update cumulative total with the compacted size (not original!)
|
|
483
|
-
cumulativeToolOutputTokens += compactedTokens;
|
|
484
|
-
_logger.info("Tool wrapper: compaction complete", {
|
|
485
|
-
toolName: originalTool.name,
|
|
486
|
-
originalTokens: outputTokens,
|
|
487
|
-
compactedTokens,
|
|
488
|
-
reduction: `${((1 - compactedTokens / outputTokens) * 100).toFixed(1)}%`,
|
|
489
|
-
totalCumulativeTokens: cumulativeToolOutputTokens,
|
|
490
|
-
});
|
|
491
|
-
// Include compaction metadata in the output for the adapter to extract
|
|
492
|
-
// Also include original content so adapter can store it
|
|
493
|
-
const originalContentStr = typeof rawOutput === "object" &&
|
|
494
|
-
rawOutput !== null &&
|
|
495
|
-
"content" in rawOutput
|
|
496
|
-
? String(rawOutput.content)
|
|
497
|
-
: JSON.stringify(rawOutput);
|
|
498
|
-
const outputWithMeta = {
|
|
499
|
-
...modifiedOutput,
|
|
500
|
-
_compactionMeta: {
|
|
501
|
-
action: hookResult.metadata.action,
|
|
502
|
-
originalTokens: hookResult.metadata.originalTokens,
|
|
503
|
-
finalTokens: hookResult.metadata.finalTokens,
|
|
504
|
-
tokensSaved: hookResult.metadata.tokensSaved,
|
|
505
|
-
originalContent: originalContentStr,
|
|
506
|
-
},
|
|
507
|
-
};
|
|
508
|
-
// Always return JSON string to preserve metadata
|
|
509
|
-
return JSON.stringify(outputWithMeta);
|
|
510
|
-
}
|
|
511
|
-
// No compaction happened, count original size
|
|
512
|
-
cumulativeToolOutputTokens += outputTokens;
|
|
513
|
-
return result;
|
|
514
|
-
};
|
|
515
|
-
// Create new tool with wrapped function
|
|
516
|
-
// biome-ignore lint/suspicious/noExplicitAny: Need to pass function with dynamic signature
|
|
517
|
-
const wrappedTool = tool(wrappedFunc, {
|
|
518
|
-
name: originalTool.name,
|
|
519
|
-
description: originalTool.description,
|
|
520
|
-
// biome-ignore lint/suspicious/noExplicitAny: Accessing internal schema property
|
|
521
|
-
schema: originalTool.schema,
|
|
522
|
-
});
|
|
523
|
-
// Preserve metadata
|
|
524
|
-
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
525
|
-
wrappedTool.prettyName = originalTool.prettyName;
|
|
526
|
-
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
527
|
-
wrappedTool.icon = originalTool.icon;
|
|
528
|
-
return wrappedTool;
|
|
422
|
+
// Simple tool wrapper that just passes through to the original tool
|
|
423
|
+
// All hook execution (compaction, restart logic) happens at the adapter layer
|
|
424
|
+
const wrappedTools = enabledTools.map((originalTool) => {
|
|
425
|
+
const wrappedFunc = async (input) => {
|
|
426
|
+
// Execute the original tool and return raw result
|
|
427
|
+
const result = await originalTool.invoke(input);
|
|
428
|
+
return result;
|
|
429
|
+
};
|
|
430
|
+
// Create new tool with wrapped function
|
|
431
|
+
// biome-ignore lint/suspicious/noExplicitAny: Need to pass function with dynamic signature
|
|
432
|
+
const wrappedTool = tool(wrappedFunc, {
|
|
433
|
+
name: originalTool.name,
|
|
434
|
+
description: originalTool.description,
|
|
435
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accessing internal schema property
|
|
436
|
+
schema: originalTool.schema,
|
|
529
437
|
});
|
|
530
|
-
|
|
438
|
+
// Preserve metadata
|
|
439
|
+
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
440
|
+
wrappedTool.prettyName = originalTool.prettyName;
|
|
441
|
+
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
442
|
+
wrappedTool.icon = originalTool.icon;
|
|
443
|
+
return wrappedTool;
|
|
444
|
+
});
|
|
531
445
|
// Filter tools if running in subagent mode
|
|
532
446
|
const isSubagent = req.sessionMeta?.[SUBAGENT_MODE_KEY] === true;
|
|
533
447
|
const filteredTools = isSubagent
|
|
@@ -50,11 +50,11 @@ export declare function makeTownE2BTools(): readonly [import("langchain").Dynami
|
|
|
50
50
|
sandboxPath: string;
|
|
51
51
|
fileName?: string | undefined;
|
|
52
52
|
}, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
53
|
-
|
|
53
|
+
document_ids: z.ZodArray<z.ZodString>;
|
|
54
54
|
}, z.core.$strip>, {
|
|
55
|
-
|
|
55
|
+
document_ids: string[];
|
|
56
56
|
}, {
|
|
57
|
-
|
|
57
|
+
document_ids: string[];
|
|
58
58
|
}, string>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
59
59
|
prompt: z.ZodString;
|
|
60
60
|
}, z.core.$strip>, {
|
|
@@ -403,8 +403,8 @@ function makeE2BToolsInternal(getSandbox) {
|
|
|
403
403
|
shareSandboxFile.prettyName = "Share from Sandbox";
|
|
404
404
|
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
405
405
|
shareSandboxFile.icon = "Share";
|
|
406
|
-
// Tool 6:
|
|
407
|
-
const
|
|
406
|
+
// Tool 6: Load Library Documents to Sandbox
|
|
407
|
+
const loadLibraryDocuments = tool(async ({ document_ids }) => {
|
|
408
408
|
const sandbox = await getSandbox();
|
|
409
409
|
try {
|
|
410
410
|
const libraryApiUrl = process.env.LIBRARY_API_URL;
|
|
@@ -412,14 +412,14 @@ function makeE2BToolsInternal(getSandbox) {
|
|
|
412
412
|
if (!libraryApiUrl || !libraryApiKey) {
|
|
413
413
|
throw new Error("LIBRARY_API_URL and LIBRARY_API_KEY environment variables are required");
|
|
414
414
|
}
|
|
415
|
-
const response = await fetch(`${libraryApiUrl}/sandbox/
|
|
415
|
+
const response = await fetch(`${libraryApiUrl}/sandbox/upload_documents_to_sandbox`, {
|
|
416
416
|
method: "POST",
|
|
417
417
|
headers: {
|
|
418
418
|
"Content-Type": "application/json",
|
|
419
419
|
Authorization: `Bearer ${libraryApiKey}`,
|
|
420
420
|
},
|
|
421
421
|
body: JSON.stringify({
|
|
422
|
-
|
|
422
|
+
document_ids,
|
|
423
423
|
sandbox_id: sandbox.sandboxId,
|
|
424
424
|
}),
|
|
425
425
|
});
|
|
@@ -428,34 +428,50 @@ function makeE2BToolsInternal(getSandbox) {
|
|
|
428
428
|
throw new Error(`Library API error: ${response.status} - ${text}`);
|
|
429
429
|
}
|
|
430
430
|
const result = await response.json();
|
|
431
|
-
|
|
431
|
+
// Format the response
|
|
432
|
+
const successfulUploads = result.results
|
|
433
|
+
.filter((r) => r.status === "success")
|
|
434
|
+
.map((r) => r.file_path);
|
|
435
|
+
const failedUploads = result.results
|
|
436
|
+
.filter((r) => r.status === "error")
|
|
437
|
+
.map((r) => `${r.document_id}: ${r.error}`);
|
|
438
|
+
let output = `Status: ${result.status}\n`;
|
|
439
|
+
output += `Uploaded ${result.successful_uploads}/${result.total_requested} documents\n`;
|
|
440
|
+
if (successfulUploads.length > 0) {
|
|
441
|
+
output += `\nSuccessful uploads:\n${successfulUploads.join("\n")}`;
|
|
442
|
+
}
|
|
443
|
+
if (failedUploads.length > 0) {
|
|
444
|
+
output += `\nFailed uploads:\n${failedUploads.join("\n")}`;
|
|
445
|
+
}
|
|
446
|
+
return output;
|
|
432
447
|
}
|
|
433
448
|
catch (error) {
|
|
434
|
-
logger.error("Error
|
|
449
|
+
logger.error("Error loading library documents to sandbox", {
|
|
435
450
|
error,
|
|
436
|
-
|
|
451
|
+
document_ids,
|
|
437
452
|
});
|
|
438
|
-
return `Error
|
|
453
|
+
return `Error loading documents: ${error instanceof Error ? error.message : String(error)}`;
|
|
439
454
|
}
|
|
440
455
|
}, {
|
|
441
|
-
name: "
|
|
442
|
-
description: "
|
|
443
|
-
"Use this to make library documents available for processing in the sandbox environment."
|
|
456
|
+
name: "Sandbox_LoadLibraryDocuments",
|
|
457
|
+
description: "Load multiple documents from the library to the cloud sandbox. " +
|
|
458
|
+
"Use this to make library documents available for processing in the sandbox environment. " +
|
|
459
|
+
"Supports batch uploads - some documents may succeed while others fail.",
|
|
444
460
|
schema: z.object({
|
|
445
|
-
|
|
446
|
-
.string()
|
|
447
|
-
.describe("The
|
|
461
|
+
document_ids: z
|
|
462
|
+
.array(z.string())
|
|
463
|
+
.describe("The IDs of the documents to load from the library"),
|
|
448
464
|
}),
|
|
449
465
|
});
|
|
450
466
|
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
451
|
-
|
|
467
|
+
loadLibraryDocuments.prettyName = "Load Library Documents";
|
|
452
468
|
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
453
|
-
|
|
469
|
+
loadLibraryDocuments.icon = "FileText";
|
|
454
470
|
// biome-ignore lint/suspicious/noExplicitAny: Need to add custom properties to LangChain tool
|
|
455
|
-
|
|
456
|
-
active: "
|
|
457
|
-
past: "
|
|
458
|
-
paramKey: "
|
|
471
|
+
loadLibraryDocuments.verbiage = {
|
|
472
|
+
active: "Loading {document_ids.length} documents",
|
|
473
|
+
past: "Loaded {document_ids.length} documents",
|
|
474
|
+
paramKey: "document_ids",
|
|
459
475
|
};
|
|
460
476
|
// Tool 7: Generate Image in Sandbox
|
|
461
477
|
const generateImage = tool(async ({ prompt }) => {
|
|
@@ -634,7 +650,7 @@ generateImage();
|
|
|
634
650
|
readSandboxFile,
|
|
635
651
|
writeSandboxFile,
|
|
636
652
|
shareSandboxFile,
|
|
637
|
-
|
|
653
|
+
loadLibraryDocuments,
|
|
638
654
|
generateImage,
|
|
639
655
|
];
|
|
640
656
|
}
|
|
@@ -28,6 +28,12 @@ export interface SubagentMessage {
|
|
|
28
28
|
content: string;
|
|
29
29
|
contentBlocks: SubagentContentBlock[];
|
|
30
30
|
toolCalls: SubagentToolCall[];
|
|
31
|
+
_meta?: {
|
|
32
|
+
semanticName?: string;
|
|
33
|
+
agentDefinitionName?: string;
|
|
34
|
+
currentActivity?: string;
|
|
35
|
+
statusGenerating?: boolean;
|
|
36
|
+
};
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Maps query hash to toolCallId.
|