@clinebot/agents 0.0.0 → 0.0.2
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/agent.d.ts +4 -0
- package/dist/index.browser.js +20 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +18 -15
- package/dist/index.node.js +18 -15
- package/dist/runtime/turn-processor.d.ts +1 -0
- package/dist/teams/multi-agent.d.ts +6 -0
- package/dist/teams/spawn-agent-tool.d.ts +10 -6
- package/dist/types.d.ts +66 -1
- package/package.json +7 -7
- package/src/agent.test.ts +186 -0
- package/src/agent.ts +255 -8
- package/src/hooks/engine.test.ts +1 -0
- package/src/index.ts +2 -0
- package/src/runtime/tool-orchestrator.test.ts +53 -0
- package/src/runtime/tool-orchestrator.ts +12 -1
- package/src/runtime/turn-processor.ts +56 -5
- package/src/teams/multi-agent.ts +96 -15
- package/src/teams/spawn-agent-tool.test.ts +92 -0
- package/src/teams/spawn-agent-tool.ts +65 -31
- package/src/teams/team-tools.test.ts +18 -0
- package/src/teams/team-tools.ts +12 -4
- package/src/types.ts +90 -2
package/src/teams/multi-agent.ts
CHANGED
|
@@ -84,6 +84,7 @@ export type TeamEvent =
|
|
|
84
84
|
agentId: string;
|
|
85
85
|
result?: AgentResult;
|
|
86
86
|
error?: Error;
|
|
87
|
+
messages?: AgentResult["messages"];
|
|
87
88
|
}
|
|
88
89
|
| { type: TeamMessageType.AgentEvent; agentId: string; event: AgentEvent }
|
|
89
90
|
| {
|
|
@@ -242,7 +243,12 @@ export class AgentTeam {
|
|
|
242
243
|
return result;
|
|
243
244
|
} catch (error) {
|
|
244
245
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
245
|
-
this.emitEvent({
|
|
246
|
+
this.emitEvent({
|
|
247
|
+
type: TeamMessageType.TaskEnd,
|
|
248
|
+
agentId,
|
|
249
|
+
error: err,
|
|
250
|
+
messages: agent.getMessages(),
|
|
251
|
+
});
|
|
246
252
|
throw error;
|
|
247
253
|
}
|
|
248
254
|
}
|
|
@@ -268,7 +274,12 @@ export class AgentTeam {
|
|
|
268
274
|
return result;
|
|
269
275
|
} catch (error) {
|
|
270
276
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
271
|
-
this.emitEvent({
|
|
277
|
+
this.emitEvent({
|
|
278
|
+
type: TeamMessageType.TaskEnd,
|
|
279
|
+
agentId,
|
|
280
|
+
error: err,
|
|
281
|
+
messages: agent.getMessages(),
|
|
282
|
+
});
|
|
272
283
|
throw error;
|
|
273
284
|
}
|
|
274
285
|
}
|
|
@@ -323,6 +334,7 @@ export class AgentTeam {
|
|
|
323
334
|
type: TeamMessageType.TaskEnd,
|
|
324
335
|
agentId: task.agentId,
|
|
325
336
|
error: err,
|
|
337
|
+
messages: agent.getMessages(),
|
|
326
338
|
});
|
|
327
339
|
return {
|
|
328
340
|
agentId: task.agentId,
|
|
@@ -384,6 +396,7 @@ export class AgentTeam {
|
|
|
384
396
|
type: TeamMessageType.TaskEnd,
|
|
385
397
|
agentId: task.agentId,
|
|
386
398
|
error: err,
|
|
399
|
+
messages: agent.getMessages(),
|
|
387
400
|
});
|
|
388
401
|
results.push({
|
|
389
402
|
agentId: task.agentId,
|
|
@@ -462,7 +475,12 @@ export class AgentTeam {
|
|
|
462
475
|
}
|
|
463
476
|
} catch (error) {
|
|
464
477
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
465
|
-
this.emitEvent({
|
|
478
|
+
this.emitEvent({
|
|
479
|
+
type: TeamMessageType.TaskEnd,
|
|
480
|
+
agentId,
|
|
481
|
+
error: err,
|
|
482
|
+
messages: agent.getMessages(),
|
|
483
|
+
});
|
|
466
484
|
results.push({
|
|
467
485
|
agentId,
|
|
468
486
|
result: undefined as unknown as AgentResult,
|
|
@@ -733,6 +751,9 @@ export interface TeamRunRecord {
|
|
|
733
751
|
endedAt?: Date;
|
|
734
752
|
leaseOwner?: string;
|
|
735
753
|
heartbeatAt?: Date;
|
|
754
|
+
lastProgressAt?: Date;
|
|
755
|
+
lastProgressMessage?: string;
|
|
756
|
+
currentActivity?: string;
|
|
736
757
|
result?: AgentResult;
|
|
737
758
|
error?: string;
|
|
738
759
|
}
|
|
@@ -1274,7 +1295,12 @@ export class AgentTeamsRuntime {
|
|
|
1274
1295
|
return result;
|
|
1275
1296
|
} catch (error) {
|
|
1276
1297
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1277
|
-
this.emitEvent({
|
|
1298
|
+
this.emitEvent({
|
|
1299
|
+
type: TeamMessageType.TaskEnd,
|
|
1300
|
+
agentId,
|
|
1301
|
+
error: err,
|
|
1302
|
+
messages: member.agent.getMessages(),
|
|
1303
|
+
});
|
|
1278
1304
|
this.appendMissionLog({
|
|
1279
1305
|
agentId,
|
|
1280
1306
|
taskId: options?.taskId,
|
|
@@ -1316,6 +1342,9 @@ export class AgentTeamsRuntime {
|
|
|
1316
1342
|
startedAt: new Date(0),
|
|
1317
1343
|
leaseOwner: options?.leaseOwner,
|
|
1318
1344
|
heartbeatAt: undefined,
|
|
1345
|
+
lastProgressAt: new Date(),
|
|
1346
|
+
lastProgressMessage: "queued",
|
|
1347
|
+
currentActivity: "queued",
|
|
1319
1348
|
};
|
|
1320
1349
|
this.runs.set(runId, record);
|
|
1321
1350
|
this.runQueue.push(runId);
|
|
@@ -1369,18 +1398,14 @@ export class AgentTeamsRuntime {
|
|
|
1369
1398
|
run.status = "running";
|
|
1370
1399
|
run.startedAt = new Date();
|
|
1371
1400
|
run.heartbeatAt = new Date();
|
|
1401
|
+
run.currentActivity = "run_started";
|
|
1372
1402
|
this.emitEvent({ type: TeamMessageType.RunStarted, run: { ...run } });
|
|
1373
1403
|
|
|
1374
1404
|
const heartbeatTimer = setInterval(() => {
|
|
1375
1405
|
if (run.status !== "running") {
|
|
1376
1406
|
return;
|
|
1377
1407
|
}
|
|
1378
|
-
run
|
|
1379
|
-
this.emitEvent({
|
|
1380
|
-
type: TeamMessageType.RunProgress,
|
|
1381
|
-
run: { ...run },
|
|
1382
|
-
message: "heartbeat",
|
|
1383
|
-
});
|
|
1408
|
+
this.recordRunProgress(run, "heartbeat");
|
|
1384
1409
|
}, 2000);
|
|
1385
1410
|
|
|
1386
1411
|
try {
|
|
@@ -1391,6 +1416,7 @@ export class AgentTeamsRuntime {
|
|
|
1391
1416
|
run.status = "completed";
|
|
1392
1417
|
run.result = result;
|
|
1393
1418
|
run.endedAt = new Date();
|
|
1419
|
+
run.currentActivity = "completed";
|
|
1394
1420
|
this.emitEvent({ type: TeamMessageType.RunCompleted, run: { ...run } });
|
|
1395
1421
|
} catch (error) {
|
|
1396
1422
|
const message =
|
|
@@ -1406,13 +1432,10 @@ export class AgentTeamsRuntime {
|
|
|
1406
1432
|
Date.now() + Math.min(30000, 1000 * 2 ** run.retryCount),
|
|
1407
1433
|
);
|
|
1408
1434
|
this.runQueue.push(run.id);
|
|
1409
|
-
this.
|
|
1410
|
-
type: TeamMessageType.RunProgress,
|
|
1411
|
-
run: { ...run },
|
|
1412
|
-
message: `retry_scheduled_${run.retryCount}`,
|
|
1413
|
-
});
|
|
1435
|
+
this.recordRunProgress(run, `retry_scheduled_${run.retryCount}`);
|
|
1414
1436
|
} else {
|
|
1415
1437
|
run.status = "failed";
|
|
1438
|
+
run.currentActivity = "failed";
|
|
1416
1439
|
this.emitEvent({ type: TeamMessageType.RunFailed, run: { ...run } });
|
|
1417
1440
|
}
|
|
1418
1441
|
} finally {
|
|
@@ -1481,6 +1504,7 @@ export class AgentTeamsRuntime {
|
|
|
1481
1504
|
run.status = "cancelled";
|
|
1482
1505
|
run.error = reason;
|
|
1483
1506
|
run.endedAt = new Date();
|
|
1507
|
+
run.currentActivity = "cancelled";
|
|
1484
1508
|
const queueIndex = this.runQueue.indexOf(runId);
|
|
1485
1509
|
if (queueIndex >= 0) {
|
|
1486
1510
|
this.runQueue.splice(queueIndex, 1);
|
|
@@ -1502,6 +1526,7 @@ export class AgentTeamsRuntime {
|
|
|
1502
1526
|
run.status = "interrupted";
|
|
1503
1527
|
run.error = reason;
|
|
1504
1528
|
run.endedAt = new Date();
|
|
1529
|
+
run.currentActivity = "interrupted";
|
|
1505
1530
|
interrupted.push({ ...run });
|
|
1506
1531
|
this.emitEvent({
|
|
1507
1532
|
type: TeamMessageType.RunInterrupted,
|
|
@@ -1764,6 +1789,8 @@ export class AgentTeamsRuntime {
|
|
|
1764
1789
|
}
|
|
1765
1790
|
|
|
1766
1791
|
private trackMeaningfulEvent(agentId: string, event: AgentEvent): void {
|
|
1792
|
+
this.recordRunActivityFromAgentEvent(agentId, event);
|
|
1793
|
+
|
|
1767
1794
|
if (event.type === "iteration_end" && event.hadToolCalls) {
|
|
1768
1795
|
this.recordProgressStep(
|
|
1769
1796
|
agentId,
|
|
@@ -1802,6 +1829,60 @@ export class AgentTeamsRuntime {
|
|
|
1802
1829
|
}
|
|
1803
1830
|
}
|
|
1804
1831
|
|
|
1832
|
+
private recordRunActivityFromAgentEvent(
|
|
1833
|
+
agentId: string,
|
|
1834
|
+
event: AgentEvent,
|
|
1835
|
+
): void {
|
|
1836
|
+
let activity: string | undefined;
|
|
1837
|
+
switch (event.type) {
|
|
1838
|
+
case "iteration_start":
|
|
1839
|
+
activity = `iteration_${event.iteration}_started`;
|
|
1840
|
+
break;
|
|
1841
|
+
case "content_start":
|
|
1842
|
+
if (event.contentType === "tool") {
|
|
1843
|
+
activity = `running_tool_${event.toolName ?? "unknown"}`;
|
|
1844
|
+
}
|
|
1845
|
+
break;
|
|
1846
|
+
case "content_end":
|
|
1847
|
+
if (event.contentType === "tool") {
|
|
1848
|
+
activity = event.error
|
|
1849
|
+
? `tool_${event.toolName ?? "unknown"}_error`
|
|
1850
|
+
: `finished_tool_${event.toolName ?? "unknown"}`;
|
|
1851
|
+
}
|
|
1852
|
+
break;
|
|
1853
|
+
case "done":
|
|
1854
|
+
activity = "finalizing_response";
|
|
1855
|
+
break;
|
|
1856
|
+
case "error":
|
|
1857
|
+
activity = "run_error";
|
|
1858
|
+
break;
|
|
1859
|
+
default:
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
if (!activity) {
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
for (const run of this.runs.values()) {
|
|
1866
|
+
if (run.agentId !== agentId || run.status !== "running") {
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
this.recordRunProgress(run, activity);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
private recordRunProgress(run: TeamRunRecord, message: string): void {
|
|
1874
|
+
const now = new Date();
|
|
1875
|
+
run.heartbeatAt = now;
|
|
1876
|
+
run.lastProgressAt = now;
|
|
1877
|
+
run.lastProgressMessage = message;
|
|
1878
|
+
run.currentActivity = message;
|
|
1879
|
+
this.emitEvent({
|
|
1880
|
+
type: TeamMessageType.RunProgress,
|
|
1881
|
+
run: { ...run },
|
|
1882
|
+
message,
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1805
1886
|
private recordProgressStep(
|
|
1806
1887
|
agentId: string,
|
|
1807
1888
|
summary: string,
|
|
@@ -169,4 +169,96 @@ describe("createSpawnAgentTool", () => {
|
|
|
169
169
|
}),
|
|
170
170
|
);
|
|
171
171
|
});
|
|
172
|
+
|
|
173
|
+
it("appends workspace metadata for cline sub-agents when missing", async () => {
|
|
174
|
+
const { createSpawnAgentTool } = await import("./spawn-agent-tool.js");
|
|
175
|
+
runMock.mockResolvedValue({
|
|
176
|
+
text: "ok",
|
|
177
|
+
iterations: 1,
|
|
178
|
+
finishReason: "completed",
|
|
179
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const workspaceMetadata = `# Workspace Configuration
|
|
183
|
+
{
|
|
184
|
+
"workspaces": {
|
|
185
|
+
"/repo/demo": {
|
|
186
|
+
"hint": "demo"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}`;
|
|
190
|
+
|
|
191
|
+
const tool = createSpawnAgentTool({
|
|
192
|
+
providerId: "cline",
|
|
193
|
+
modelId: "anthropic/claude-sonnet-4.6",
|
|
194
|
+
cwd: "/repo/demo",
|
|
195
|
+
clineWorkspaceMetadata: workspaceMetadata,
|
|
196
|
+
subAgentTools: [],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await tool.execute(
|
|
200
|
+
{
|
|
201
|
+
systemPrompt: "You are a specialist teammate.",
|
|
202
|
+
task: "Investigate module boundaries",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
agentId: "parent-4",
|
|
206
|
+
conversationId: "conv-parent",
|
|
207
|
+
iteration: 1,
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(agentConstructorSpy).toHaveBeenCalledWith(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
systemPrompt: expect.stringContaining(workspaceMetadata),
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("does not duplicate workspace metadata for cline sub-agents", async () => {
|
|
219
|
+
const { createSpawnAgentTool } = await import("./spawn-agent-tool.js");
|
|
220
|
+
runMock.mockResolvedValue({
|
|
221
|
+
text: "ok",
|
|
222
|
+
iterations: 1,
|
|
223
|
+
finishReason: "completed",
|
|
224
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const inputSystemPrompt = `You are a specialist teammate.
|
|
228
|
+
|
|
229
|
+
# Workspace Configuration
|
|
230
|
+
{
|
|
231
|
+
"workspaces": {
|
|
232
|
+
"/repo/demo": {
|
|
233
|
+
"hint": "demo"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}`;
|
|
237
|
+
|
|
238
|
+
const tool = createSpawnAgentTool({
|
|
239
|
+
providerId: "cline",
|
|
240
|
+
modelId: "anthropic/claude-sonnet-4.6",
|
|
241
|
+
cwd: "/repo/demo",
|
|
242
|
+
clineWorkspaceMetadata: "# Workspace Configuration\n{}",
|
|
243
|
+
subAgentTools: [],
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await tool.execute(
|
|
247
|
+
{
|
|
248
|
+
systemPrompt: inputSystemPrompt,
|
|
249
|
+
task: "Investigate module boundaries",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
agentId: "parent-5",
|
|
253
|
+
conversationId: "conv-parent",
|
|
254
|
+
iteration: 1,
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
expect(agentConstructorSpy).toHaveBeenCalledWith(
|
|
259
|
+
expect.objectContaining({
|
|
260
|
+
systemPrompt: inputSystemPrompt,
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
});
|
|
172
264
|
});
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
* Reusable spawn_agent tool for delegating tasks to sub-agents.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { basename, resolve } from "node:path";
|
|
5
6
|
import type { providers as LlmsProviders } from "@clinebot/llms";
|
|
6
|
-
import
|
|
7
|
-
Tool,
|
|
8
|
-
ToolApprovalRequest,
|
|
9
|
-
ToolApprovalResult,
|
|
10
|
-
ToolContext,
|
|
11
|
-
ToolPolicy,
|
|
7
|
+
import {
|
|
8
|
+
type Tool,
|
|
9
|
+
type ToolApprovalRequest,
|
|
10
|
+
type ToolApprovalResult,
|
|
11
|
+
type ToolContext,
|
|
12
|
+
type ToolPolicy,
|
|
13
|
+
zodToJsonSchema,
|
|
12
14
|
} from "@clinebot/shared";
|
|
15
|
+
import { z } from "zod";
|
|
13
16
|
import { Agent } from "../agent.js";
|
|
14
17
|
import { createTool } from "../tools/create.js";
|
|
15
18
|
import type {
|
|
@@ -21,11 +24,20 @@ import type {
|
|
|
21
24
|
HookErrorMode,
|
|
22
25
|
} from "../types.js";
|
|
23
26
|
|
|
24
|
-
export
|
|
25
|
-
systemPrompt:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
export const SpawnAgentInputSchema = z.object({
|
|
28
|
+
systemPrompt: z
|
|
29
|
+
.string()
|
|
30
|
+
.describe("System prompt defining the sub-agent's behavior"),
|
|
31
|
+
task: z.string().describe("Task for the sub-agent to complete"),
|
|
32
|
+
maxIterations: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.min(1)
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Max iterations for the sub-agent"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type SpawnAgentInput = z.infer<typeof SpawnAgentInputSchema>;
|
|
29
41
|
|
|
30
42
|
export interface SpawnAgentOutput {
|
|
31
43
|
text: string;
|
|
@@ -56,11 +68,13 @@ export interface SubAgentEndContext {
|
|
|
56
68
|
export interface SpawnAgentToolConfig {
|
|
57
69
|
providerId: string;
|
|
58
70
|
modelId: string;
|
|
71
|
+
cwd?: string;
|
|
59
72
|
apiKey?: string;
|
|
60
73
|
baseUrl?: string;
|
|
61
74
|
providerConfig?: LlmsProviders.ProviderConfig;
|
|
62
75
|
knownModels?: Record<string, LlmsProviders.ModelInfo>;
|
|
63
76
|
thinking?: boolean;
|
|
77
|
+
clineWorkspaceMetadata?: string;
|
|
64
78
|
defaultMaxIterations?: number;
|
|
65
79
|
subAgentTools?: Tool[];
|
|
66
80
|
createSubAgentTools?: (
|
|
@@ -106,6 +120,44 @@ export interface SpawnAgentToolConfig {
|
|
|
106
120
|
logger?: BasicLogger;
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
const WORKSPACE_CONFIGURATION_MARKER = "# Workspace Configuration";
|
|
124
|
+
|
|
125
|
+
function buildFallbackWorkspaceMetadata(cwd: string): string {
|
|
126
|
+
const rootPath = resolve(cwd);
|
|
127
|
+
return `# Workspace Configuration\n${JSON.stringify(
|
|
128
|
+
{
|
|
129
|
+
workspaces: {
|
|
130
|
+
[rootPath]: {
|
|
131
|
+
hint: basename(rootPath),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
null,
|
|
136
|
+
2,
|
|
137
|
+
)}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeSubAgentSystemPrompt(
|
|
141
|
+
inputSystemPrompt: string,
|
|
142
|
+
config: SpawnAgentToolConfig,
|
|
143
|
+
): string {
|
|
144
|
+
if (config.providerId !== "cline") {
|
|
145
|
+
return inputSystemPrompt;
|
|
146
|
+
}
|
|
147
|
+
const trimmedPrompt = inputSystemPrompt.trim();
|
|
148
|
+
if (trimmedPrompt.includes(WORKSPACE_CONFIGURATION_MARKER)) {
|
|
149
|
+
return trimmedPrompt;
|
|
150
|
+
}
|
|
151
|
+
const cwd = config.cwd?.trim() || process.cwd();
|
|
152
|
+
const workspaceMetadata =
|
|
153
|
+
config.clineWorkspaceMetadata?.trim() ||
|
|
154
|
+
buildFallbackWorkspaceMetadata(cwd);
|
|
155
|
+
if (!workspaceMetadata) {
|
|
156
|
+
return trimmedPrompt;
|
|
157
|
+
}
|
|
158
|
+
return `${trimmedPrompt}\n\n${workspaceMetadata}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
/**
|
|
110
162
|
* Create a spawn_agent tool that can run a delegated task with a focused sub-agent.
|
|
111
163
|
*/
|
|
@@ -115,25 +167,7 @@ export function createSpawnAgentTool(
|
|
|
115
167
|
return createTool<SpawnAgentInput, SpawnAgentOutput>({
|
|
116
168
|
name: "spawn_agent",
|
|
117
169
|
description: `Spawn a sub-agent with a custom system prompt for specialized tasks. Use when delegating work that benefits from focused expertise.`,
|
|
118
|
-
inputSchema:
|
|
119
|
-
type: "object",
|
|
120
|
-
properties: {
|
|
121
|
-
systemPrompt: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: "System prompt defining the sub-agent's behavior",
|
|
124
|
-
},
|
|
125
|
-
task: {
|
|
126
|
-
type: "string",
|
|
127
|
-
description: "Task for the sub-agent to complete",
|
|
128
|
-
},
|
|
129
|
-
maxIterations: {
|
|
130
|
-
type: "integer",
|
|
131
|
-
description: "Max iterations for the sub-agent",
|
|
132
|
-
minimum: 1,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
required: ["systemPrompt", "task"],
|
|
136
|
-
},
|
|
170
|
+
inputSchema: zodToJsonSchema(SpawnAgentInputSchema),
|
|
137
171
|
execute: async (input, context) => {
|
|
138
172
|
const tools = config.createSubAgentTools
|
|
139
173
|
? await config.createSubAgentTools(input, context)
|
|
@@ -147,7 +181,7 @@ export function createSpawnAgentTool(
|
|
|
147
181
|
providerConfig: config.providerConfig,
|
|
148
182
|
knownModels: config.knownModels,
|
|
149
183
|
thinking: config.thinking,
|
|
150
|
-
systemPrompt: input.systemPrompt,
|
|
184
|
+
systemPrompt: normalizeSubAgentSystemPrompt(input.systemPrompt, config),
|
|
151
185
|
tools,
|
|
152
186
|
maxIterations: input.maxIterations ?? config.defaultMaxIterations,
|
|
153
187
|
parentAgentId: context.agentId,
|
|
@@ -445,4 +445,22 @@ describe("createAgentTeamsTools runtime behavior", () => {
|
|
|
445
445
|
"One or more runs did not complete successfully: run_bad:failed(Auth expired)",
|
|
446
446
|
);
|
|
447
447
|
});
|
|
448
|
+
|
|
449
|
+
it("sets long timeout for team await tools", () => {
|
|
450
|
+
const runtime = new AgentTeamsRuntime({ teamName: "test-team" });
|
|
451
|
+
const tools = createAgentTeamsTools({
|
|
452
|
+
runtime,
|
|
453
|
+
requesterId: "lead",
|
|
454
|
+
teammateRuntime: {
|
|
455
|
+
providerId: "anthropic",
|
|
456
|
+
modelId: "claude-sonnet-4-5-20250929",
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
const awaitRun = tools.find((tool) => tool.name === "team_await_run");
|
|
460
|
+
const awaitAllRuns = tools.find(
|
|
461
|
+
(tool) => tool.name === "team_await_all_runs",
|
|
462
|
+
);
|
|
463
|
+
expect(awaitRun?.timeoutMs).toBe(60 * 60 * 1000);
|
|
464
|
+
expect(awaitAllRuns?.timeoutMs).toBe(60 * 60 * 1000);
|
|
465
|
+
});
|
|
448
466
|
});
|
package/src/teams/team-tools.ts
CHANGED
|
@@ -40,7 +40,10 @@ const TeamStatusInputSchema = z.object({});
|
|
|
40
40
|
const TeamCreateTaskInputSchema = z.object({
|
|
41
41
|
title: z.string().min(1).describe("Task title"),
|
|
42
42
|
description: z.string().min(1).describe("Task details"),
|
|
43
|
-
dependsOn: z
|
|
43
|
+
dependsOn: z
|
|
44
|
+
.array(z.string().describe("Dependency task ID"))
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Array of the dependency task IDs"),
|
|
44
47
|
assignee: z.string().min(1).optional().describe("Optional assignee"),
|
|
45
48
|
});
|
|
46
49
|
|
|
@@ -154,6 +157,7 @@ const DEFAULT_OUTCOME_REQUIRED_SECTIONS = [
|
|
|
154
157
|
"boundary_analysis",
|
|
155
158
|
"interface_proposal",
|
|
156
159
|
];
|
|
160
|
+
const TEAM_AWAIT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
157
161
|
|
|
158
162
|
const TeamCreateOutcomeInputSchema = z.object({
|
|
159
163
|
title: z.string().describe("Outcome title"),
|
|
@@ -598,7 +602,7 @@ export function createAgentTeamsTools(
|
|
|
598
602
|
createTool<TeamListRunsInput, ReturnType<AgentTeamsRuntime["listRuns"]>>({
|
|
599
603
|
name: "team_list_runs",
|
|
600
604
|
description:
|
|
601
|
-
"List teammate runs started with team_run_task in async mode.",
|
|
605
|
+
"List teammate runs started with team_run_task in async mode, including live activity/progress fields when available.",
|
|
602
606
|
inputSchema: zodToJsonSchema(TeamListRunsInputSchema),
|
|
603
607
|
execute: async (input) =>
|
|
604
608
|
options.runtime.listRuns(
|
|
@@ -613,8 +617,10 @@ export function createAgentTeamsTools(
|
|
|
613
617
|
Awaited<ReturnType<AgentTeamsRuntime["awaitRun"]>>
|
|
614
618
|
>({
|
|
615
619
|
name: "team_await_run",
|
|
616
|
-
description:
|
|
620
|
+
description:
|
|
621
|
+
"Wait for one async run by runId. Uses a long timeout for legitimate teammate work.",
|
|
617
622
|
inputSchema: zodToJsonSchema(TeamAwaitRunInputSchema),
|
|
623
|
+
timeoutMs: TEAM_AWAIT_TIMEOUT_MS,
|
|
618
624
|
execute: async (input) => {
|
|
619
625
|
const validatedInput = validateWithZod(TeamAwaitRunInputSchema, input);
|
|
620
626
|
const run = await options.runtime.awaitRun(validatedInput.runId);
|
|
@@ -644,8 +650,10 @@ export function createAgentTeamsTools(
|
|
|
644
650
|
Awaited<ReturnType<AgentTeamsRuntime["awaitAllRuns"]>>
|
|
645
651
|
>({
|
|
646
652
|
name: "team_await_all_runs",
|
|
647
|
-
description:
|
|
653
|
+
description:
|
|
654
|
+
"Wait for all active async runs to complete. Uses a long timeout for legitimate teammate work.",
|
|
648
655
|
inputSchema: zodToJsonSchema(TeamAwaitAllRunsInputSchema),
|
|
656
|
+
timeoutMs: TEAM_AWAIT_TIMEOUT_MS,
|
|
649
657
|
execute: async (input) => {
|
|
650
658
|
validateWithZod(TeamAwaitAllRunsInputSchema, input);
|
|
651
659
|
const runs = await options.runtime.awaitAllRuns();
|
package/src/types.ts
CHANGED
|
@@ -126,6 +126,8 @@ export interface AgentDoneEvent {
|
|
|
126
126
|
text: string;
|
|
127
127
|
/** Total number of iterations */
|
|
128
128
|
iterations: number;
|
|
129
|
+
/** Aggregated usage information */
|
|
130
|
+
usage?: AgentUsage;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
export interface AgentErrorEvent {
|
|
@@ -138,6 +140,30 @@ export interface AgentErrorEvent {
|
|
|
138
140
|
iteration: number;
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
export interface ConsecutiveMistakeLimitContext {
|
|
144
|
+
iteration: number;
|
|
145
|
+
consecutiveMistakes: number;
|
|
146
|
+
maxConsecutiveMistakes: number;
|
|
147
|
+
reason: "api_error" | "invalid_tool_call" | "tool_execution_failed";
|
|
148
|
+
details?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type ConsecutiveMistakeLimitDecision =
|
|
152
|
+
| {
|
|
153
|
+
action: "continue";
|
|
154
|
+
/**
|
|
155
|
+
* Optional guidance appended as a user message before continuing.
|
|
156
|
+
*/
|
|
157
|
+
guidance?: string;
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
action: "stop";
|
|
161
|
+
/**
|
|
162
|
+
* Optional reason surfaced when stopping due to the limit.
|
|
163
|
+
*/
|
|
164
|
+
reason?: string;
|
|
165
|
+
};
|
|
166
|
+
|
|
141
167
|
// =============================================================================
|
|
142
168
|
// Hooks
|
|
143
169
|
// =============================================================================
|
|
@@ -658,9 +684,9 @@ export const AgentResultSchema = z.object({
|
|
|
658
684
|
/**
|
|
659
685
|
* Reasoning effort level for capable models
|
|
660
686
|
*/
|
|
661
|
-
export type ReasoningEffort = "low" | "medium" | "high";
|
|
687
|
+
export type ReasoningEffort = "low" | "medium" | "high" | "xhigh";
|
|
662
688
|
|
|
663
|
-
export const ReasoningEffortSchema = z.enum(["low", "medium", "high"]);
|
|
689
|
+
export const ReasoningEffortSchema = z.enum(["low", "medium", "high", "xhigh"]);
|
|
664
690
|
|
|
665
691
|
/**
|
|
666
692
|
* Configuration for creating an Agent
|
|
@@ -729,6 +755,13 @@ export interface AgentConfig {
|
|
|
729
755
|
* @default 120000 (2 minutes)
|
|
730
756
|
*/
|
|
731
757
|
apiTimeoutMs?: number;
|
|
758
|
+
/**
|
|
759
|
+
* Maximum consecutive internal mistakes before escalation.
|
|
760
|
+
* Mistakes include API turn failures, invalid/missing tool-call arguments,
|
|
761
|
+
* and iterations where every executed tool call fails.
|
|
762
|
+
* @default 3
|
|
763
|
+
*/
|
|
764
|
+
maxConsecutiveMistakes?: number;
|
|
732
765
|
/**
|
|
733
766
|
* Optional runtime file-content loader used when user files are attached.
|
|
734
767
|
* When omitted, attached files will be represented as loader errors.
|
|
@@ -797,6 +830,14 @@ export interface AgentConfig {
|
|
|
797
830
|
requestToolApproval?: (
|
|
798
831
|
request: ToolApprovalRequest,
|
|
799
832
|
) => Promise<ToolApprovalResult> | ToolApprovalResult;
|
|
833
|
+
/**
|
|
834
|
+
* Optional callback invoked when consecutive mistakes reach maxConsecutiveMistakes.
|
|
835
|
+
*/
|
|
836
|
+
onConsecutiveMistakeLimitReached?: (
|
|
837
|
+
context: ConsecutiveMistakeLimitContext,
|
|
838
|
+
) =>
|
|
839
|
+
| Promise<ConsecutiveMistakeLimitDecision>
|
|
840
|
+
| ConsecutiveMistakeLimitDecision;
|
|
800
841
|
/**
|
|
801
842
|
* Optional logger for tracing agent loop lifecycle and recoverable failures.
|
|
802
843
|
*/
|
|
@@ -845,6 +886,7 @@ export const AgentConfigSchema = z.object({
|
|
|
845
886
|
maxParallelToolCalls: z.number().int().positive().default(8),
|
|
846
887
|
maxTokensPerTurn: z.number().positive().optional(),
|
|
847
888
|
apiTimeoutMs: z.number().positive().default(120000),
|
|
889
|
+
maxConsecutiveMistakes: z.number().int().positive().default(3),
|
|
848
890
|
userFileContentLoader: z
|
|
849
891
|
.function()
|
|
850
892
|
.input([z.string()])
|
|
@@ -911,6 +953,46 @@ export const AgentConfigSchema = z.object({
|
|
|
911
953
|
]),
|
|
912
954
|
)
|
|
913
955
|
.optional(),
|
|
956
|
+
onConsecutiveMistakeLimitReached: z
|
|
957
|
+
.function()
|
|
958
|
+
.input([
|
|
959
|
+
z.object({
|
|
960
|
+
iteration: z.number().int().positive(),
|
|
961
|
+
consecutiveMistakes: z.number().int().positive(),
|
|
962
|
+
maxConsecutiveMistakes: z.number().int().positive(),
|
|
963
|
+
reason: z.enum([
|
|
964
|
+
"api_error",
|
|
965
|
+
"invalid_tool_call",
|
|
966
|
+
"tool_execution_failed",
|
|
967
|
+
]),
|
|
968
|
+
details: z.string().optional(),
|
|
969
|
+
}),
|
|
970
|
+
])
|
|
971
|
+
.output(
|
|
972
|
+
z.union([
|
|
973
|
+
z.object({
|
|
974
|
+
action: z.literal("continue"),
|
|
975
|
+
guidance: z.string().optional(),
|
|
976
|
+
}),
|
|
977
|
+
z.object({
|
|
978
|
+
action: z.literal("stop"),
|
|
979
|
+
reason: z.string().optional(),
|
|
980
|
+
}),
|
|
981
|
+
z.promise(
|
|
982
|
+
z.union([
|
|
983
|
+
z.object({
|
|
984
|
+
action: z.literal("continue"),
|
|
985
|
+
guidance: z.string().optional(),
|
|
986
|
+
}),
|
|
987
|
+
z.object({
|
|
988
|
+
action: z.literal("stop"),
|
|
989
|
+
reason: z.string().optional(),
|
|
990
|
+
}),
|
|
991
|
+
]),
|
|
992
|
+
),
|
|
993
|
+
]),
|
|
994
|
+
)
|
|
995
|
+
.optional(),
|
|
914
996
|
logger: z.custom<BasicLogger>().optional(),
|
|
915
997
|
|
|
916
998
|
// Cancellation
|
|
@@ -942,6 +1024,12 @@ export interface ProcessedTurn {
|
|
|
942
1024
|
reasoning?: string;
|
|
943
1025
|
/** Tool calls requested by the model */
|
|
944
1026
|
toolCalls: PendingToolCall[];
|
|
1027
|
+
/** Model-emitted tool calls that were invalid or missing required fields */
|
|
1028
|
+
invalidToolCalls: Array<{
|
|
1029
|
+
id: string;
|
|
1030
|
+
name?: string;
|
|
1031
|
+
reason: "missing_name" | "missing_arguments" | "invalid_arguments";
|
|
1032
|
+
}>;
|
|
945
1033
|
/** Token usage for this turn */
|
|
946
1034
|
usage: {
|
|
947
1035
|
inputTokens: number;
|