@townco/agent 0.1.53 → 0.1.55
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.d.ts +16 -0
- package/dist/acp-server/adapter.js +231 -17
- package/dist/acp-server/cli.d.ts +1 -3
- package/dist/acp-server/http.js +51 -7
- package/dist/acp-server/session-storage.d.ts +16 -1
- package/dist/acp-server/session-storage.js +23 -0
- package/dist/bin.js +0 -0
- package/dist/definition/index.d.ts +2 -2
- package/dist/definition/index.js +1 -0
- package/dist/index.js +1 -1
- package/dist/logger.d.ts +26 -0
- package/dist/logger.js +43 -0
- package/dist/runner/agent-runner.d.ts +7 -2
- package/dist/runner/hooks/executor.js +1 -1
- package/dist/runner/hooks/loader.js +1 -1
- package/dist/runner/hooks/predefined/compaction-tool.js +1 -1
- package/dist/runner/hooks/predefined/tool-response-compactor.js +1 -1
- package/dist/runner/index.d.ts +1 -3
- package/dist/runner/langchain/index.js +179 -39
- package/dist/runner/langchain/model-factory.js +1 -1
- package/dist/runner/langchain/tools/generate_image.d.ts +28 -0
- package/dist/runner/langchain/tools/generate_image.js +135 -0
- package/dist/runner/langchain/tools/port-utils.d.ts +8 -0
- package/dist/runner/langchain/tools/port-utils.js +35 -0
- package/dist/runner/langchain/tools/subagent.d.ts +6 -1
- package/dist/runner/langchain/tools/subagent.js +242 -129
- package/dist/runner/tools.d.ts +19 -2
- package/dist/runner/tools.js +9 -0
- package/dist/storage/index.js +1 -1
- package/dist/telemetry/index.js +7 -1
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +27 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +1 -1
- package/package.json +11 -6
- package/templates/index.ts +37 -6
- package/dist/definition/mcp.d.ts +0 -0
- package/dist/definition/mcp.js +0 -0
- package/dist/definition/tools/todo.d.ts +0 -49
- package/dist/definition/tools/todo.js +0 -80
- package/dist/definition/tools/web_search.d.ts +0 -4
- package/dist/definition/tools/web_search.js +0 -26
- package/dist/dev-agent/index.d.ts +0 -2
- package/dist/dev-agent/index.js +0 -18
- package/dist/example.d.ts +0 -2
- package/dist/example.js +0 -19
- package/dist/scaffold/link-local.d.ts +0 -1
- package/dist/scaffold/link-local.js +0 -54
- package/dist/utils/__tests__/tool-overhead-calculator.test.d.ts +0 -1
- package/dist/utils/__tests__/tool-overhead-calculator.test.js +0 -153
- package/dist/utils/logger.d.ts +0 -39
- package/dist/utils/logger.js +0 -175
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
|
|
4
|
+
import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
6
5
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
6
|
+
import { createLogger as coreCreateLogger } from "@townco/core";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { SUBAGENT_MODE_KEY } from "../../../acp-server/adapter.js";
|
|
9
|
+
import { findAvailablePort } from "./port-utils.js";
|
|
9
10
|
/**
|
|
10
11
|
* Name of the Task tool created by makeSubagentsTool
|
|
11
12
|
*/
|
|
12
|
-
export const
|
|
13
|
+
export const SUBAGENT_TOOL_NAME = "subagent";
|
|
14
|
+
/**
|
|
15
|
+
* Base port for subagent HTTP servers (avoid conflict with main agents at 3100+)
|
|
16
|
+
*/
|
|
17
|
+
const SUBAGENT_BASE_PORT = 4000;
|
|
18
|
+
/**
|
|
19
|
+
* Wait for HTTP server to be ready by polling health endpoint
|
|
20
|
+
*/
|
|
21
|
+
async function waitForServerReady(port, timeoutMs = 30000) {
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
const baseDelay = 50;
|
|
24
|
+
let attempt = 0;
|
|
25
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`http://localhost:${port}/health`, {
|
|
28
|
+
signal: AbortSignal.timeout(1000),
|
|
29
|
+
});
|
|
30
|
+
if (response.ok) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Server not ready yet
|
|
36
|
+
}
|
|
37
|
+
await new Promise((r) => setTimeout(r, baseDelay * Math.pow(1.5, attempt)));
|
|
38
|
+
attempt++;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Subagent server at port ${port} did not become ready within ${timeoutMs}ms`);
|
|
41
|
+
}
|
|
13
42
|
/**
|
|
14
43
|
* Creates a DirectTool that delegates work to one of multiple configured subagents.
|
|
15
44
|
*
|
|
@@ -61,9 +90,17 @@ export function makeSubagentsTool(configs) {
|
|
|
61
90
|
.map((config) => `"${config.agentName}": ${config.description}`)
|
|
62
91
|
.join("\n");
|
|
63
92
|
const agentNames = configs.map((c) => c.agentName);
|
|
93
|
+
// Extract subagent configs for metadata (agentName, description, displayName)
|
|
94
|
+
const subagentConfigs = configs.map((config) => ({
|
|
95
|
+
agentName: config.agentName,
|
|
96
|
+
description: config.description,
|
|
97
|
+
displayName: config.displayName,
|
|
98
|
+
}));
|
|
64
99
|
return {
|
|
65
100
|
type: "direct",
|
|
66
|
-
name:
|
|
101
|
+
name: SUBAGENT_TOOL_NAME,
|
|
102
|
+
prettyName: "Subagent",
|
|
103
|
+
icon: "BrainCircuit",
|
|
67
104
|
description: `Launch a new agent to handle complex, multi-step tasks autonomously.
|
|
68
105
|
|
|
69
106
|
The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
|
|
@@ -129,20 +166,22 @@ assistant: "I'm going to use the Task tool to launch the greeting-responder agen
|
|
|
129
166
|
.describe("The name of the subagent to use"),
|
|
130
167
|
query: z.string().describe("The query or task to send to the subagent"),
|
|
131
168
|
}),
|
|
169
|
+
// Expose subagent configs for metadata extraction by the adapter
|
|
170
|
+
subagentConfigs,
|
|
132
171
|
fn: async (input) => {
|
|
133
172
|
const { agentName, query } = input;
|
|
134
173
|
const agent = agentMap.get(agentName);
|
|
135
174
|
if (!agent) {
|
|
136
175
|
throw new Error(`Unknown agent: ${agentName}`);
|
|
137
176
|
}
|
|
138
|
-
return await querySubagent(agent.agentPath, agent.agentDir, query);
|
|
177
|
+
return await querySubagent(agentName, agent.agentPath, agent.agentDir, query);
|
|
139
178
|
},
|
|
140
179
|
};
|
|
141
180
|
}
|
|
142
181
|
/**
|
|
143
|
-
* Internal function that spawns a subagent
|
|
182
|
+
* Internal function that spawns a subagent HTTP server and queries it.
|
|
144
183
|
*/
|
|
145
|
-
async function querySubagent(agentPath, agentWorkingDirectory, query) {
|
|
184
|
+
async function querySubagent(agentName, agentPath, agentWorkingDirectory, query) {
|
|
146
185
|
// Validate that the agent exists
|
|
147
186
|
try {
|
|
148
187
|
await fs.access(agentPath);
|
|
@@ -150,153 +189,227 @@ async function querySubagent(agentPath, agentWorkingDirectory, query) {
|
|
|
150
189
|
catch (_error) {
|
|
151
190
|
throw new Error(`Agent not found at ${agentPath}. Make sure the agent exists and has an index.ts file.`);
|
|
152
191
|
}
|
|
192
|
+
// Find an available port for this subagent
|
|
193
|
+
const port = await findAvailablePort(SUBAGENT_BASE_PORT, 100);
|
|
194
|
+
// Create a logger for this subagent instance with port prefix
|
|
195
|
+
// Use core logger directly since service name already identifies the subagent
|
|
196
|
+
const logger = coreCreateLogger(`subagent:${port}:${agentName}`);
|
|
153
197
|
let agentProcess = null;
|
|
154
|
-
let
|
|
198
|
+
let sseAbortController = null;
|
|
155
199
|
try {
|
|
156
|
-
//
|
|
157
|
-
|
|
200
|
+
// Get the parent's logs directory to pass to the subagent
|
|
201
|
+
const parentLogsDir = process.env.TOWN_LOGS_DIR || path.join(process.cwd(), ".logs");
|
|
202
|
+
// Spawn the agent process in HTTP mode
|
|
203
|
+
agentProcess = spawn("bun", [agentPath, "http"], {
|
|
158
204
|
cwd: agentWorkingDirectory,
|
|
159
|
-
env: {
|
|
205
|
+
env: {
|
|
206
|
+
...process.env,
|
|
207
|
+
PORT: String(port),
|
|
208
|
+
TOWN_LOGS_DIR: parentLogsDir,
|
|
209
|
+
TOWN_SUBAGENT_NAME: agentName,
|
|
210
|
+
},
|
|
160
211
|
stdio: ["pipe", "pipe", "pipe"],
|
|
161
212
|
});
|
|
162
|
-
if (!agentProcess.
|
|
163
|
-
throw new Error("Failed to create
|
|
213
|
+
if (!agentProcess.stderr) {
|
|
214
|
+
throw new Error("Failed to create stderr pipe for agent process");
|
|
164
215
|
}
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
async requestPermission(_params) {
|
|
176
|
-
// Deny all permission requests from the subagent
|
|
177
|
-
return { outcome: { outcome: "cancelled" } };
|
|
178
|
-
},
|
|
179
|
-
async sessionUpdate(params) {
|
|
180
|
-
// Handle session updates from the agent
|
|
181
|
-
const paramsExtended = params;
|
|
182
|
-
const update = paramsExtended.update;
|
|
183
|
-
// Reset accumulated text when a tool call starts (marks a new message boundary)
|
|
184
|
-
if (update?.sessionUpdate === "tool_call") {
|
|
185
|
-
responseText = "";
|
|
186
|
-
}
|
|
187
|
-
// Accumulate agent_message_chunk text content
|
|
188
|
-
if (update?.sessionUpdate === "agent_message_chunk") {
|
|
189
|
-
const content = update.content;
|
|
190
|
-
if (content &&
|
|
191
|
-
content.type === "text" &&
|
|
192
|
-
typeof content.text === "string") {
|
|
193
|
-
responseText += content.text;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
async writeTextFile() {
|
|
198
|
-
// Subagents should not write files outside their scope
|
|
199
|
-
throw new Error("Subagent attempted to write files, which is not allowed");
|
|
200
|
-
},
|
|
201
|
-
async readTextFile() {
|
|
202
|
-
// Subagents should not read files outside their scope
|
|
203
|
-
throw new Error("Subagent attempted to read files, which is not allowed");
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
};
|
|
207
|
-
// Create the client-side connection
|
|
208
|
-
connection = new ClientSideConnection(clientFactory, stream);
|
|
209
|
-
// Set up timeout for the entire operation
|
|
210
|
-
const timeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
211
|
-
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
212
|
-
setTimeout(() => {
|
|
213
|
-
reject(new Error(`Subagent query timed out after ${timeoutMs / 1000} seconds`));
|
|
214
|
-
}, timeoutMs);
|
|
215
|
-
});
|
|
216
|
-
// Handle process errors and exit
|
|
217
|
-
const processExitPromise = new Promise((_resolve, reject) => {
|
|
218
|
-
agentProcess?.on("exit", (code, signal) => {
|
|
219
|
-
if (code !== 0 && code !== null) {
|
|
220
|
-
reject(new Error(`Agent process exited with code ${code} and signal ${signal}`));
|
|
216
|
+
// Capture stdout and forward to logger
|
|
217
|
+
if (agentProcess.stdout) {
|
|
218
|
+
agentProcess.stdout.on("data", (data) => {
|
|
219
|
+
const lines = data
|
|
220
|
+
.toString()
|
|
221
|
+
.split("\n")
|
|
222
|
+
.filter((line) => line.trim());
|
|
223
|
+
for (const line of lines) {
|
|
224
|
+
logger.info(line);
|
|
221
225
|
}
|
|
222
226
|
});
|
|
227
|
+
}
|
|
228
|
+
// Capture stderr and forward to logger as errors
|
|
229
|
+
agentProcess.stderr.on("data", (data) => {
|
|
230
|
+
const lines = data
|
|
231
|
+
.toString()
|
|
232
|
+
.split("\n")
|
|
233
|
+
.filter((line) => line.trim());
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
logger.error(line);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// Handle process errors
|
|
239
|
+
const processErrorPromise = new Promise((_, reject) => {
|
|
223
240
|
agentProcess?.on("error", (error) => {
|
|
241
|
+
logger.error(`Process error: ${error.message}`);
|
|
224
242
|
reject(new Error(`Agent process error: ${error.message}`));
|
|
225
243
|
});
|
|
244
|
+
agentProcess?.on("exit", (code, signal) => {
|
|
245
|
+
if (code !== 0 && code !== null) {
|
|
246
|
+
logger.error(`Process exited with code ${code}, signal ${signal}`);
|
|
247
|
+
reject(new Error(`Agent process exited unexpectedly with code ${code}, signal ${signal}`));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
226
250
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
logger.info(`Starting subagent HTTP server on port ${port}`);
|
|
252
|
+
// Wait for server to be ready
|
|
253
|
+
await Promise.race([waitForServerReady(port), processErrorPromise]);
|
|
254
|
+
logger.info(`Subagent server ready on port ${port}`);
|
|
255
|
+
const baseUrl = `http://localhost:${port}`;
|
|
256
|
+
// Step 1: Initialize ACP connection
|
|
257
|
+
const initResponse = await fetch(`${baseUrl}/rpc`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: { "Content-Type": "application/json" },
|
|
260
|
+
body: JSON.stringify({
|
|
261
|
+
jsonrpc: "2.0",
|
|
262
|
+
id: "init-1",
|
|
263
|
+
method: "initialize",
|
|
264
|
+
params: {
|
|
265
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
266
|
+
clientCapabilities: {
|
|
267
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
236
268
|
},
|
|
237
269
|
},
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const ctxForInjection = activeSpan
|
|
253
|
-
? trace.setSpan(activeCtx, activeSpan)
|
|
254
|
-
: activeCtx;
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
if (!initResponse.ok) {
|
|
273
|
+
throw new Error(`Initialize failed: HTTP ${initResponse.status}`);
|
|
274
|
+
}
|
|
275
|
+
// Step 2: Create new session with subagent mode and OTEL context
|
|
276
|
+
// Prepare OpenTelemetry trace context to propagate to the subagent
|
|
277
|
+
const otelCarrier = {};
|
|
278
|
+
const activeCtx = context.active();
|
|
279
|
+
const activeSpan = trace.getSpan(activeCtx);
|
|
280
|
+
if (process.env.DEBUG_TELEMETRY === "true") {
|
|
281
|
+
console.log(`[querySubagent] Active span when tool executes:`, activeSpan?.spanContext());
|
|
282
|
+
}
|
|
283
|
+
if (activeSpan) {
|
|
284
|
+
const ctxForInjection = trace.setSpan(activeCtx, activeSpan);
|
|
255
285
|
propagation.inject(ctxForInjection, otelCarrier);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
}
|
|
287
|
+
const hasOtelContext = Object.keys(otelCarrier).length > 0;
|
|
288
|
+
const sessionResponse = await fetch(`${baseUrl}/rpc`, {
|
|
289
|
+
method: "POST",
|
|
290
|
+
headers: { "Content-Type": "application/json" },
|
|
291
|
+
body: JSON.stringify({
|
|
292
|
+
jsonrpc: "2.0",
|
|
293
|
+
id: "session-1",
|
|
294
|
+
method: "session/new",
|
|
295
|
+
params: {
|
|
296
|
+
cwd: agentWorkingDirectory,
|
|
297
|
+
mcpServers: [],
|
|
298
|
+
_meta: {
|
|
299
|
+
[SUBAGENT_MODE_KEY]: true,
|
|
300
|
+
...(hasOtelContext ? { otelTraceContext: otelCarrier } : {}),
|
|
301
|
+
},
|
|
264
302
|
},
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
if (!sessionResponse.ok) {
|
|
306
|
+
throw new Error(`Session creation failed: HTTP ${sessionResponse.status}`);
|
|
307
|
+
}
|
|
308
|
+
const sessionResult = (await sessionResponse.json());
|
|
309
|
+
const sessionId = sessionResult.result?.sessionId;
|
|
310
|
+
if (!sessionId) {
|
|
311
|
+
throw new Error("No sessionId in session/new response");
|
|
312
|
+
}
|
|
313
|
+
// Step 3: Connect to SSE for receiving streaming responses
|
|
314
|
+
sseAbortController = new AbortController();
|
|
315
|
+
let responseText = "";
|
|
316
|
+
const ssePromise = (async () => {
|
|
317
|
+
const sseResponse = await fetch(`${baseUrl}/events`, {
|
|
318
|
+
headers: { "X-Session-ID": sessionId },
|
|
319
|
+
signal: sseAbortController.signal,
|
|
265
320
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
321
|
+
if (!sseResponse.ok || !sseResponse.body) {
|
|
322
|
+
throw new Error(`SSE connection failed: HTTP ${sseResponse.status}`);
|
|
323
|
+
}
|
|
324
|
+
const reader = sseResponse.body.getReader();
|
|
325
|
+
const decoder = new TextDecoder();
|
|
326
|
+
let buffer = "";
|
|
327
|
+
while (true) {
|
|
328
|
+
const { done, value } = await reader.read();
|
|
329
|
+
if (done)
|
|
330
|
+
break;
|
|
331
|
+
buffer += decoder.decode(value, { stream: true });
|
|
332
|
+
const lines = buffer.split("\n");
|
|
333
|
+
buffer = lines.pop() || "";
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
if (line.startsWith("data:")) {
|
|
336
|
+
const data = line.substring(5).trim();
|
|
337
|
+
if (!data)
|
|
338
|
+
continue;
|
|
339
|
+
try {
|
|
340
|
+
const message = JSON.parse(data);
|
|
341
|
+
// Handle session/update notifications for agent_message_chunk
|
|
342
|
+
if (message.method === "session/update" &&
|
|
343
|
+
message.params?.update?.sessionUpdate === "agent_message_chunk") {
|
|
344
|
+
const content = message.params.update.content;
|
|
345
|
+
if (content?.type === "text" &&
|
|
346
|
+
typeof content.text === "string") {
|
|
347
|
+
responseText += content.text;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Reset on tool_call (marks new message boundary)
|
|
351
|
+
if (message.params?.update?.sessionUpdate === "tool_call") {
|
|
352
|
+
responseText = "";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Ignore malformed SSE data
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
})();
|
|
362
|
+
// Step 4: Send the prompt with timeout
|
|
363
|
+
const timeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
364
|
+
const promptPromise = (async () => {
|
|
365
|
+
const promptResponse = await fetch(`${baseUrl}/rpc`, {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: { "Content-Type": "application/json" },
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
jsonrpc: "2.0",
|
|
370
|
+
id: "prompt-1",
|
|
371
|
+
method: "session/prompt",
|
|
372
|
+
params: {
|
|
373
|
+
sessionId,
|
|
374
|
+
prompt: [{ type: "text", text: query }],
|
|
273
375
|
},
|
|
274
|
-
|
|
376
|
+
}),
|
|
275
377
|
});
|
|
276
|
-
|
|
378
|
+
if (!promptResponse.ok) {
|
|
379
|
+
throw new Error(`Prompt failed: HTTP ${promptResponse.status}`);
|
|
380
|
+
}
|
|
381
|
+
// Wait for prompt to complete (this blocks until agent finishes processing)
|
|
382
|
+
const promptResult = (await promptResponse.json());
|
|
383
|
+
if (promptResult.error) {
|
|
384
|
+
throw new Error(`Prompt error: ${promptResult.error.message || JSON.stringify(promptResult.error)}`);
|
|
385
|
+
}
|
|
277
386
|
})();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
387
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
reject(new Error(`Subagent query timed out after ${timeoutMs / 1000} seconds`));
|
|
390
|
+
}, timeoutMs);
|
|
391
|
+
});
|
|
392
|
+
// Wait for prompt to complete with timeout
|
|
393
|
+
await Promise.race([promptPromise, timeoutPromise, processErrorPromise]);
|
|
394
|
+
// Give SSE a moment to flush remaining messages
|
|
395
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
396
|
+
// Abort SSE connection
|
|
397
|
+
sseAbortController.abort();
|
|
398
|
+
// Wait for SSE to finish (with timeout)
|
|
399
|
+
await Promise.race([
|
|
400
|
+
ssePromise.catch(() => { }), // Ignore abort errors
|
|
401
|
+
new Promise((r) => setTimeout(r, 1000)),
|
|
283
402
|
]);
|
|
403
|
+
return responseText;
|
|
284
404
|
}
|
|
285
405
|
finally {
|
|
286
|
-
// Cleanup:
|
|
406
|
+
// Cleanup: abort SSE and kill process
|
|
407
|
+
logger.info(`Shutting down subagent on port ${port}`);
|
|
408
|
+
if (sseAbortController) {
|
|
409
|
+
sseAbortController.abort();
|
|
410
|
+
}
|
|
287
411
|
if (agentProcess) {
|
|
288
412
|
agentProcess.kill();
|
|
289
413
|
}
|
|
290
|
-
if (connection) {
|
|
291
|
-
try {
|
|
292
|
-
await Promise.race([
|
|
293
|
-
connection.closed,
|
|
294
|
-
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
295
|
-
]);
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
// Ignore cleanup errors
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
414
|
}
|
|
302
415
|
}
|
package/dist/runner/tools.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** Built-in tool types. */
|
|
3
|
-
export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>;
|
|
3
|
+
export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>;
|
|
4
|
+
/** Subagent configuration schema for Task tools. */
|
|
5
|
+
export declare const zSubagentConfig: z.ZodObject<{
|
|
6
|
+
agentName: z.ZodString;
|
|
7
|
+
description: z.ZodString;
|
|
8
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
4
10
|
/** Direct tool object schema (for tools imported directly in code). */
|
|
5
11
|
declare const zDirectTool: z.ZodObject<{
|
|
6
12
|
type: z.ZodLiteral<"direct">;
|
|
@@ -10,9 +16,14 @@ declare const zDirectTool: z.ZodObject<{
|
|
|
10
16
|
schema: z.ZodAny;
|
|
11
17
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
12
18
|
icon: z.ZodOptional<z.ZodString>;
|
|
19
|
+
subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
20
|
+
agentName: z.ZodString;
|
|
21
|
+
description: z.ZodString;
|
|
22
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>>>;
|
|
13
24
|
}, z.core.$strip>;
|
|
14
25
|
/** Tool type - can be a built-in tool string or custom tool object. */
|
|
15
|
-
export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>, z.ZodObject<{
|
|
26
|
+
export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>, z.ZodObject<{
|
|
16
27
|
type: z.ZodLiteral<"custom">;
|
|
17
28
|
modulePath: z.ZodString;
|
|
18
29
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -26,8 +37,14 @@ export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodL
|
|
|
26
37
|
schema: z.ZodAny;
|
|
27
38
|
prettyName: z.ZodOptional<z.ZodString>;
|
|
28
39
|
icon: z.ZodOptional<z.ZodString>;
|
|
40
|
+
subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
41
|
+
agentName: z.ZodString;
|
|
42
|
+
description: z.ZodString;
|
|
43
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strip>>>;
|
|
29
45
|
}, z.core.$strip>]>;
|
|
30
46
|
export type ToolType = z.infer<typeof zToolType>;
|
|
31
47
|
export type BuiltInToolType = z.infer<typeof zBuiltInToolType>;
|
|
32
48
|
export type DirectTool = z.infer<typeof zDirectTool>;
|
|
49
|
+
export type SubagentConfig = z.infer<typeof zSubagentConfig>;
|
|
33
50
|
export {};
|
package/dist/runner/tools.js
CHANGED
|
@@ -5,6 +5,7 @@ export const zBuiltInToolType = z.union([
|
|
|
5
5
|
z.literal("get_weather"),
|
|
6
6
|
z.literal("web_search"),
|
|
7
7
|
z.literal("filesystem"),
|
|
8
|
+
z.literal("generate_image"),
|
|
8
9
|
]);
|
|
9
10
|
/** Custom tool schema (loaded from module path). */
|
|
10
11
|
const zCustomTool = z.object({
|
|
@@ -16,6 +17,12 @@ const zFilesystemTool = z.object({
|
|
|
16
17
|
type: z.literal("filesystem"),
|
|
17
18
|
working_directory: z.string().optional(),
|
|
18
19
|
});
|
|
20
|
+
/** Subagent configuration schema for Task tools. */
|
|
21
|
+
export const zSubagentConfig = z.object({
|
|
22
|
+
agentName: z.string(),
|
|
23
|
+
description: z.string(),
|
|
24
|
+
displayName: z.string().optional(),
|
|
25
|
+
});
|
|
19
26
|
/** Direct tool object schema (for tools imported directly in code). */
|
|
20
27
|
const zDirectTool = z.object({
|
|
21
28
|
type: z.literal("direct"),
|
|
@@ -25,6 +32,8 @@ const zDirectTool = z.object({
|
|
|
25
32
|
schema: z.any(), // Accept any Zod schema
|
|
26
33
|
prettyName: z.string().optional(),
|
|
27
34
|
icon: z.string().optional(),
|
|
35
|
+
/** Subagent configurations (only present for Task tools created by makeSubagentsTool). */
|
|
36
|
+
subagentConfigs: z.array(zSubagentConfig).optional(),
|
|
28
37
|
});
|
|
29
38
|
/** Tool type - can be a built-in tool string or custom tool object. */
|
|
30
39
|
export const zToolType = z.union([
|
package/dist/storage/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { createLogger } from "
|
|
4
|
+
import { createLogger } from "../logger.js";
|
|
5
5
|
const AGENTS_DIR = join(homedir(), ".config", "town", "agents");
|
|
6
6
|
const logger = createLogger("storage");
|
|
7
7
|
/**
|
package/dist/telemetry/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides tracing and logging capabilities for agent operations
|
|
4
4
|
*/
|
|
5
5
|
import { context, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
6
|
-
import { logs } from "@opentelemetry/api-logs";
|
|
6
|
+
import { logs, SeverityNumber, } from "@opentelemetry/api-logs";
|
|
7
7
|
class AgentTelemetry {
|
|
8
8
|
tracer = null;
|
|
9
9
|
logger = null;
|
|
@@ -115,7 +115,13 @@ class AgentTelemetry {
|
|
|
115
115
|
if (!this.enabled || !this.logger) {
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
+
const severityNumber = {
|
|
119
|
+
info: SeverityNumber.INFO,
|
|
120
|
+
warn: SeverityNumber.WARN,
|
|
121
|
+
error: SeverityNumber.ERROR,
|
|
122
|
+
}[level];
|
|
118
123
|
this.logger.emit({
|
|
124
|
+
severityNumber,
|
|
119
125
|
severityText: level.toUpperCase(),
|
|
120
126
|
body: message,
|
|
121
127
|
attributes: {
|
|
@@ -2,6 +2,9 @@ import type { AgentDefinition } from "../definition";
|
|
|
2
2
|
export interface TemplateVars {
|
|
3
3
|
name: string;
|
|
4
4
|
model: string;
|
|
5
|
+
displayName?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
suggestedPrompts?: string[];
|
|
5
8
|
tools: Array<string | {
|
|
6
9
|
type: "custom";
|
|
7
10
|
modulePath: string;
|
package/dist/templates/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as prettier from "prettier";
|
|
2
2
|
export function getTemplateVars(name, definition) {
|
|
3
3
|
const tools = definition.tools ?? [];
|
|
4
|
-
|
|
4
|
+
const result = {
|
|
5
5
|
name,
|
|
6
6
|
model: definition.model,
|
|
7
7
|
tools,
|
|
@@ -9,6 +9,16 @@ export function getTemplateVars(name, definition) {
|
|
|
9
9
|
hasWebSearch: tools.some((tool) => typeof tool === "string" && tool === "web_search"),
|
|
10
10
|
hooks: definition.hooks,
|
|
11
11
|
};
|
|
12
|
+
if (definition.displayName) {
|
|
13
|
+
result.displayName = definition.displayName;
|
|
14
|
+
}
|
|
15
|
+
if (definition.description) {
|
|
16
|
+
result.description = definition.description;
|
|
17
|
+
}
|
|
18
|
+
if (definition.suggestedPrompts) {
|
|
19
|
+
result.suggestedPrompts = definition.suggestedPrompts;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
12
22
|
}
|
|
13
23
|
export function generatePackageJson(vars) {
|
|
14
24
|
// Include @townco/agent as a dependency instead of bundling
|
|
@@ -44,16 +54,28 @@ export function generatePackageJson(vars) {
|
|
|
44
54
|
return JSON.stringify(pkg, null, 2);
|
|
45
55
|
}
|
|
46
56
|
export async function generateIndexTs(vars) {
|
|
57
|
+
// Build agent definition with fields in a logical order
|
|
47
58
|
const agentDef = {
|
|
48
59
|
model: vars.model,
|
|
49
|
-
systemPrompt: vars.systemPrompt,
|
|
50
|
-
tools: vars.tools,
|
|
51
|
-
hooks: vars.hooks,
|
|
52
60
|
};
|
|
61
|
+
if (vars.displayName) {
|
|
62
|
+
agentDef.displayName = vars.displayName;
|
|
63
|
+
}
|
|
64
|
+
if (vars.description) {
|
|
65
|
+
agentDef.description = vars.description;
|
|
66
|
+
}
|
|
67
|
+
if (vars.suggestedPrompts) {
|
|
68
|
+
agentDef.suggestedPrompts = vars.suggestedPrompts;
|
|
69
|
+
}
|
|
70
|
+
agentDef.systemPrompt = vars.systemPrompt;
|
|
71
|
+
agentDef.tools = vars.tools;
|
|
72
|
+
if (vars.hooks) {
|
|
73
|
+
agentDef.hooks = vars.hooks;
|
|
74
|
+
}
|
|
53
75
|
return prettier.format(`import { makeHttpTransport, makeStdioTransport } from "@townco/agent/acp-server";
|
|
54
76
|
import type { AgentDefinition } from "@townco/agent/definition";
|
|
77
|
+
import { createLogger } from "@townco/agent/logger";
|
|
55
78
|
import { basename } from "node:path";
|
|
56
|
-
import { createLogger } from "@townco/core";
|
|
57
79
|
|
|
58
80
|
const logger = createLogger("agent-index");
|
|
59
81
|
|