gauss-ai 4.0.0
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/CHANGELOG.md +489 -0
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/dist/a2a/index.cjs +7 -0
- package/dist/a2a/index.cjs.map +1 -0
- package/dist/a2a/index.d.cts +30 -0
- package/dist/a2a/index.d.ts +30 -0
- package/dist/a2a/index.js +7 -0
- package/dist/a2a/index.js.map +1 -0
- package/dist/agent-UIQDSYCE.js +16 -0
- package/dist/agent-UIQDSYCE.js.map +1 -0
- package/dist/agent-builder-8W3mBR-N.d.ts +1075 -0
- package/dist/agent-builder-GEMYdb1p.d.cts +1075 -0
- package/dist/agent-graph-AMQYAWNF.js +1422 -0
- package/dist/agent-graph-AMQYAWNF.js.map +1 -0
- package/dist/ai-sdk-mcp.adapter-SEN6KHNU.js +124 -0
- package/dist/ai-sdk-mcp.adapter-SEN6KHNU.js.map +1 -0
- package/dist/browser/index.js +10 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/bun-runtime.adapter-MQDAJLQM.js +8 -0
- package/dist/bun-runtime.adapter-MQDAJLQM.js.map +1 -0
- package/dist/bun-runtime.adapter-XKOUXVAK.cjs +8 -0
- package/dist/bun-runtime.adapter-XKOUXVAK.cjs.map +1 -0
- package/dist/chat-A3XMRPJL.js +129 -0
- package/dist/chat-A3XMRPJL.js.map +1 -0
- package/dist/chunk-2ZRU47NC.js +91 -0
- package/dist/chunk-2ZRU47NC.js.map +1 -0
- package/dist/chunk-3LD3JTH4.cjs +18 -0
- package/dist/chunk-3LD3JTH4.cjs.map +1 -0
- package/dist/chunk-5FE5TG2W.cjs +16 -0
- package/dist/chunk-5FE5TG2W.cjs.map +1 -0
- package/dist/chunk-6XF673YC.cjs +436 -0
- package/dist/chunk-6XF673YC.cjs.map +1 -0
- package/dist/chunk-7CKWZJNS.js +230 -0
- package/dist/chunk-7CKWZJNS.js.map +1 -0
- package/dist/chunk-BI2G665F.js +4588 -0
- package/dist/chunk-BI2G665F.js.map +1 -0
- package/dist/chunk-C5NLWJS2.js +139 -0
- package/dist/chunk-C5NLWJS2.js.map +1 -0
- package/dist/chunk-CJZ66SU3.cjs +4321 -0
- package/dist/chunk-CJZ66SU3.cjs.map +1 -0
- package/dist/chunk-DAMT2CXW.cjs +91 -0
- package/dist/chunk-DAMT2CXW.cjs.map +1 -0
- package/dist/chunk-E7WG3MO5.js +18 -0
- package/dist/chunk-E7WG3MO5.js.map +1 -0
- package/dist/chunk-EFDM6R4J.js +99 -0
- package/dist/chunk-EFDM6R4J.js.map +1 -0
- package/dist/chunk-F7WIPPEO.js +256 -0
- package/dist/chunk-F7WIPPEO.js.map +1 -0
- package/dist/chunk-FAYDE67N.js +6927 -0
- package/dist/chunk-FAYDE67N.js.map +1 -0
- package/dist/chunk-GAE2KKCM.js +21 -0
- package/dist/chunk-GAE2KKCM.js.map +1 -0
- package/dist/chunk-INLNGRXM.cjs +130 -0
- package/dist/chunk-INLNGRXM.cjs.map +1 -0
- package/dist/chunk-JKXKXB5O.js +130 -0
- package/dist/chunk-JKXKXB5O.js.map +1 -0
- package/dist/chunk-K6SAETGP.js +375 -0
- package/dist/chunk-K6SAETGP.js.map +1 -0
- package/dist/chunk-KEASLAYR.js +157 -0
- package/dist/chunk-KEASLAYR.js.map +1 -0
- package/dist/chunk-KKJVNM6O.js +436 -0
- package/dist/chunk-KKJVNM6O.js.map +1 -0
- package/dist/chunk-KYIMVRIM.js +16 -0
- package/dist/chunk-KYIMVRIM.js.map +1 -0
- package/dist/chunk-MB7NXIZD.js +4321 -0
- package/dist/chunk-MB7NXIZD.js.map +1 -0
- package/dist/chunk-MHHDXPGE.js +209 -0
- package/dist/chunk-MHHDXPGE.js.map +1 -0
- package/dist/chunk-NE6JJA5W.js +401 -0
- package/dist/chunk-NE6JJA5W.js.map +1 -0
- package/dist/chunk-PF46XZBF.cjs +6927 -0
- package/dist/chunk-PF46XZBF.cjs.map +1 -0
- package/dist/chunk-PSJIAGDE.cjs +375 -0
- package/dist/chunk-PSJIAGDE.cjs.map +1 -0
- package/dist/chunk-PWOQDXNQ.js +16 -0
- package/dist/chunk-PWOQDXNQ.js.map +1 -0
- package/dist/chunk-QYOMQBBZ.cjs +230 -0
- package/dist/chunk-QYOMQBBZ.cjs.map +1 -0
- package/dist/chunk-UDFXLC4J.cjs +16 -0
- package/dist/chunk-UDFXLC4J.cjs.map +1 -0
- package/dist/chunk-UO4NGXRT.cjs +259 -0
- package/dist/chunk-UO4NGXRT.cjs.map +1 -0
- package/dist/chunk-UPFDFLEW.js +40 -0
- package/dist/chunk-UPFDFLEW.js.map +1 -0
- package/dist/chunk-V55JSQS7.cjs +16 -0
- package/dist/chunk-V55JSQS7.cjs.map +1 -0
- package/dist/chunk-VJADHXZL.cjs +16 -0
- package/dist/chunk-VJADHXZL.cjs.map +1 -0
- package/dist/chunk-VRWD7LCI.js +59 -0
- package/dist/chunk-VRWD7LCI.js.map +1 -0
- package/dist/chunk-WKKQ443C.js +487 -0
- package/dist/chunk-WKKQ443C.js.map +1 -0
- package/dist/chunk-X2GHUHAF.js +436 -0
- package/dist/chunk-X2GHUHAF.js.map +1 -0
- package/dist/chunk-XLGW3XNI.cjs +256 -0
- package/dist/chunk-XLGW3XNI.cjs.map +1 -0
- package/dist/chunk-ZFJKX4DP.js +16 -0
- package/dist/chunk-ZFJKX4DP.js.map +1 -0
- package/dist/chunk-ZM2OEWM2.js +259 -0
- package/dist/chunk-ZM2OEWM2.js.map +1 -0
- package/dist/chunk-ZNAIP2XV.js +16 -0
- package/dist/chunk-ZNAIP2XV.js.map +1 -0
- package/dist/chunk-ZYFAZYSL.js +42 -0
- package/dist/chunk-ZYFAZYSL.js.map +1 -0
- package/dist/cli/index.js +421 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config-4MHT6TQW.js +153 -0
- package/dist/config-4MHT6TQW.js.map +1 -0
- package/dist/config-REERQFK4.cjs +153 -0
- package/dist/config-REERQFK4.cjs.map +1 -0
- package/dist/cost-tracker-JLOU7IZJ.js +7 -0
- package/dist/cost-tracker-JLOU7IZJ.js.map +1 -0
- package/dist/demo-C52GMSYH.js +188 -0
- package/dist/demo-C52GMSYH.js.map +1 -0
- package/dist/deno/index.js +306 -0
- package/dist/deno/index.js.map +1 -0
- package/dist/deno-runtime.adapter-F744HY7K.js +8 -0
- package/dist/deno-runtime.adapter-F744HY7K.js.map +1 -0
- package/dist/deno-runtime.adapter-RFEVNSCV.cjs +8 -0
- package/dist/deno-runtime.adapter-RFEVNSCV.cjs.map +1 -0
- package/dist/dev-D7DDVDA4.js +218 -0
- package/dist/dev-D7DDVDA4.js.map +1 -0
- package/dist/edge/index.js +10 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/edge-runtime.adapter-UQCW2F7X.js +8 -0
- package/dist/edge-runtime.adapter-UQCW2F7X.js.map +1 -0
- package/dist/edge-runtime.adapter-YED6F3AY.cjs +8 -0
- package/dist/edge-runtime.adapter-YED6F3AY.cjs.map +1 -0
- package/dist/graph-MGFAQZ5W.js +50 -0
- package/dist/graph-MGFAQZ5W.js.map +1 -0
- package/dist/graph-visualization-HBSVQXJK.js +9 -0
- package/dist/graph-visualization-HBSVQXJK.js.map +1 -0
- package/dist/index-BRgqNnh3.d.cts +982 -0
- package/dist/index-CZxpYUxZ.d.ts +982 -0
- package/dist/index.cjs +14789 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10275 -0
- package/dist/index.d.ts +10275 -0
- package/dist/index.js +14789 -0
- package/dist/index.js.map +1 -0
- package/dist/init-CFWXTQ35.js +133 -0
- package/dist/init-CFWXTQ35.js.map +1 -0
- package/dist/llm-VWO4MC7J.cjs +17 -0
- package/dist/llm-VWO4MC7J.cjs.map +1 -0
- package/dist/llm-XLXVSPBI.js +17 -0
- package/dist/llm-XLXVSPBI.js.map +1 -0
- package/dist/logging-WRAK5ZXT.js +33 -0
- package/dist/logging-WRAK5ZXT.js.map +1 -0
- package/dist/metrics-FAHZVVD4.js +47 -0
- package/dist/metrics-FAHZVVD4.js.map +1 -0
- package/dist/node/index.cjs +280 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +51 -0
- package/dist/node/index.d.ts +51 -0
- package/dist/node/index.js +280 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node-runtime.adapter-5L7PJ6W2.js +8 -0
- package/dist/node-runtime.adapter-5L7PJ6W2.js.map +1 -0
- package/dist/node-runtime.adapter-CCRZVGHB.cjs +8 -0
- package/dist/node-runtime.adapter-CCRZVGHB.cjs.map +1 -0
- package/dist/persist-usage-WTBTCWEF.js +7 -0
- package/dist/persist-usage-WTBTCWEF.js.map +1 -0
- package/dist/plugin-RCPBWUUA.js +207 -0
- package/dist/plugin-RCPBWUUA.js.map +1 -0
- package/dist/plugins/index.cjs +75 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +8 -0
- package/dist/plugins/index.d.ts +8 -0
- package/dist/plugins/index.js +75 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins-L4ING3CX.js +4625 -0
- package/dist/plugins-L4ING3CX.js.map +1 -0
- package/dist/providers/index.cjs +189 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +168 -0
- package/dist/providers/index.d.ts +168 -0
- package/dist/providers/index.js +189 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers-3RNQ5CKZ.js +59 -0
- package/dist/providers-3RNQ5CKZ.js.map +1 -0
- package/dist/providers-66GPXUGQ.cjs +59 -0
- package/dist/providers-66GPXUGQ.cjs.map +1 -0
- package/dist/repl-K6QN4I2S.js +678 -0
- package/dist/repl-K6QN4I2S.js.map +1 -0
- package/dist/rest/index.cjs +17 -0
- package/dist/rest/index.cjs.map +1 -0
- package/dist/rest/index.d.cts +102 -0
- package/dist/rest/index.d.ts +102 -0
- package/dist/rest/index.js +17 -0
- package/dist/rest/index.js.map +1 -0
- package/dist/runtime-deno.js +15 -0
- package/dist/runtime-deno.js.map +1 -0
- package/dist/runtime-edge.js +15 -0
- package/dist/runtime-edge.js.map +1 -0
- package/dist/runtime-node.js +15 -0
- package/dist/runtime-node.js.map +1 -0
- package/dist/scraping/index.cjs +11 -0
- package/dist/scraping/index.cjs.map +1 -0
- package/dist/scraping/index.d.cts +17 -0
- package/dist/scraping/index.d.ts +17 -0
- package/dist/scraping/index.js +11 -0
- package/dist/scraping/index.js.map +1 -0
- package/dist/semantic-scraping.port-CZWUea88.d.cts +54 -0
- package/dist/semantic-scraping.port-CZWUea88.d.ts +54 -0
- package/dist/server/index.js +166 -0
- package/dist/server/index.js.map +1 -0
- package/dist/testing/index.cjs +25 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +63 -0
- package/dist/testing/index.d.ts +63 -0
- package/dist/testing/index.js +25 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/token-counter.port-CRgxZZGe.d.ts +334 -0
- package/dist/token-counter.port-D7BHMCRR.d.cts +334 -0
- package/dist/tools-BZM33OBZ.js +10 -0
- package/dist/tools-BZM33OBZ.js.map +1 -0
- package/dist/tracing-XA3TEWP4.js +48 -0
- package/dist/tracing-XA3TEWP4.js.map +1 -0
- package/dist/types-CVsP7gFI.d.cts +235 -0
- package/dist/types-CVsP7gFI.d.ts +235 -0
- package/dist/virtual-fs.adapter-BBLS-3AY.d.ts +26 -0
- package/dist/virtual-fs.adapter-nb0CTYOj.d.cts +26 -0
- package/dist/workflow/index.cjs +9 -0
- package/dist/workflow/index.cjs.map +1 -0
- package/dist/workflow/index.d.cts +62 -0
- package/dist/workflow/index.d.ts +62 -0
- package/dist/workflow/index.js +9 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow.port-BaCttxrw.d.cts +153 -0
- package/dist/workflow.port-BaCttxrw.d.ts +153 -0
- package/package.json +230 -0
|
@@ -0,0 +1,4588 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CircuitBreaker,
|
|
3
|
+
DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
4
|
+
DEFAULT_TOOL_CACHE_CONFIG,
|
|
5
|
+
PluginManager,
|
|
6
|
+
ToolCache
|
|
7
|
+
} from "./chunk-X2GHUHAF.js";
|
|
8
|
+
import {
|
|
9
|
+
generateText,
|
|
10
|
+
stepCountIs,
|
|
11
|
+
streamText,
|
|
12
|
+
tool
|
|
13
|
+
} from "./chunk-K6SAETGP.js";
|
|
14
|
+
|
|
15
|
+
// src/adapters/runtime/detect-runtime.ts
|
|
16
|
+
function detectRuntimeName() {
|
|
17
|
+
if (typeof globalThis !== "undefined") {
|
|
18
|
+
if ("Deno" in globalThis) return "deno";
|
|
19
|
+
if ("Bun" in globalThis) return "bun";
|
|
20
|
+
if (typeof globalThis.process?.versions?.node === "string") return "node";
|
|
21
|
+
if (typeof globalThis.fetch === "function" && !("process" in globalThis)) return "edge";
|
|
22
|
+
}
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
async function createRuntimeAdapterAsync(name) {
|
|
26
|
+
const resolved = name ?? detectRuntimeName();
|
|
27
|
+
switch (resolved) {
|
|
28
|
+
case "deno": {
|
|
29
|
+
const { DenoRuntimeAdapter: D } = await import("./deno-runtime.adapter-F744HY7K.js");
|
|
30
|
+
return new D();
|
|
31
|
+
}
|
|
32
|
+
case "bun": {
|
|
33
|
+
const { BunRuntimeAdapter: B } = await import("./bun-runtime.adapter-MQDAJLQM.js");
|
|
34
|
+
return new B();
|
|
35
|
+
}
|
|
36
|
+
case "edge": {
|
|
37
|
+
const { EdgeRuntimeAdapter: E } = await import("./edge-runtime.adapter-UQCW2F7X.js");
|
|
38
|
+
return new E();
|
|
39
|
+
}
|
|
40
|
+
case "node":
|
|
41
|
+
case "unknown":
|
|
42
|
+
default: {
|
|
43
|
+
const { NodeRuntimeAdapter: N } = await import("./node-runtime.adapter-5L7PJ6W2.js");
|
|
44
|
+
return new N();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/agent/event-bus.ts
|
|
50
|
+
var EventBus = class _EventBus {
|
|
51
|
+
sessionId;
|
|
52
|
+
listeners = /* @__PURE__ */ new Map();
|
|
53
|
+
maxListenersPerEvent;
|
|
54
|
+
maxBubblesPerSecond;
|
|
55
|
+
// Hierarchical relationships
|
|
56
|
+
parent = null;
|
|
57
|
+
children = /* @__PURE__ */ new Map();
|
|
58
|
+
namespace = null;
|
|
59
|
+
// Namespaced listeners: namespace -> Set<handler>
|
|
60
|
+
namespacedListeners = /* @__PURE__ */ new Map();
|
|
61
|
+
// Anti-storm: track bubble counts per child per second window
|
|
62
|
+
bubbleCounts = /* @__PURE__ */ new Map();
|
|
63
|
+
constructor(sessionId, options) {
|
|
64
|
+
this.sessionId = sessionId;
|
|
65
|
+
this.maxListenersPerEvent = options?.maxListenersPerEvent ?? 100;
|
|
66
|
+
this.maxBubblesPerSecond = options?.maxBubblesPerSecond ?? 100;
|
|
67
|
+
}
|
|
68
|
+
/** Subscribe to a specific event type or '*' for all events. Returns unsubscribe fn. */
|
|
69
|
+
on(eventType, handler) {
|
|
70
|
+
let set = this.listeners.get(eventType);
|
|
71
|
+
if (!set) {
|
|
72
|
+
set = /* @__PURE__ */ new Set();
|
|
73
|
+
this.listeners.set(eventType, set);
|
|
74
|
+
}
|
|
75
|
+
if (set.size >= this.maxListenersPerEvent) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`EventBus: max listeners (${this.maxListenersPerEvent}) reached for "${String(eventType)}"`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
set.add(handler);
|
|
81
|
+
return () => this.off(eventType, handler);
|
|
82
|
+
}
|
|
83
|
+
/** Unsubscribe a handler from a specific event type. */
|
|
84
|
+
off(eventType, handler) {
|
|
85
|
+
const set = this.listeners.get(eventType);
|
|
86
|
+
if (!set) return;
|
|
87
|
+
set.delete(handler);
|
|
88
|
+
if (set.size === 0) this.listeners.delete(eventType);
|
|
89
|
+
}
|
|
90
|
+
/** Emit an event, auto-filling timestamp and sessionId. Also bubbles to parent. */
|
|
91
|
+
emit(type, data) {
|
|
92
|
+
const event = {
|
|
93
|
+
type,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
sessionId: this.sessionId,
|
|
96
|
+
data
|
|
97
|
+
};
|
|
98
|
+
this.dispatchLocal(event);
|
|
99
|
+
this.bubbleToParent(event);
|
|
100
|
+
}
|
|
101
|
+
/** Remove all listeners for a given event type, or all listeners if no type specified. */
|
|
102
|
+
removeAllListeners(eventType) {
|
|
103
|
+
if (eventType) {
|
|
104
|
+
this.listeners.delete(eventType);
|
|
105
|
+
} else {
|
|
106
|
+
this.listeners.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Return the number of listeners for a given event type. */
|
|
110
|
+
listenerCount(eventType) {
|
|
111
|
+
return this.listeners.get(eventType)?.size ?? 0;
|
|
112
|
+
}
|
|
113
|
+
// ===========================================================================
|
|
114
|
+
// Hierarchical API
|
|
115
|
+
// ===========================================================================
|
|
116
|
+
/** Create a child bus with a given namespace. */
|
|
117
|
+
createChild(childNamespace) {
|
|
118
|
+
if (this.children.has(childNamespace)) {
|
|
119
|
+
throw new Error(`EventBus: child namespace "${childNamespace}" already exists`);
|
|
120
|
+
}
|
|
121
|
+
const child = new _EventBus(this.sessionId, {
|
|
122
|
+
maxListenersPerEvent: this.maxListenersPerEvent,
|
|
123
|
+
maxBubblesPerSecond: this.maxBubblesPerSecond
|
|
124
|
+
});
|
|
125
|
+
child.parent = this;
|
|
126
|
+
child.namespace = childNamespace;
|
|
127
|
+
this.children.set(childNamespace, child);
|
|
128
|
+
return child;
|
|
129
|
+
}
|
|
130
|
+
/** Broadcast an event from this bus to ALL children recursively (capturing phase). */
|
|
131
|
+
broadcast(type, data) {
|
|
132
|
+
const event = {
|
|
133
|
+
type,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
sessionId: this.sessionId,
|
|
136
|
+
data
|
|
137
|
+
};
|
|
138
|
+
for (const child of this.children.values()) {
|
|
139
|
+
child.dispatchLocal(event);
|
|
140
|
+
child.broadcast(type, data);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Subscribe to events bubbled from a specific child namespace. */
|
|
144
|
+
onNamespaced(childNamespace, handler) {
|
|
145
|
+
let set = this.namespacedListeners.get(childNamespace);
|
|
146
|
+
if (!set) {
|
|
147
|
+
set = /* @__PURE__ */ new Set();
|
|
148
|
+
this.namespacedListeners.set(childNamespace, set);
|
|
149
|
+
}
|
|
150
|
+
set.add(handler);
|
|
151
|
+
return () => {
|
|
152
|
+
set.delete(handler);
|
|
153
|
+
if (set.size === 0) this.namespacedListeners.delete(childNamespace);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** Get all child buses. */
|
|
157
|
+
getChildren() {
|
|
158
|
+
return new Map(this.children);
|
|
159
|
+
}
|
|
160
|
+
/** Get the parent bus, or null for root. */
|
|
161
|
+
getParent() {
|
|
162
|
+
return this.parent;
|
|
163
|
+
}
|
|
164
|
+
// ===========================================================================
|
|
165
|
+
// Internal helpers
|
|
166
|
+
// ===========================================================================
|
|
167
|
+
/** Dispatch an event to local listeners (specific + wildcard). */
|
|
168
|
+
dispatchLocal(event) {
|
|
169
|
+
const specific = this.listeners.get(event.type);
|
|
170
|
+
if (specific) {
|
|
171
|
+
for (const handler of specific) handler(event);
|
|
172
|
+
}
|
|
173
|
+
const wildcard = this.listeners.get("*");
|
|
174
|
+
if (wildcard) {
|
|
175
|
+
for (const handler of wildcard) handler(event);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Bubble an event to the parent bus with _source and _bubbled metadata. */
|
|
179
|
+
bubbleToParent(event) {
|
|
180
|
+
if (!this.parent || !this.namespace) return;
|
|
181
|
+
if (!this.parent.checkBubbleRate(this.namespace)) return;
|
|
182
|
+
const bubbledData = typeof event.data === "object" && event.data !== null ? { ...event.data, _source: this.namespace, _bubbled: true } : { _value: event.data, _source: this.namespace, _bubbled: true };
|
|
183
|
+
const bubbledEvent = {
|
|
184
|
+
type: event.type,
|
|
185
|
+
timestamp: event.timestamp,
|
|
186
|
+
sessionId: event.sessionId,
|
|
187
|
+
data: bubbledData
|
|
188
|
+
};
|
|
189
|
+
this.parent.dispatchLocal(bubbledEvent);
|
|
190
|
+
const nsListeners = this.parent.namespacedListeners.get(this.namespace);
|
|
191
|
+
if (nsListeners) {
|
|
192
|
+
for (const handler of nsListeners) handler(bubbledEvent);
|
|
193
|
+
}
|
|
194
|
+
this.parent.bubbleToParent(bubbledEvent);
|
|
195
|
+
}
|
|
196
|
+
/** Check if a child is within the allowed bubble rate. Returns true if allowed. */
|
|
197
|
+
checkBubbleRate(childNamespace) {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
let tracker = this.bubbleCounts.get(childNamespace);
|
|
200
|
+
if (!tracker || now - tracker.windowStart >= 1e3) {
|
|
201
|
+
tracker = { count: 0, windowStart: now };
|
|
202
|
+
this.bubbleCounts.set(childNamespace, tracker);
|
|
203
|
+
}
|
|
204
|
+
tracker.count++;
|
|
205
|
+
if (tracker.count > this.maxBubblesPerSecond) {
|
|
206
|
+
if (tracker.count === this.maxBubblesPerSecond + 1) {
|
|
207
|
+
console.warn(
|
|
208
|
+
`EventBus: anti-storm \u2014 child "${childNamespace}" exceeded ${this.maxBubblesPerSecond} bubbles/sec, dropping events`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/context/token-tracker.ts
|
|
218
|
+
var TokenTracker = class {
|
|
219
|
+
constructor(counter, budget) {
|
|
220
|
+
this.counter = counter;
|
|
221
|
+
this.budget = budget;
|
|
222
|
+
}
|
|
223
|
+
inputTokens = 0;
|
|
224
|
+
outputTokens = 0;
|
|
225
|
+
addInput(tokens) {
|
|
226
|
+
this.inputTokens += tokens;
|
|
227
|
+
}
|
|
228
|
+
addOutput(tokens) {
|
|
229
|
+
this.outputTokens += tokens;
|
|
230
|
+
}
|
|
231
|
+
getUsage() {
|
|
232
|
+
const totalTokens = this.inputTokens + this.outputTokens;
|
|
233
|
+
return {
|
|
234
|
+
inputTokens: this.inputTokens,
|
|
235
|
+
outputTokens: this.outputTokens,
|
|
236
|
+
totalTokens,
|
|
237
|
+
estimatedCost: this.counter.estimateCost(
|
|
238
|
+
this.inputTokens,
|
|
239
|
+
this.outputTokens,
|
|
240
|
+
""
|
|
241
|
+
),
|
|
242
|
+
isOverBudget: this.isOverBudget()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
isOverBudget() {
|
|
246
|
+
return this.inputTokens > this.budget.maxInputTokens || this.outputTokens > this.budget.maxOutputTokens || this.inputTokens + this.outputTokens > this.budget.maxTotalTokens;
|
|
247
|
+
}
|
|
248
|
+
isNearBudget(threshold) {
|
|
249
|
+
const t = threshold ?? this.budget.warningThreshold;
|
|
250
|
+
return this.inputTokens > this.budget.maxInputTokens * t || this.outputTokens > this.budget.maxOutputTokens * t || this.inputTokens + this.outputTokens > this.budget.maxTotalTokens * t;
|
|
251
|
+
}
|
|
252
|
+
getRemainingBudget() {
|
|
253
|
+
const total = this.inputTokens + this.outputTokens;
|
|
254
|
+
return {
|
|
255
|
+
inputTokens: Math.max(0, this.budget.maxInputTokens - this.inputTokens),
|
|
256
|
+
outputTokens: Math.max(0, this.budget.maxOutputTokens - this.outputTokens),
|
|
257
|
+
totalTokens: Math.max(0, this.budget.maxTotalTokens - total)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
reset() {
|
|
261
|
+
this.inputTokens = 0;
|
|
262
|
+
this.outputTokens = 0;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/agent/tool-manager.ts
|
|
267
|
+
import { z as z18 } from "zod";
|
|
268
|
+
|
|
269
|
+
// src/agent/approval-manager.ts
|
|
270
|
+
var ApprovalManager = class {
|
|
271
|
+
config;
|
|
272
|
+
sessionId;
|
|
273
|
+
eventHandler;
|
|
274
|
+
constructor(config, sessionId, eventHandler) {
|
|
275
|
+
this.config = config;
|
|
276
|
+
this.sessionId = sessionId;
|
|
277
|
+
this.eventHandler = eventHandler;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Synchronous check whether a tool is auto-approved by configuration.
|
|
281
|
+
*
|
|
282
|
+
* - "approve-all": returns true unless tool is in `requireApproval`
|
|
283
|
+
* - "deny-all": returns true only if tool is in `autoApprove`
|
|
284
|
+
*/
|
|
285
|
+
shouldApprove(toolName) {
|
|
286
|
+
if (this.config.defaultMode === "approve-all") {
|
|
287
|
+
return !this.config.requireApproval.includes(toolName);
|
|
288
|
+
}
|
|
289
|
+
return this.config.autoApprove.includes(toolName);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Requests approval via the configured callback, emitting lifecycle events.
|
|
293
|
+
*/
|
|
294
|
+
async requestApproval(request) {
|
|
295
|
+
this.emit("tool:approval-required", request);
|
|
296
|
+
const approved = await this.config.onApprovalRequired(request);
|
|
297
|
+
if (approved) {
|
|
298
|
+
this.emit("tool:approved", { toolName: request.toolName, toolCallId: request.toolCallId });
|
|
299
|
+
} else {
|
|
300
|
+
this.emit("tool:denied", { toolName: request.toolName, toolCallId: request.toolCallId });
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
approved,
|
|
304
|
+
reason: approved ? void 0 : "Approval denied by callback"
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Combined check: auto-approves when policy allows, otherwise delegates
|
|
309
|
+
* to the async approval flow.
|
|
310
|
+
*/
|
|
311
|
+
async checkAndApprove(toolName, toolCallId, args, stepIndex) {
|
|
312
|
+
if (this.shouldApprove(toolName)) {
|
|
313
|
+
return { approved: true };
|
|
314
|
+
}
|
|
315
|
+
return this.requestApproval({
|
|
316
|
+
toolName,
|
|
317
|
+
toolCallId,
|
|
318
|
+
args,
|
|
319
|
+
sessionId: this.sessionId,
|
|
320
|
+
stepIndex
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Helpers
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
emit(type, data) {
|
|
327
|
+
this.eventHandler?.({
|
|
328
|
+
type,
|
|
329
|
+
timestamp: Date.now(),
|
|
330
|
+
sessionId: this.sessionId,
|
|
331
|
+
data
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/tools/filesystem/ls.tool.ts
|
|
337
|
+
import { z } from "zod";
|
|
338
|
+
var zoneSchema = z.enum(["transient", "persistent"]).default("transient").describe("Filesystem zone to operate in");
|
|
339
|
+
function createLsTool(fs) {
|
|
340
|
+
return tool({
|
|
341
|
+
description: "List files and directories at the given path. Returns names, sizes, and types. Use recursive to walk subdirectories.",
|
|
342
|
+
inputSchema: z.object({
|
|
343
|
+
path: z.string().describe("Directory path to list"),
|
|
344
|
+
recursive: z.boolean().optional().default(false).describe("Whether to list subdirectories recursively"),
|
|
345
|
+
zone: zoneSchema.optional()
|
|
346
|
+
}),
|
|
347
|
+
execute: async ({ path, recursive, zone }) => {
|
|
348
|
+
const entries = await fs.list(
|
|
349
|
+
path,
|
|
350
|
+
{ recursive },
|
|
351
|
+
zone ?? "transient"
|
|
352
|
+
);
|
|
353
|
+
if (entries.length === 0) return "Directory is empty.";
|
|
354
|
+
const lines = entries.map((e) => {
|
|
355
|
+
const tag = e.isDirectory ? "[dir] " : " ";
|
|
356
|
+
return `${tag}${e.path} (${e.size} bytes)`;
|
|
357
|
+
});
|
|
358
|
+
return lines.join("\n");
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/tools/filesystem/read-file.tool.ts
|
|
364
|
+
import { z as z2 } from "zod";
|
|
365
|
+
var MAX_CHARS = 5e4;
|
|
366
|
+
function createReadFileTool(fs) {
|
|
367
|
+
return tool({
|
|
368
|
+
description: "Read the contents of a file and return it as a string. Very large files are truncated to 50000 characters.",
|
|
369
|
+
inputSchema: z2.object({
|
|
370
|
+
path: z2.string().describe("File path to read"),
|
|
371
|
+
zone: z2.enum(["transient", "persistent"]).default("transient").optional().describe("Filesystem zone")
|
|
372
|
+
}),
|
|
373
|
+
execute: async ({ path, zone }) => {
|
|
374
|
+
const content = await fs.read(path, zone ?? "transient");
|
|
375
|
+
if (content.length <= MAX_CHARS) return content;
|
|
376
|
+
return content.slice(0, MAX_CHARS) + `
|
|
377
|
+
|
|
378
|
+
[Truncated: showing ${MAX_CHARS} of ${content.length} characters]`;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/tools/filesystem/write-file.tool.ts
|
|
384
|
+
import { z as z3 } from "zod";
|
|
385
|
+
function createWriteFileTool(fs) {
|
|
386
|
+
return tool({
|
|
387
|
+
description: "Create or overwrite a file with the given content. Parent directories are created automatically.",
|
|
388
|
+
inputSchema: z3.object({
|
|
389
|
+
path: z3.string().describe("File path to write"),
|
|
390
|
+
content: z3.string().describe("Content to write to the file"),
|
|
391
|
+
zone: z3.enum(["transient", "persistent"]).default("transient").optional().describe("Filesystem zone")
|
|
392
|
+
}),
|
|
393
|
+
execute: async ({ path, content, zone }) => {
|
|
394
|
+
await fs.write(path, content, zone ?? "transient");
|
|
395
|
+
return `File written: ${path} (${content.length} bytes)`;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/tools/filesystem/edit-file.tool.ts
|
|
401
|
+
import { z as z4 } from "zod";
|
|
402
|
+
function createEditFileTool(fs) {
|
|
403
|
+
return tool({
|
|
404
|
+
description: "Perform a surgical string replacement in a file. Replaces the first occurrence of oldStr with newStr. Fails if oldStr is not found.",
|
|
405
|
+
inputSchema: z4.object({
|
|
406
|
+
path: z4.string().describe("File path to edit"),
|
|
407
|
+
oldStr: z4.string().describe("Exact string to find and replace"),
|
|
408
|
+
newStr: z4.string().describe("Replacement string"),
|
|
409
|
+
zone: z4.enum(["transient", "persistent"]).default("transient").optional().describe("Filesystem zone")
|
|
410
|
+
}),
|
|
411
|
+
execute: async ({ path, oldStr, newStr, zone }) => {
|
|
412
|
+
const z22 = zone ?? "transient";
|
|
413
|
+
const content = await fs.read(path, z22);
|
|
414
|
+
if (!content.includes(oldStr)) {
|
|
415
|
+
throw new Error(`oldStr not found in ${path}`);
|
|
416
|
+
}
|
|
417
|
+
const updated = content.replace(oldStr, newStr);
|
|
418
|
+
await fs.write(path, updated, z22);
|
|
419
|
+
return `File edited: ${path}`;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/tools/filesystem/glob.tool.ts
|
|
425
|
+
import { z as z5 } from "zod";
|
|
426
|
+
function createGlobTool(fs) {
|
|
427
|
+
return tool({
|
|
428
|
+
description: "Find files matching a glob pattern (e.g. '**/*.ts'). Returns a list of matching file paths.",
|
|
429
|
+
inputSchema: z5.object({
|
|
430
|
+
pattern: z5.string().describe("Glob pattern to match files"),
|
|
431
|
+
zone: z5.enum(["transient", "persistent"]).default("transient").optional().describe("Filesystem zone")
|
|
432
|
+
}),
|
|
433
|
+
execute: async ({ pattern, zone }) => {
|
|
434
|
+
const matches = await fs.glob(pattern, zone ?? "transient");
|
|
435
|
+
if (matches.length === 0) return "No files matched the pattern.";
|
|
436
|
+
return matches.join("\n");
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/tools/filesystem/grep.tool.ts
|
|
442
|
+
import { z as z6 } from "zod";
|
|
443
|
+
function createGrepTool(fs) {
|
|
444
|
+
return tool({
|
|
445
|
+
description: "Search file contents for a regex or string pattern. Returns matching lines with file paths and line numbers.",
|
|
446
|
+
inputSchema: z6.object({
|
|
447
|
+
pattern: z6.string().describe("Regex or string pattern to search for"),
|
|
448
|
+
path: z6.string().optional().describe("Directory or file to search in"),
|
|
449
|
+
caseSensitive: z6.boolean().optional().default(true),
|
|
450
|
+
maxResults: z6.number().int().optional().default(50).describe("Maximum number of results to return"),
|
|
451
|
+
zone: z6.enum(["transient", "persistent"]).default("transient").optional().describe("Filesystem zone")
|
|
452
|
+
}),
|
|
453
|
+
execute: async ({ pattern, path, caseSensitive, maxResults, zone }) => {
|
|
454
|
+
const results = await fs.search(
|
|
455
|
+
pattern,
|
|
456
|
+
{ caseSensitive, maxResults, includeLineNumbers: true, filePattern: path },
|
|
457
|
+
zone ?? "transient"
|
|
458
|
+
);
|
|
459
|
+
if (results.length === 0) return "No matches found.";
|
|
460
|
+
const lines = results.map(
|
|
461
|
+
(r) => `${r.filePath}:${r.lineNumber}: ${r.lineContent}`
|
|
462
|
+
);
|
|
463
|
+
return lines.join("\n");
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/tools/filesystem/index.ts
|
|
469
|
+
function createFilesystemTools(fs) {
|
|
470
|
+
return {
|
|
471
|
+
ls: createLsTool(fs),
|
|
472
|
+
read_file: createReadFileTool(fs),
|
|
473
|
+
write_file: createWriteFileTool(fs),
|
|
474
|
+
edit_file: createEditFileTool(fs),
|
|
475
|
+
glob: createGlobTool(fs),
|
|
476
|
+
grep: createGrepTool(fs)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/tools/planning/write-todos.tool.ts
|
|
481
|
+
import { z as z7 } from "zod";
|
|
482
|
+
|
|
483
|
+
// src/tools/planning/shared.ts
|
|
484
|
+
var TODOS_PATH = "todos.json";
|
|
485
|
+
async function loadTodos(fs) {
|
|
486
|
+
const exists = await fs.exists(TODOS_PATH, "persistent");
|
|
487
|
+
if (!exists) return [];
|
|
488
|
+
const raw = await fs.read(TODOS_PATH, "persistent");
|
|
489
|
+
return JSON.parse(raw);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/tools/planning/write-todos.tool.ts
|
|
493
|
+
var TodoInputSchema = z7.object({
|
|
494
|
+
id: z7.string().describe("Unique identifier (kebab-case)"),
|
|
495
|
+
title: z7.string().describe("Short title of the task"),
|
|
496
|
+
description: z7.string().optional(),
|
|
497
|
+
status: z7.enum(["pending", "in_progress", "done", "blocked"]).optional(),
|
|
498
|
+
dependencies: z7.array(z7.string()).optional(),
|
|
499
|
+
priority: z7.enum(["low", "medium", "high", "critical"]).optional()
|
|
500
|
+
});
|
|
501
|
+
function createWriteTodosTool(fs) {
|
|
502
|
+
return tool({
|
|
503
|
+
description: "Create or update a task plan. Each todo has an id, title, optional description, status, dependencies, and priority.",
|
|
504
|
+
inputSchema: z7.object({
|
|
505
|
+
todos: z7.array(TodoInputSchema).describe("Todos to create or update")
|
|
506
|
+
}),
|
|
507
|
+
execute: async ({ todos }) => {
|
|
508
|
+
const existing = await loadTodos(fs);
|
|
509
|
+
const byId = new Map(existing.map((t) => [t.id, t]));
|
|
510
|
+
let created = 0;
|
|
511
|
+
let updated = 0;
|
|
512
|
+
for (const input of todos) {
|
|
513
|
+
const now = Date.now();
|
|
514
|
+
const prev = byId.get(input.id);
|
|
515
|
+
if (prev) {
|
|
516
|
+
byId.set(input.id, { ...prev, ...input, updatedAt: now });
|
|
517
|
+
updated++;
|
|
518
|
+
} else {
|
|
519
|
+
byId.set(input.id, {
|
|
520
|
+
status: "pending",
|
|
521
|
+
dependencies: [],
|
|
522
|
+
priority: "medium",
|
|
523
|
+
createdAt: now,
|
|
524
|
+
updatedAt: now,
|
|
525
|
+
...input
|
|
526
|
+
});
|
|
527
|
+
created++;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const all = Array.from(byId.values());
|
|
531
|
+
await fs.write(TODOS_PATH, JSON.stringify(all, null, 2), "persistent");
|
|
532
|
+
return `Plan updated: ${created} created, ${updated} updated, ${all.length} total.`;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/tools/planning/review-todos.tool.ts
|
|
538
|
+
import { z as z8 } from "zod";
|
|
539
|
+
function createReviewTodosTool(fs) {
|
|
540
|
+
return tool({
|
|
541
|
+
description: "Review current task plan and optionally update todo statuses. Returns the full plan with progress.",
|
|
542
|
+
inputSchema: z8.object({
|
|
543
|
+
updates: z8.array(
|
|
544
|
+
z8.object({
|
|
545
|
+
id: z8.string().describe("Todo ID to update"),
|
|
546
|
+
status: z8.enum(["pending", "in_progress", "done", "blocked"])
|
|
547
|
+
})
|
|
548
|
+
).optional().describe("Optional status updates to apply")
|
|
549
|
+
}),
|
|
550
|
+
execute: async ({ updates }) => {
|
|
551
|
+
const todos = await loadTodos(fs);
|
|
552
|
+
if (todos.length === 0) return "No todos found. Use write_todos first.";
|
|
553
|
+
if (updates?.length) {
|
|
554
|
+
const byId = new Map(todos.map((t) => [t.id, t]));
|
|
555
|
+
for (const u of updates) {
|
|
556
|
+
const t = byId.get(u.id);
|
|
557
|
+
if (!t) continue;
|
|
558
|
+
t.status = u.status;
|
|
559
|
+
t.updatedAt = Date.now();
|
|
560
|
+
if (u.status === "done") t.completedAt = Date.now();
|
|
561
|
+
}
|
|
562
|
+
await fs.write(
|
|
563
|
+
TODOS_PATH,
|
|
564
|
+
JSON.stringify(todos, null, 2),
|
|
565
|
+
"persistent"
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
return formatPlan(todos);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
function formatPlan(todos) {
|
|
573
|
+
const counts = { pending: 0, in_progress: 0, done: 0, blocked: 0 };
|
|
574
|
+
for (const t of todos) counts[t.status]++;
|
|
575
|
+
const header = `Plan: ${todos.length} total | ${counts.done} done, ${counts.in_progress} in-progress, ${counts.pending} pending, ${counts.blocked} blocked`;
|
|
576
|
+
const lines = todos.map(
|
|
577
|
+
(t) => `[${t.status}] ${t.id}: ${t.title}` + (t.dependencies?.length ? ` (deps: ${t.dependencies.join(", ")})` : "")
|
|
578
|
+
);
|
|
579
|
+
return [header, "---", ...lines].join("\n");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/tools/planning/plan-create.tool.ts
|
|
583
|
+
import { z as z10 } from "zod";
|
|
584
|
+
|
|
585
|
+
// src/domain/plan.schema.ts
|
|
586
|
+
import { z as z9 } from "zod";
|
|
587
|
+
var StepExecutionModeSchema = z9.enum([
|
|
588
|
+
"sequential",
|
|
589
|
+
"parallel",
|
|
590
|
+
"conditional",
|
|
591
|
+
"loop"
|
|
592
|
+
]);
|
|
593
|
+
var StepStatusSchema = z9.enum([
|
|
594
|
+
"idle",
|
|
595
|
+
"pending",
|
|
596
|
+
"running",
|
|
597
|
+
"completed",
|
|
598
|
+
"failed",
|
|
599
|
+
"skipped",
|
|
600
|
+
"blocked",
|
|
601
|
+
"cancelled"
|
|
602
|
+
]);
|
|
603
|
+
var StepPrioritySchema = z9.enum(["low", "medium", "high", "critical"]);
|
|
604
|
+
var PlanStatusSchema = z9.enum([
|
|
605
|
+
"draft",
|
|
606
|
+
"active",
|
|
607
|
+
"paused",
|
|
608
|
+
"completed",
|
|
609
|
+
"failed",
|
|
610
|
+
"cancelled"
|
|
611
|
+
]);
|
|
612
|
+
var STEP_STATUS_TRANSITIONS = {
|
|
613
|
+
idle: ["pending", "skipped", "cancelled"],
|
|
614
|
+
pending: ["running", "blocked", "skipped", "cancelled"],
|
|
615
|
+
running: ["completed", "failed", "cancelled"],
|
|
616
|
+
completed: [],
|
|
617
|
+
failed: ["pending"],
|
|
618
|
+
// retry
|
|
619
|
+
skipped: [],
|
|
620
|
+
blocked: ["pending", "cancelled"],
|
|
621
|
+
cancelled: []
|
|
622
|
+
};
|
|
623
|
+
var ResourceRequirementsSchema = z9.object({
|
|
624
|
+
/** Budget massimo di token (input + output) per questo step */
|
|
625
|
+
maxTokenBudget: z9.number().positive().optional(),
|
|
626
|
+
/** Timeout in millisecondi */
|
|
627
|
+
timeoutMs: z9.number().positive().optional(),
|
|
628
|
+
/** Numero massimo di retry in caso di fallimento */
|
|
629
|
+
maxRetries: z9.number().int().min(0).optional().default(0),
|
|
630
|
+
/** Modello preferito (override del default del piano) */
|
|
631
|
+
preferredModel: z9.string().optional(),
|
|
632
|
+
/** Livello di concorrenza massimo per sotto-step paralleli */
|
|
633
|
+
maxConcurrency: z9.number().int().positive().optional().default(5)
|
|
634
|
+
});
|
|
635
|
+
var IOFieldSchema = z9.object({
|
|
636
|
+
name: z9.string().describe("Nome del campo"),
|
|
637
|
+
type: z9.enum(["string", "number", "boolean", "array", "object", "any"]).describe("Tipo del campo"),
|
|
638
|
+
description: z9.string().optional(),
|
|
639
|
+
required: z9.boolean().default(true)
|
|
640
|
+
});
|
|
641
|
+
var StepContractSchema = z9.object({
|
|
642
|
+
/** Campi di input attesi — cosa serve allo step per eseguire */
|
|
643
|
+
inputs: z9.array(IOFieldSchema).default([]),
|
|
644
|
+
/** Campi di output prodotti — cosa produce lo step */
|
|
645
|
+
outputs: z9.array(IOFieldSchema).default([])
|
|
646
|
+
});
|
|
647
|
+
var StepResultSchema = z9.object({
|
|
648
|
+
/** Output testuale prodotto */
|
|
649
|
+
output: z9.string().optional(),
|
|
650
|
+
/** Dati strutturati di output (match con contract.outputs) */
|
|
651
|
+
data: z9.record(z9.string(), z9.unknown()).optional(),
|
|
652
|
+
/** Uso token */
|
|
653
|
+
tokenUsage: z9.object({ input: z9.number(), output: z9.number() }).optional(),
|
|
654
|
+
/** Durata in ms */
|
|
655
|
+
durationMs: z9.number().optional(),
|
|
656
|
+
/** Errore se fallito */
|
|
657
|
+
error: z9.string().optional()
|
|
658
|
+
});
|
|
659
|
+
var StepConditionSchema = z9.object({
|
|
660
|
+
/** Espressione condizionale (riferimenti a output di step precedenti) */
|
|
661
|
+
expression: z9.string().describe(
|
|
662
|
+
"Espressione valutabile, es: 'steps.auth-design.status === completed'"
|
|
663
|
+
),
|
|
664
|
+
/** ID dello step da eseguire se la condizione è vera */
|
|
665
|
+
ifTrueStepId: z9.string().optional(),
|
|
666
|
+
/** ID dello step da eseguire se la condizione è falsa */
|
|
667
|
+
ifFalseStepId: z9.string().optional()
|
|
668
|
+
});
|
|
669
|
+
var LoopConfigSchema = z9.object({
|
|
670
|
+
/** Condizione di continuazione */
|
|
671
|
+
condition: z9.string().describe(
|
|
672
|
+
"Espressione per continuare il loop, es: 'iteration < 3 && !steps.validate.data.allPassing'"
|
|
673
|
+
),
|
|
674
|
+
/** Numero massimo di iterazioni (safety) */
|
|
675
|
+
maxIterations: z9.number().int().positive().default(10),
|
|
676
|
+
/** ID dello step che funge da corpo del loop */
|
|
677
|
+
bodyStepIds: z9.array(z9.string()).min(1)
|
|
678
|
+
});
|
|
679
|
+
var BaseSubStepSchema = z9.object({
|
|
680
|
+
id: z9.string().describe("ID univoco del sotto-step (kebab-case)"),
|
|
681
|
+
title: z9.string().describe("Titolo breve del sotto-step"),
|
|
682
|
+
description: z9.string().optional(),
|
|
683
|
+
status: StepStatusSchema.default("idle"),
|
|
684
|
+
executionMode: StepExecutionModeSchema.default("sequential"),
|
|
685
|
+
contract: StepContractSchema.optional(),
|
|
686
|
+
resources: ResourceRequirementsSchema.optional(),
|
|
687
|
+
result: StepResultSchema.optional(),
|
|
688
|
+
/** Istruzioni/prompt per l'agente che esegue questo sotto-step */
|
|
689
|
+
prompt: z9.string().optional(),
|
|
690
|
+
/** Tool specifici richiesti per questo sotto-step */
|
|
691
|
+
requiredTools: z9.array(z9.string()).default([])
|
|
692
|
+
});
|
|
693
|
+
var SubStepSchema = BaseSubStepSchema.extend({
|
|
694
|
+
children: z9.lazy(() => z9.array(SubStepSchema)).optional()
|
|
695
|
+
});
|
|
696
|
+
var StepSchema = z9.object({
|
|
697
|
+
id: z9.string().describe("ID univoco dello step (kebab-case)"),
|
|
698
|
+
title: z9.string().describe("Titolo breve dello step"),
|
|
699
|
+
description: z9.string().optional().describe("Descrizione dettagliata"),
|
|
700
|
+
/** Modalità di esecuzione */
|
|
701
|
+
executionMode: StepExecutionModeSchema.default("sequential"),
|
|
702
|
+
/** Stato corrente */
|
|
703
|
+
status: StepStatusSchema.default("idle"),
|
|
704
|
+
/** Priorità */
|
|
705
|
+
priority: StepPrioritySchema.default("medium"),
|
|
706
|
+
/** Contratto input/output strutturato */
|
|
707
|
+
contract: StepContractSchema.default({ inputs: [], outputs: [] }),
|
|
708
|
+
/** Requisiti di risorse */
|
|
709
|
+
resources: ResourceRequirementsSchema.optional(),
|
|
710
|
+
/** Dipendenze esplicite: ID di step nella stessa fase o in fasi precedenti */
|
|
711
|
+
dependencies: z9.array(z9.string()).default([]),
|
|
712
|
+
/** Istruzioni/prompt per l'agente */
|
|
713
|
+
prompt: z9.string().optional().describe(
|
|
714
|
+
"Prompt specifico per l'agente che esegue questo step"
|
|
715
|
+
),
|
|
716
|
+
/** Tool specifici richiesti */
|
|
717
|
+
requiredTools: z9.array(z9.string()).default([]),
|
|
718
|
+
/** Sotto-step (gerarchia ricorsiva) */
|
|
719
|
+
subSteps: z9.array(SubStepSchema).default([]),
|
|
720
|
+
/** Condizione (solo se executionMode === 'conditional') */
|
|
721
|
+
condition: StepConditionSchema.optional(),
|
|
722
|
+
/** Configurazione loop (solo se executionMode === 'loop') */
|
|
723
|
+
loopConfig: LoopConfigSchema.optional(),
|
|
724
|
+
/** Risultato dell'esecuzione */
|
|
725
|
+
result: StepResultSchema.optional(),
|
|
726
|
+
/** Timestamp */
|
|
727
|
+
createdAt: z9.number().default(() => Date.now()),
|
|
728
|
+
updatedAt: z9.number().default(() => Date.now()),
|
|
729
|
+
startedAt: z9.number().optional(),
|
|
730
|
+
completedAt: z9.number().optional(),
|
|
731
|
+
/** Metadati arbitrari */
|
|
732
|
+
metadata: z9.record(z9.string(), z9.unknown()).optional()
|
|
733
|
+
});
|
|
734
|
+
var PhaseSchema = z9.object({
|
|
735
|
+
id: z9.string().describe("ID univoco della fase (kebab-case)"),
|
|
736
|
+
title: z9.string().describe("Titolo della fase"),
|
|
737
|
+
description: z9.string().optional(),
|
|
738
|
+
/** Come eseguire gli step dentro questa fase */
|
|
739
|
+
executionMode: StepExecutionModeSchema.default("sequential"),
|
|
740
|
+
/** Stato derivato dagli step contenuti */
|
|
741
|
+
status: StepStatusSchema.default("idle"),
|
|
742
|
+
/** Step contenuti in questa fase */
|
|
743
|
+
steps: z9.array(StepSchema).min(1).describe("Step della fase"),
|
|
744
|
+
/** Ordine (per fasi sequenziali nel piano) */
|
|
745
|
+
order: z9.number().int().min(0).default(0),
|
|
746
|
+
/** Dipendenze da altre fasi (ID) */
|
|
747
|
+
dependencies: z9.array(z9.string()).default([]),
|
|
748
|
+
/** Requisiti di risorse per l'intera fase */
|
|
749
|
+
resources: ResourceRequirementsSchema.optional(),
|
|
750
|
+
/** Timestamp */
|
|
751
|
+
createdAt: z9.number().default(() => Date.now()),
|
|
752
|
+
updatedAt: z9.number().default(() => Date.now())
|
|
753
|
+
});
|
|
754
|
+
var PlanMetadataSchema = z9.object({
|
|
755
|
+
/** Chi ha creato il piano (agente o umano) */
|
|
756
|
+
createdBy: z9.string().default("agent"),
|
|
757
|
+
/** Versione del piano (incrementa ad ogni modifica) */
|
|
758
|
+
version: z9.number().int().min(1).default(1),
|
|
759
|
+
/** Tag liberi per classificazione */
|
|
760
|
+
tags: z9.array(z9.string()).default([]),
|
|
761
|
+
/** Obiettivo di alto livello che il piano deve raggiungere */
|
|
762
|
+
goal: z9.string().describe("Obiettivo principale del piano"),
|
|
763
|
+
/** Contesto/vincoli aggiuntivi */
|
|
764
|
+
constraints: z9.array(z9.string()).default([]),
|
|
765
|
+
/** Stima dei token totali per l'intero piano */
|
|
766
|
+
estimatedTotalTokens: z9.number().optional(),
|
|
767
|
+
/** Stima della durata totale in ms */
|
|
768
|
+
estimatedDurationMs: z9.number().optional()
|
|
769
|
+
});
|
|
770
|
+
var PlanSchema = z9.object({
|
|
771
|
+
id: z9.string().describe("ID univoco del piano (kebab-case)"),
|
|
772
|
+
title: z9.string().describe("Titolo del piano"),
|
|
773
|
+
description: z9.string().optional().describe("Descrizione del piano"),
|
|
774
|
+
/** Stato globale del piano */
|
|
775
|
+
status: PlanStatusSchema.default("draft"),
|
|
776
|
+
/** Metadata del piano */
|
|
777
|
+
metadata: PlanMetadataSchema,
|
|
778
|
+
/** Fasi del piano (ordinate) */
|
|
779
|
+
phases: z9.array(PhaseSchema).min(1).describe("Fasi del piano"),
|
|
780
|
+
/** Configurazione risorse globali */
|
|
781
|
+
globalResources: ResourceRequirementsSchema.optional(),
|
|
782
|
+
/** Timestamp */
|
|
783
|
+
createdAt: z9.number().default(() => Date.now()),
|
|
784
|
+
updatedAt: z9.number().default(() => Date.now()),
|
|
785
|
+
startedAt: z9.number().optional(),
|
|
786
|
+
completedAt: z9.number().optional()
|
|
787
|
+
});
|
|
788
|
+
var PlanEventTypeSchema = z9.enum([
|
|
789
|
+
"plan:created",
|
|
790
|
+
"plan:started",
|
|
791
|
+
"plan:paused",
|
|
792
|
+
"plan:resumed",
|
|
793
|
+
"plan:completed",
|
|
794
|
+
"plan:failed",
|
|
795
|
+
"plan:cancelled",
|
|
796
|
+
"plan:updated",
|
|
797
|
+
"phase:started",
|
|
798
|
+
"phase:completed",
|
|
799
|
+
"phase:failed",
|
|
800
|
+
"step:started",
|
|
801
|
+
"step:completed",
|
|
802
|
+
"step:failed",
|
|
803
|
+
"step:skipped",
|
|
804
|
+
"step:blocked",
|
|
805
|
+
"step:retrying",
|
|
806
|
+
"substep:started",
|
|
807
|
+
"substep:completed",
|
|
808
|
+
"substep:failed"
|
|
809
|
+
]);
|
|
810
|
+
var PlanEventSchema = z9.object({
|
|
811
|
+
type: PlanEventTypeSchema,
|
|
812
|
+
planId: z9.string(),
|
|
813
|
+
phaseId: z9.string().optional(),
|
|
814
|
+
stepId: z9.string().optional(),
|
|
815
|
+
subStepId: z9.string().optional(),
|
|
816
|
+
timestamp: z9.number().default(() => Date.now()),
|
|
817
|
+
data: z9.record(z9.string(), z9.unknown()).optional()
|
|
818
|
+
});
|
|
819
|
+
var StepProgressSchema = z9.object({
|
|
820
|
+
stepId: z9.string(),
|
|
821
|
+
title: z9.string(),
|
|
822
|
+
status: StepStatusSchema,
|
|
823
|
+
/** Progresso dei sotto-step (0.0 - 1.0) */
|
|
824
|
+
progress: z9.number().min(0).max(1).default(0),
|
|
825
|
+
/** Numero sotto-step completati / totali */
|
|
826
|
+
subStepsDone: z9.number().int().min(0).default(0),
|
|
827
|
+
subStepsTotal: z9.number().int().min(0).default(0)
|
|
828
|
+
});
|
|
829
|
+
var PhaseProgressSchema = z9.object({
|
|
830
|
+
phaseId: z9.string(),
|
|
831
|
+
title: z9.string(),
|
|
832
|
+
status: StepStatusSchema,
|
|
833
|
+
progress: z9.number().min(0).max(1).default(0),
|
|
834
|
+
steps: z9.array(StepProgressSchema)
|
|
835
|
+
});
|
|
836
|
+
var PlanProgressSchema = z9.object({
|
|
837
|
+
planId: z9.string(),
|
|
838
|
+
title: z9.string(),
|
|
839
|
+
status: PlanStatusSchema,
|
|
840
|
+
/** Progresso globale (0.0 - 1.0) */
|
|
841
|
+
progress: z9.number().min(0).max(1).default(0),
|
|
842
|
+
/** Conteggi globali */
|
|
843
|
+
totalSteps: z9.number().int().min(0),
|
|
844
|
+
completedSteps: z9.number().int().min(0),
|
|
845
|
+
failedSteps: z9.number().int().min(0),
|
|
846
|
+
/** Dettaglio per fase */
|
|
847
|
+
phases: z9.array(PhaseProgressSchema),
|
|
848
|
+
/** Durata totale fino ad ora */
|
|
849
|
+
elapsedMs: z9.number().optional(),
|
|
850
|
+
/** Token usati fino ad ora */
|
|
851
|
+
tokenUsage: z9.object({ input: z9.number(), output: z9.number() }).optional()
|
|
852
|
+
});
|
|
853
|
+
function validatePlan(plan) {
|
|
854
|
+
const errors = [];
|
|
855
|
+
const warnings = [];
|
|
856
|
+
const allStepIds = /* @__PURE__ */ new Set();
|
|
857
|
+
const allPhaseIds = /* @__PURE__ */ new Set();
|
|
858
|
+
for (const phase of plan.phases) {
|
|
859
|
+
if (allPhaseIds.has(phase.id)) {
|
|
860
|
+
errors.push(`ID fase duplicato: "${phase.id}"`);
|
|
861
|
+
}
|
|
862
|
+
allPhaseIds.add(phase.id);
|
|
863
|
+
for (const step of phase.steps) {
|
|
864
|
+
if (allStepIds.has(step.id)) {
|
|
865
|
+
errors.push(`ID step duplicato: "${step.id}"`);
|
|
866
|
+
}
|
|
867
|
+
allStepIds.add(step.id);
|
|
868
|
+
collectSubStepIds(step.subSteps, allStepIds, errors);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
for (const phase of plan.phases) {
|
|
872
|
+
for (const dep of phase.dependencies) {
|
|
873
|
+
if (!allPhaseIds.has(dep)) {
|
|
874
|
+
errors.push(`Fase "${phase.id}" dipende da fase inesistente "${dep}"`);
|
|
875
|
+
}
|
|
876
|
+
if (dep === phase.id) {
|
|
877
|
+
errors.push(`Fase "${phase.id}" dipende da se stessa`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
for (const step of phase.steps) {
|
|
881
|
+
for (const dep of step.dependencies) {
|
|
882
|
+
if (!allStepIds.has(dep)) {
|
|
883
|
+
errors.push(
|
|
884
|
+
`Step "${step.id}" dipende da step inesistente "${dep}"`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (dep === step.id) {
|
|
888
|
+
errors.push(`Step "${step.id}" dipende da se stesso`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (step.executionMode === "conditional" && !step.condition) {
|
|
892
|
+
errors.push(
|
|
893
|
+
`Step "${step.id}" \xE8 condizionale ma manca la configurazione condition`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (step.executionMode === "loop" && !step.loopConfig) {
|
|
897
|
+
errors.push(
|
|
898
|
+
`Step "${step.id}" \xE8 loop ma manca la configurazione loopConfig`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
if (step.contract.inputs.length === 0 && step.dependencies.length > 0) {
|
|
902
|
+
warnings.push(
|
|
903
|
+
`Step "${step.id}" ha dipendenze ma nessun input definito nel contratto`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const phaseDepErrors = detectCycles(
|
|
909
|
+
plan.phases.map((p) => p.id),
|
|
910
|
+
plan.phases.reduce(
|
|
911
|
+
(acc, p) => {
|
|
912
|
+
acc[p.id] = p.dependencies;
|
|
913
|
+
return acc;
|
|
914
|
+
},
|
|
915
|
+
{}
|
|
916
|
+
)
|
|
917
|
+
);
|
|
918
|
+
errors.push(...phaseDepErrors);
|
|
919
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
920
|
+
}
|
|
921
|
+
function collectSubStepIds(subSteps, allIds, errors) {
|
|
922
|
+
for (const ss of subSteps) {
|
|
923
|
+
if (allIds.has(ss.id)) {
|
|
924
|
+
errors.push(`ID sotto-step duplicato: "${ss.id}"`);
|
|
925
|
+
}
|
|
926
|
+
allIds.add(ss.id);
|
|
927
|
+
if (ss.children) {
|
|
928
|
+
collectSubStepIds(ss.children, allIds, errors);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function detectCycles(nodeIds, deps) {
|
|
933
|
+
const errors = [];
|
|
934
|
+
const visited = /* @__PURE__ */ new Set();
|
|
935
|
+
const stack = /* @__PURE__ */ new Set();
|
|
936
|
+
const visit = (id) => {
|
|
937
|
+
if (stack.has(id)) {
|
|
938
|
+
errors.push(`Ciclo rilevato che coinvolge "${id}"`);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (visited.has(id)) return;
|
|
942
|
+
stack.add(id);
|
|
943
|
+
for (const dep of deps[id] ?? []) {
|
|
944
|
+
visit(dep);
|
|
945
|
+
}
|
|
946
|
+
stack.delete(id);
|
|
947
|
+
visited.add(id);
|
|
948
|
+
};
|
|
949
|
+
for (const id of nodeIds) {
|
|
950
|
+
visit(id);
|
|
951
|
+
}
|
|
952
|
+
return errors;
|
|
953
|
+
}
|
|
954
|
+
function isValidStepTransition(from, to) {
|
|
955
|
+
return STEP_STATUS_TRANSITIONS[from]?.includes(to) ?? false;
|
|
956
|
+
}
|
|
957
|
+
function calculateProgress(plan) {
|
|
958
|
+
let totalSteps = 0;
|
|
959
|
+
let completedSteps = 0;
|
|
960
|
+
let failedSteps = 0;
|
|
961
|
+
const phaseProgresses = plan.phases.map((phase) => {
|
|
962
|
+
const stepProgresses = phase.steps.map((step) => {
|
|
963
|
+
totalSteps++;
|
|
964
|
+
const subTotal = countSubSteps(step.subSteps);
|
|
965
|
+
const subDone = countCompletedSubSteps(step.subSteps);
|
|
966
|
+
if (step.status === "completed") completedSteps++;
|
|
967
|
+
if (step.status === "failed") failedSteps++;
|
|
968
|
+
const stepProgress = step.status === "completed" ? 1 : step.status === "running" && subTotal > 0 ? subDone / subTotal : 0;
|
|
969
|
+
return {
|
|
970
|
+
stepId: step.id,
|
|
971
|
+
title: step.title,
|
|
972
|
+
status: step.status,
|
|
973
|
+
progress: stepProgress,
|
|
974
|
+
subStepsDone: subDone,
|
|
975
|
+
subStepsTotal: subTotal
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
const phaseTotal = stepProgresses.length;
|
|
979
|
+
const phaseProgress = phaseTotal > 0 ? stepProgresses.reduce((sum, s) => sum + s.progress, 0) / phaseTotal : 0;
|
|
980
|
+
const phaseStatus = derivePhaseStatus(phase.steps);
|
|
981
|
+
return {
|
|
982
|
+
phaseId: phase.id,
|
|
983
|
+
title: phase.title,
|
|
984
|
+
status: phaseStatus,
|
|
985
|
+
progress: phaseProgress,
|
|
986
|
+
steps: stepProgresses
|
|
987
|
+
};
|
|
988
|
+
});
|
|
989
|
+
const globalProgress = totalSteps > 0 ? completedSteps / totalSteps : 0;
|
|
990
|
+
const elapsedMs = plan.startedAt ? Date.now() - plan.startedAt : void 0;
|
|
991
|
+
return {
|
|
992
|
+
planId: plan.id,
|
|
993
|
+
title: plan.title,
|
|
994
|
+
status: plan.status,
|
|
995
|
+
progress: globalProgress,
|
|
996
|
+
totalSteps,
|
|
997
|
+
completedSteps,
|
|
998
|
+
failedSteps,
|
|
999
|
+
phases: phaseProgresses,
|
|
1000
|
+
elapsedMs
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function countSubSteps(subSteps) {
|
|
1004
|
+
let count = 0;
|
|
1005
|
+
for (const ss of subSteps) {
|
|
1006
|
+
count++;
|
|
1007
|
+
if (ss.children) count += countSubSteps(ss.children);
|
|
1008
|
+
}
|
|
1009
|
+
return count;
|
|
1010
|
+
}
|
|
1011
|
+
function countCompletedSubSteps(subSteps) {
|
|
1012
|
+
let count = 0;
|
|
1013
|
+
for (const ss of subSteps) {
|
|
1014
|
+
if (ss.status === "completed") count++;
|
|
1015
|
+
if (ss.children) count += countCompletedSubSteps(ss.children);
|
|
1016
|
+
}
|
|
1017
|
+
return count;
|
|
1018
|
+
}
|
|
1019
|
+
function derivePhaseStatus(steps) {
|
|
1020
|
+
if (steps.every((s) => s.status === "completed")) return "completed";
|
|
1021
|
+
if (steps.some((s) => s.status === "failed")) return "failed";
|
|
1022
|
+
if (steps.some((s) => s.status === "running")) return "running";
|
|
1023
|
+
if (steps.some((s) => s.status === "pending")) return "pending";
|
|
1024
|
+
if (steps.some((s) => s.status === "blocked")) return "blocked";
|
|
1025
|
+
return "idle";
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/tools/planning/plan-shared.ts
|
|
1029
|
+
var PLAN_PATH = "plan.json";
|
|
1030
|
+
async function loadPlan(fs) {
|
|
1031
|
+
const exists = await fs.exists(PLAN_PATH, "persistent");
|
|
1032
|
+
if (!exists) return null;
|
|
1033
|
+
const raw = await fs.read(PLAN_PATH, "persistent");
|
|
1034
|
+
return JSON.parse(raw);
|
|
1035
|
+
}
|
|
1036
|
+
async function savePlan(fs, plan) {
|
|
1037
|
+
await fs.write(PLAN_PATH, JSON.stringify(plan, null, 2), "persistent");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/tools/planning/plan-create.tool.ts
|
|
1041
|
+
var StepInputSchema = z10.object({
|
|
1042
|
+
id: z10.string().describe("ID univoco dello step (kebab-case)"),
|
|
1043
|
+
title: z10.string().describe("Titolo breve"),
|
|
1044
|
+
description: z10.string().optional(),
|
|
1045
|
+
executionMode: StepExecutionModeSchema.optional(),
|
|
1046
|
+
priority: StepPrioritySchema.optional(),
|
|
1047
|
+
prompt: z10.string().optional().describe("Istruzioni per l'agente"),
|
|
1048
|
+
dependencies: z10.array(z10.string()).optional(),
|
|
1049
|
+
requiredTools: z10.array(z10.string()).optional(),
|
|
1050
|
+
inputs: z10.array(IOFieldSchema).optional().describe("Contratto input"),
|
|
1051
|
+
outputs: z10.array(IOFieldSchema).optional().describe("Contratto output"),
|
|
1052
|
+
maxTokenBudget: z10.number().optional(),
|
|
1053
|
+
timeoutMs: z10.number().optional(),
|
|
1054
|
+
subSteps: z10.array(z10.object({
|
|
1055
|
+
id: z10.string(),
|
|
1056
|
+
title: z10.string(),
|
|
1057
|
+
description: z10.string().optional(),
|
|
1058
|
+
prompt: z10.string().optional()
|
|
1059
|
+
})).optional()
|
|
1060
|
+
});
|
|
1061
|
+
var PhaseInputSchema = z10.object({
|
|
1062
|
+
id: z10.string().describe("ID univoco della fase (kebab-case)"),
|
|
1063
|
+
title: z10.string().describe("Titolo della fase"),
|
|
1064
|
+
description: z10.string().optional(),
|
|
1065
|
+
executionMode: StepExecutionModeSchema.optional(),
|
|
1066
|
+
dependencies: z10.array(z10.string()).optional(),
|
|
1067
|
+
steps: z10.array(StepInputSchema).min(1)
|
|
1068
|
+
});
|
|
1069
|
+
function createPlanCreateTool(fs) {
|
|
1070
|
+
return tool({
|
|
1071
|
+
description: "Crea un piano strutturato gerarchico con fasi, step e sotto-step. Ogni step ha contratti input/output, priorit\xE0, risorse e dipendenze. Supporta esecuzione sequenziale, parallela, condizionale e loop.",
|
|
1072
|
+
inputSchema: z10.object({
|
|
1073
|
+
id: z10.string().describe("ID univoco del piano (kebab-case)"),
|
|
1074
|
+
title: z10.string().describe("Titolo del piano"),
|
|
1075
|
+
description: z10.string().optional(),
|
|
1076
|
+
goal: z10.string().describe("Obiettivo principale del piano"),
|
|
1077
|
+
tags: z10.array(z10.string()).optional(),
|
|
1078
|
+
constraints: z10.array(z10.string()).optional(),
|
|
1079
|
+
phases: z10.array(PhaseInputSchema).min(1).describe("Fasi del piano"),
|
|
1080
|
+
maxTokenBudget: z10.number().optional(),
|
|
1081
|
+
timeoutMs: z10.number().optional()
|
|
1082
|
+
}),
|
|
1083
|
+
execute: async (input) => {
|
|
1084
|
+
const now = Date.now();
|
|
1085
|
+
const phases = input.phases.map((p, idx) => ({
|
|
1086
|
+
id: p.id,
|
|
1087
|
+
title: p.title,
|
|
1088
|
+
description: p.description,
|
|
1089
|
+
executionMode: p.executionMode ?? "sequential",
|
|
1090
|
+
status: "idle",
|
|
1091
|
+
order: idx,
|
|
1092
|
+
dependencies: p.dependencies ?? [],
|
|
1093
|
+
steps: p.steps.map((s) => ({
|
|
1094
|
+
id: s.id,
|
|
1095
|
+
title: s.title,
|
|
1096
|
+
description: s.description,
|
|
1097
|
+
executionMode: s.executionMode ?? "sequential",
|
|
1098
|
+
status: "idle",
|
|
1099
|
+
priority: s.priority ?? "medium",
|
|
1100
|
+
contract: {
|
|
1101
|
+
inputs: s.inputs ?? [],
|
|
1102
|
+
outputs: s.outputs ?? []
|
|
1103
|
+
},
|
|
1104
|
+
resources: s.maxTokenBudget || s.timeoutMs ? {
|
|
1105
|
+
maxTokenBudget: s.maxTokenBudget,
|
|
1106
|
+
timeoutMs: s.timeoutMs,
|
|
1107
|
+
maxRetries: 0,
|
|
1108
|
+
maxConcurrency: 5
|
|
1109
|
+
} : void 0,
|
|
1110
|
+
dependencies: s.dependencies ?? [],
|
|
1111
|
+
prompt: s.prompt,
|
|
1112
|
+
requiredTools: s.requiredTools ?? [],
|
|
1113
|
+
subSteps: (s.subSteps ?? []).map((ss) => ({
|
|
1114
|
+
id: ss.id,
|
|
1115
|
+
title: ss.title,
|
|
1116
|
+
description: ss.description,
|
|
1117
|
+
prompt: ss.prompt,
|
|
1118
|
+
status: "idle",
|
|
1119
|
+
executionMode: "sequential",
|
|
1120
|
+
requiredTools: []
|
|
1121
|
+
})),
|
|
1122
|
+
createdAt: now,
|
|
1123
|
+
updatedAt: now
|
|
1124
|
+
})),
|
|
1125
|
+
createdAt: now,
|
|
1126
|
+
updatedAt: now
|
|
1127
|
+
}));
|
|
1128
|
+
const plan = PlanSchema.parse({
|
|
1129
|
+
id: input.id,
|
|
1130
|
+
title: input.title,
|
|
1131
|
+
description: input.description,
|
|
1132
|
+
status: "draft",
|
|
1133
|
+
metadata: {
|
|
1134
|
+
goal: input.goal,
|
|
1135
|
+
createdBy: "agent",
|
|
1136
|
+
version: 1,
|
|
1137
|
+
tags: input.tags ?? [],
|
|
1138
|
+
constraints: input.constraints ?? []
|
|
1139
|
+
},
|
|
1140
|
+
phases,
|
|
1141
|
+
globalResources: input.maxTokenBudget || input.timeoutMs ? {
|
|
1142
|
+
maxTokenBudget: input.maxTokenBudget,
|
|
1143
|
+
timeoutMs: input.timeoutMs,
|
|
1144
|
+
maxRetries: 0,
|
|
1145
|
+
maxConcurrency: 5
|
|
1146
|
+
} : void 0,
|
|
1147
|
+
createdAt: now,
|
|
1148
|
+
updatedAt: now
|
|
1149
|
+
});
|
|
1150
|
+
const validation = validatePlan(plan);
|
|
1151
|
+
if (!validation.valid) {
|
|
1152
|
+
return `Errore nella creazione del piano:
|
|
1153
|
+
${validation.errors.join("\n")}`;
|
|
1154
|
+
}
|
|
1155
|
+
await savePlan(fs, plan);
|
|
1156
|
+
const totalSteps = plan.phases.reduce((sum, p) => sum + p.steps.length, 0);
|
|
1157
|
+
const warnings = validation.warnings.length > 0 ? `
|
|
1158
|
+
Warning: ${validation.warnings.join("; ")}` : "";
|
|
1159
|
+
return `Piano "${plan.title}" creato con successo.
|
|
1160
|
+
ID: ${plan.id}
|
|
1161
|
+
Fasi: ${plan.phases.length}
|
|
1162
|
+
Step totali: ${totalSteps}
|
|
1163
|
+
Stato: ${plan.status}` + warnings;
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/tools/planning/plan-update.tool.ts
|
|
1169
|
+
import { z as z11 } from "zod";
|
|
1170
|
+
function createPlanUpdateTool(fs) {
|
|
1171
|
+
return tool({
|
|
1172
|
+
description: "Modifica un piano esistente a runtime: aggiorna stato, aggiungi/rimuovi step, modifica prompt, priorit\xE0, dipendenze. Supporta transizioni di stato validate.",
|
|
1173
|
+
inputSchema: z11.object({
|
|
1174
|
+
action: z11.enum([
|
|
1175
|
+
"update_step",
|
|
1176
|
+
"add_step",
|
|
1177
|
+
"remove_step",
|
|
1178
|
+
"update_phase",
|
|
1179
|
+
"add_phase",
|
|
1180
|
+
"update_plan_status",
|
|
1181
|
+
"set_result"
|
|
1182
|
+
]).describe("Tipo di modifica"),
|
|
1183
|
+
// Per update_step / add_step / remove_step
|
|
1184
|
+
phaseId: z11.string().optional().describe("ID della fase target"),
|
|
1185
|
+
stepId: z11.string().optional().describe("ID dello step target"),
|
|
1186
|
+
// Campi aggiornabili
|
|
1187
|
+
status: StepStatusSchema.optional(),
|
|
1188
|
+
title: z11.string().optional(),
|
|
1189
|
+
description: z11.string().optional(),
|
|
1190
|
+
prompt: z11.string().optional(),
|
|
1191
|
+
priority: StepPrioritySchema.optional(),
|
|
1192
|
+
executionMode: StepExecutionModeSchema.optional(),
|
|
1193
|
+
dependencies: z11.array(z11.string()).optional(),
|
|
1194
|
+
requiredTools: z11.array(z11.string()).optional(),
|
|
1195
|
+
inputs: z11.array(IOFieldSchema).optional(),
|
|
1196
|
+
outputs: z11.array(IOFieldSchema).optional(),
|
|
1197
|
+
// Per set_result
|
|
1198
|
+
resultOutput: z11.string().optional(),
|
|
1199
|
+
resultData: z11.record(z11.string(), z11.unknown()).optional(),
|
|
1200
|
+
// Per add_phase
|
|
1201
|
+
phaseTitle: z11.string().optional(),
|
|
1202
|
+
phaseOrder: z11.number().optional(),
|
|
1203
|
+
// Per update_plan_status
|
|
1204
|
+
planStatus: z11.enum(["draft", "active", "paused", "completed", "failed", "cancelled"]).optional()
|
|
1205
|
+
}),
|
|
1206
|
+
execute: async (input) => {
|
|
1207
|
+
const plan = await loadPlan(fs);
|
|
1208
|
+
if (!plan) return "Nessun piano trovato. Usa plan_create prima.";
|
|
1209
|
+
const now = Date.now();
|
|
1210
|
+
switch (input.action) {
|
|
1211
|
+
case "update_step": {
|
|
1212
|
+
if (!input.stepId) return "stepId richiesto per update_step";
|
|
1213
|
+
const { phase, step } = findStep(plan, input.stepId);
|
|
1214
|
+
if (!step) return `Step "${input.stepId}" non trovato`;
|
|
1215
|
+
if (input.status && input.status !== step.status) {
|
|
1216
|
+
if (!isValidStepTransition(step.status, input.status)) {
|
|
1217
|
+
return `Transizione non valida: "${step.status}" \u2192 "${input.status}" per step "${step.id}"`;
|
|
1218
|
+
}
|
|
1219
|
+
step.status = input.status;
|
|
1220
|
+
if (input.status === "running") step.startedAt = now;
|
|
1221
|
+
if (input.status === "completed" || input.status === "failed") {
|
|
1222
|
+
step.completedAt = now;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
if (input.title) step.title = input.title;
|
|
1226
|
+
if (input.description) step.description = input.description;
|
|
1227
|
+
if (input.prompt) step.prompt = input.prompt;
|
|
1228
|
+
if (input.priority) step.priority = input.priority;
|
|
1229
|
+
if (input.executionMode) step.executionMode = input.executionMode;
|
|
1230
|
+
if (input.dependencies) step.dependencies = input.dependencies;
|
|
1231
|
+
if (input.requiredTools) step.requiredTools = input.requiredTools;
|
|
1232
|
+
if (input.inputs) step.contract.inputs = input.inputs;
|
|
1233
|
+
if (input.outputs) step.contract.outputs = input.outputs;
|
|
1234
|
+
step.updatedAt = now;
|
|
1235
|
+
plan.updatedAt = now;
|
|
1236
|
+
plan.metadata.version++;
|
|
1237
|
+
await savePlan(fs, plan);
|
|
1238
|
+
return `Step "${step.id}" aggiornato (v${plan.metadata.version}).`;
|
|
1239
|
+
}
|
|
1240
|
+
case "add_step": {
|
|
1241
|
+
if (!input.phaseId) return "phaseId richiesto per add_step";
|
|
1242
|
+
if (!input.stepId || !input.title) return "stepId e title richiesti per add_step";
|
|
1243
|
+
const phase = plan.phases.find((p) => p.id === input.phaseId);
|
|
1244
|
+
if (!phase) return `Fase "${input.phaseId}" non trovata`;
|
|
1245
|
+
const newStep = {
|
|
1246
|
+
id: input.stepId,
|
|
1247
|
+
title: input.title,
|
|
1248
|
+
description: input.description,
|
|
1249
|
+
executionMode: input.executionMode ?? "sequential",
|
|
1250
|
+
status: "idle",
|
|
1251
|
+
priority: input.priority ?? "medium",
|
|
1252
|
+
contract: {
|
|
1253
|
+
inputs: input.inputs ?? [],
|
|
1254
|
+
outputs: input.outputs ?? []
|
|
1255
|
+
},
|
|
1256
|
+
dependencies: input.dependencies ?? [],
|
|
1257
|
+
prompt: input.prompt,
|
|
1258
|
+
requiredTools: input.requiredTools ?? [],
|
|
1259
|
+
subSteps: [],
|
|
1260
|
+
createdAt: now,
|
|
1261
|
+
updatedAt: now
|
|
1262
|
+
};
|
|
1263
|
+
phase.steps.push(newStep);
|
|
1264
|
+
phase.updatedAt = now;
|
|
1265
|
+
plan.updatedAt = now;
|
|
1266
|
+
plan.metadata.version++;
|
|
1267
|
+
const validation = validatePlan(plan);
|
|
1268
|
+
if (!validation.valid) {
|
|
1269
|
+
phase.steps.pop();
|
|
1270
|
+
return `Errore: lo step creerebbe problemi:
|
|
1271
|
+
${validation.errors.join("\n")}`;
|
|
1272
|
+
}
|
|
1273
|
+
await savePlan(fs, plan);
|
|
1274
|
+
return `Step "${input.stepId}" aggiunto alla fase "${input.phaseId}" (v${plan.metadata.version}).`;
|
|
1275
|
+
}
|
|
1276
|
+
case "remove_step": {
|
|
1277
|
+
if (!input.stepId) return "stepId richiesto per remove_step";
|
|
1278
|
+
const { phase } = findStep(plan, input.stepId);
|
|
1279
|
+
if (!phase) return `Step "${input.stepId}" non trovato`;
|
|
1280
|
+
phase.steps = phase.steps.filter((s) => s.id !== input.stepId);
|
|
1281
|
+
phase.updatedAt = now;
|
|
1282
|
+
plan.updatedAt = now;
|
|
1283
|
+
plan.metadata.version++;
|
|
1284
|
+
await savePlan(fs, plan);
|
|
1285
|
+
return `Step "${input.stepId}" rimosso (v${plan.metadata.version}).`;
|
|
1286
|
+
}
|
|
1287
|
+
case "update_phase": {
|
|
1288
|
+
if (!input.phaseId) return "phaseId richiesto per update_phase";
|
|
1289
|
+
const phase = plan.phases.find((p) => p.id === input.phaseId);
|
|
1290
|
+
if (!phase) return `Fase "${input.phaseId}" non trovata`;
|
|
1291
|
+
if (input.title) phase.title = input.title;
|
|
1292
|
+
if (input.description) phase.description = input.description;
|
|
1293
|
+
if (input.executionMode) phase.executionMode = input.executionMode;
|
|
1294
|
+
if (input.dependencies) phase.dependencies = input.dependencies;
|
|
1295
|
+
phase.updatedAt = now;
|
|
1296
|
+
plan.updatedAt = now;
|
|
1297
|
+
plan.metadata.version++;
|
|
1298
|
+
await savePlan(fs, plan);
|
|
1299
|
+
return `Fase "${phase.id}" aggiornata (v${plan.metadata.version}).`;
|
|
1300
|
+
}
|
|
1301
|
+
case "add_phase": {
|
|
1302
|
+
if (!input.phaseId || !input.phaseTitle) {
|
|
1303
|
+
return "phaseId e phaseTitle richiesti per add_phase";
|
|
1304
|
+
}
|
|
1305
|
+
const newPhase = {
|
|
1306
|
+
id: input.phaseId,
|
|
1307
|
+
title: input.phaseTitle,
|
|
1308
|
+
description: input.description,
|
|
1309
|
+
executionMode: input.executionMode ?? "sequential",
|
|
1310
|
+
status: "idle",
|
|
1311
|
+
steps: [],
|
|
1312
|
+
order: input.phaseOrder ?? plan.phases.length,
|
|
1313
|
+
dependencies: input.dependencies ?? [],
|
|
1314
|
+
createdAt: now,
|
|
1315
|
+
updatedAt: now
|
|
1316
|
+
};
|
|
1317
|
+
plan.phases.push(newPhase);
|
|
1318
|
+
plan.updatedAt = now;
|
|
1319
|
+
plan.metadata.version++;
|
|
1320
|
+
await savePlan(fs, plan);
|
|
1321
|
+
return `Fase "${input.phaseId}" aggiunta (v${plan.metadata.version}). Aggiungi step con add_step.`;
|
|
1322
|
+
}
|
|
1323
|
+
case "update_plan_status": {
|
|
1324
|
+
if (!input.planStatus) return "planStatus richiesto";
|
|
1325
|
+
plan.status = input.planStatus;
|
|
1326
|
+
if (input.planStatus === "active" && !plan.startedAt) {
|
|
1327
|
+
plan.startedAt = now;
|
|
1328
|
+
}
|
|
1329
|
+
if (input.planStatus === "completed" || input.planStatus === "failed") {
|
|
1330
|
+
plan.completedAt = now;
|
|
1331
|
+
}
|
|
1332
|
+
plan.updatedAt = now;
|
|
1333
|
+
plan.metadata.version++;
|
|
1334
|
+
await savePlan(fs, plan);
|
|
1335
|
+
return `Piano aggiornato a stato "${input.planStatus}" (v${plan.metadata.version}).`;
|
|
1336
|
+
}
|
|
1337
|
+
case "set_result": {
|
|
1338
|
+
if (!input.stepId) return "stepId richiesto per set_result";
|
|
1339
|
+
const { step } = findStep(plan, input.stepId);
|
|
1340
|
+
if (!step) return `Step "${input.stepId}" non trovato`;
|
|
1341
|
+
step.result = {
|
|
1342
|
+
output: input.resultOutput,
|
|
1343
|
+
data: input.resultData
|
|
1344
|
+
};
|
|
1345
|
+
step.updatedAt = now;
|
|
1346
|
+
plan.updatedAt = now;
|
|
1347
|
+
await savePlan(fs, plan);
|
|
1348
|
+
return `Risultato salvato per step "${input.stepId}".`;
|
|
1349
|
+
}
|
|
1350
|
+
default:
|
|
1351
|
+
return `Azione "${input.action}" non supportata.`;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function findStep(plan, stepId) {
|
|
1357
|
+
for (const phase of plan.phases) {
|
|
1358
|
+
const step = phase.steps.find((s) => s.id === stepId);
|
|
1359
|
+
if (step) return { phase, step };
|
|
1360
|
+
}
|
|
1361
|
+
return {};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/tools/planning/plan-status.tool.ts
|
|
1365
|
+
import { z as z12 } from "zod";
|
|
1366
|
+
function createPlanStatusTool(fs) {
|
|
1367
|
+
return tool({
|
|
1368
|
+
description: "Mostra lo stato corrente del piano con progress tree. Visualizza avanzamento per fase, step e sotto-step.",
|
|
1369
|
+
inputSchema: z12.object({
|
|
1370
|
+
verbose: z12.boolean().optional().default(false).describe("Se true, mostra anche i sotto-step")
|
|
1371
|
+
}),
|
|
1372
|
+
execute: async ({ verbose }) => {
|
|
1373
|
+
const plan = await loadPlan(fs);
|
|
1374
|
+
if (!plan) return "Nessun piano trovato. Usa plan_create prima.";
|
|
1375
|
+
const progress = calculateProgress(plan);
|
|
1376
|
+
return formatProgressTree(progress, plan, verbose);
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
function formatProgressTree(progress, plan, verbose) {
|
|
1381
|
+
const lines = [];
|
|
1382
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
1383
|
+
lines.push(
|
|
1384
|
+
`\u{1F4CB} ${progress.title} [${progress.status}] \u2014 ${pct(progress.progress)} completato`
|
|
1385
|
+
);
|
|
1386
|
+
lines.push(
|
|
1387
|
+
` Step: ${progress.completedSteps}/${progress.totalSteps} completati` + (progress.failedSteps > 0 ? `, ${progress.failedSteps} falliti` : "")
|
|
1388
|
+
);
|
|
1389
|
+
if (progress.elapsedMs) {
|
|
1390
|
+
lines.push(` Tempo: ${Math.round(progress.elapsedMs / 1e3)}s`);
|
|
1391
|
+
}
|
|
1392
|
+
if (progress.tokenUsage) {
|
|
1393
|
+
lines.push(
|
|
1394
|
+
` Token: ${progress.tokenUsage.input + progress.tokenUsage.output} totali`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
lines.push(` Versione: v${plan.metadata.version}`);
|
|
1398
|
+
lines.push("");
|
|
1399
|
+
for (const phase of progress.phases) {
|
|
1400
|
+
const phaseIcon = statusIcon(phase.status);
|
|
1401
|
+
lines.push(
|
|
1402
|
+
`${phaseIcon} Fase: ${phase.title} [${phase.status}] \u2014 ${pct(phase.progress)}`
|
|
1403
|
+
);
|
|
1404
|
+
for (const step of phase.steps) {
|
|
1405
|
+
const stepIcon = statusIcon(step.status);
|
|
1406
|
+
const subInfo = step.subStepsTotal > 0 ? ` (${step.subStepsDone}/${step.subStepsTotal} sotto-step)` : "";
|
|
1407
|
+
lines.push(
|
|
1408
|
+
` ${stepIcon} ${step.stepId}: ${step.title} [${step.status}]${subInfo}`
|
|
1409
|
+
);
|
|
1410
|
+
if (verbose) {
|
|
1411
|
+
const planPhase = plan.phases.find(
|
|
1412
|
+
(p) => p.steps.some((s) => s.id === step.stepId)
|
|
1413
|
+
);
|
|
1414
|
+
const planStep = planPhase?.steps.find((s) => s.id === step.stepId);
|
|
1415
|
+
if (planStep?.subSteps.length) {
|
|
1416
|
+
for (const ss of planStep.subSteps) {
|
|
1417
|
+
const ssIcon = statusIcon(ss.status);
|
|
1418
|
+
lines.push(` ${ssIcon} ${ss.id}: ${ss.title} [${ss.status}]`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (planStep?.dependencies.length) {
|
|
1422
|
+
lines.push(` \u21B3 dipende da: ${planStep.dependencies.join(", ")}`);
|
|
1423
|
+
}
|
|
1424
|
+
if (planStep?.contract.outputs.length) {
|
|
1425
|
+
const outs = planStep.contract.outputs.map((o) => o.name).join(", ");
|
|
1426
|
+
lines.push(` \u21B3 produce: ${outs}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
lines.push("");
|
|
1431
|
+
}
|
|
1432
|
+
return lines.join("\n");
|
|
1433
|
+
}
|
|
1434
|
+
function statusIcon(status) {
|
|
1435
|
+
const icons = {
|
|
1436
|
+
idle: "\u26AA",
|
|
1437
|
+
pending: "\u{1F535}",
|
|
1438
|
+
running: "\u{1F504}",
|
|
1439
|
+
completed: "\u2705",
|
|
1440
|
+
failed: "\u274C",
|
|
1441
|
+
skipped: "\u23ED\uFE0F",
|
|
1442
|
+
blocked: "\u{1F6AB}",
|
|
1443
|
+
cancelled: "\u{1F534}"
|
|
1444
|
+
};
|
|
1445
|
+
return icons[status] ?? "\u2753";
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/tools/planning/plan-visualize.tool.ts
|
|
1449
|
+
import { z as z13 } from "zod";
|
|
1450
|
+
function createPlanVisualizeTool(fs) {
|
|
1451
|
+
return tool({
|
|
1452
|
+
description: "Visualizza il piano come diagramma ASCII o Mermaid. Mostra la gerarchia fasi/step e le dipendenze.",
|
|
1453
|
+
inputSchema: z13.object({
|
|
1454
|
+
format: z13.enum(["ascii", "mermaid"]).default("ascii").describe("Formato di output")
|
|
1455
|
+
}),
|
|
1456
|
+
execute: async ({ format }) => {
|
|
1457
|
+
const plan = await loadPlan(fs);
|
|
1458
|
+
if (!plan) return "Nessun piano trovato. Usa plan_create prima.";
|
|
1459
|
+
return format === "mermaid" ? renderMermaid(plan) : renderAscii(plan);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
function renderMermaid(plan) {
|
|
1464
|
+
const lines = [];
|
|
1465
|
+
lines.push("```mermaid");
|
|
1466
|
+
lines.push("graph TD");
|
|
1467
|
+
lines.push(` subgraph ${sanitize(plan.title)}`);
|
|
1468
|
+
for (const phase of plan.phases) {
|
|
1469
|
+
const phaseLabel = `phase_${phase.id}`;
|
|
1470
|
+
lines.push(` subgraph ${phaseLabel}["${phase.title} (${phase.executionMode})"]`);
|
|
1471
|
+
for (const step of phase.steps) {
|
|
1472
|
+
const shape = stepShape(step);
|
|
1473
|
+
lines.push(` ${step.id}${shape}`);
|
|
1474
|
+
}
|
|
1475
|
+
if (phase.executionMode === "sequential") {
|
|
1476
|
+
for (let i = 1; i < phase.steps.length; i++) {
|
|
1477
|
+
const prev = phase.steps[i - 1];
|
|
1478
|
+
const curr = phase.steps[i];
|
|
1479
|
+
if (!curr.dependencies.includes(prev.id)) {
|
|
1480
|
+
lines.push(` ${prev.id} --> ${curr.id}`);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
lines.push(" end");
|
|
1485
|
+
}
|
|
1486
|
+
for (const phase of plan.phases) {
|
|
1487
|
+
for (const step of phase.steps) {
|
|
1488
|
+
for (const dep of step.dependencies) {
|
|
1489
|
+
lines.push(` ${dep} --> ${step.id}`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
for (const phase of plan.phases) {
|
|
1494
|
+
for (const depPhaseId of phase.dependencies) {
|
|
1495
|
+
const depPhase = plan.phases.find((p) => p.id === depPhaseId);
|
|
1496
|
+
if (depPhase) {
|
|
1497
|
+
const lastStep = depPhase.steps[depPhase.steps.length - 1];
|
|
1498
|
+
const firstStep = phase.steps[0];
|
|
1499
|
+
if (lastStep && firstStep) {
|
|
1500
|
+
lines.push(` ${lastStep.id} ==> ${firstStep.id}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
lines.push(" end");
|
|
1506
|
+
lines.push("```");
|
|
1507
|
+
lines.push("");
|
|
1508
|
+
lines.push("Legenda:");
|
|
1509
|
+
lines.push(" [step] = sequenziale");
|
|
1510
|
+
lines.push(" {step} = condizionale");
|
|
1511
|
+
lines.push(" ((step)) = loop");
|
|
1512
|
+
lines.push(" ==> = dipendenza tra fasi");
|
|
1513
|
+
return lines.join("\n");
|
|
1514
|
+
}
|
|
1515
|
+
function stepShape(step) {
|
|
1516
|
+
const label = `${step.title}`;
|
|
1517
|
+
switch (step.executionMode) {
|
|
1518
|
+
case "conditional":
|
|
1519
|
+
return `{${label}}`;
|
|
1520
|
+
case "loop":
|
|
1521
|
+
return `((${label}))`;
|
|
1522
|
+
case "parallel":
|
|
1523
|
+
return `[/${label}/]`;
|
|
1524
|
+
default:
|
|
1525
|
+
return `["${label}"]`;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function sanitize(s) {
|
|
1529
|
+
return s.replace(/[^a-zA-Z0-9_\- àèéìòù]/g, "_");
|
|
1530
|
+
}
|
|
1531
|
+
function renderAscii(plan) {
|
|
1532
|
+
const lines = [];
|
|
1533
|
+
const W = 72;
|
|
1534
|
+
lines.push("\u2554" + "\u2550".repeat(W) + "\u2557");
|
|
1535
|
+
lines.push(
|
|
1536
|
+
"\u2551" + centerPad(`\u{1F4CB} ${plan.title}`, W) + "\u2551"
|
|
1537
|
+
);
|
|
1538
|
+
lines.push(
|
|
1539
|
+
"\u2551" + centerPad(`Stato: ${plan.status} | v${plan.metadata.version}`, W) + "\u2551"
|
|
1540
|
+
);
|
|
1541
|
+
lines.push("\u2560" + "\u2550".repeat(W) + "\u2563");
|
|
1542
|
+
for (let pi = 0; pi < plan.phases.length; pi++) {
|
|
1543
|
+
const phase = plan.phases[pi];
|
|
1544
|
+
const modeLabel = phase.executionMode === "parallel" ? "\u26A1 PARALLEL" : "\u2192 SEQ";
|
|
1545
|
+
lines.push(
|
|
1546
|
+
"\u2551" + leftPad(
|
|
1547
|
+
` Fase ${pi + 1}: ${phase.title} [${modeLabel}]`,
|
|
1548
|
+
W
|
|
1549
|
+
) + "\u2551"
|
|
1550
|
+
);
|
|
1551
|
+
lines.push("\u2551" + " ".repeat(2) + "\u2500".repeat(W - 2) + "\u2551");
|
|
1552
|
+
for (const step of phase.steps) {
|
|
1553
|
+
const icon = statusIconAscii(step.status);
|
|
1554
|
+
const depStr = step.dependencies.length > 0 ? ` \u2190 [${step.dependencies.join(", ")}]` : "";
|
|
1555
|
+
const modeBadge = step.executionMode !== "sequential" ? ` (${step.executionMode})` : "";
|
|
1556
|
+
lines.push(
|
|
1557
|
+
"\u2551" + leftPad(
|
|
1558
|
+
` ${icon} ${step.id}: ${step.title}${modeBadge}${depStr}`,
|
|
1559
|
+
W
|
|
1560
|
+
) + "\u2551"
|
|
1561
|
+
);
|
|
1562
|
+
for (const ss of step.subSteps) {
|
|
1563
|
+
const ssIcon = statusIconAscii(ss.status);
|
|
1564
|
+
lines.push(
|
|
1565
|
+
"\u2551" + leftPad(` ${ssIcon} \u2514\u2500 ${ss.id}: ${ss.title}`, W) + "\u2551"
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
if (step.contract.outputs.length > 0) {
|
|
1569
|
+
const outs = step.contract.outputs.map((o) => o.name).join(", ");
|
|
1570
|
+
lines.push(
|
|
1571
|
+
"\u2551" + leftPad(` \u21B3 output: ${outs}`, W) + "\u2551"
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (pi < plan.phases.length - 1) {
|
|
1576
|
+
lines.push("\u2551" + " ".repeat(W) + "\u2551");
|
|
1577
|
+
const nextPhase = plan.phases[pi + 1];
|
|
1578
|
+
if (nextPhase.dependencies.includes(phase.id)) {
|
|
1579
|
+
lines.push("\u2551" + centerPad("\u2502", W) + "\u2551");
|
|
1580
|
+
lines.push("\u2551" + centerPad("\u25BC", W) + "\u2551");
|
|
1581
|
+
} else {
|
|
1582
|
+
lines.push("\u2551" + centerPad("\u250A", W) + "\u2551");
|
|
1583
|
+
}
|
|
1584
|
+
lines.push("\u2551" + " ".repeat(W) + "\u2551");
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
lines.push("\u255A" + "\u2550".repeat(W) + "\u255D");
|
|
1588
|
+
return lines.join("\n");
|
|
1589
|
+
}
|
|
1590
|
+
function statusIconAscii(status) {
|
|
1591
|
+
const icons = {
|
|
1592
|
+
idle: "\u25CB",
|
|
1593
|
+
pending: "\u25C9",
|
|
1594
|
+
running: "\u25BA",
|
|
1595
|
+
completed: "\u2713",
|
|
1596
|
+
failed: "\u2717",
|
|
1597
|
+
skipped: "\u2298",
|
|
1598
|
+
blocked: "\u2297",
|
|
1599
|
+
cancelled: "\u2296"
|
|
1600
|
+
};
|
|
1601
|
+
return icons[status] ?? "?";
|
|
1602
|
+
}
|
|
1603
|
+
function centerPad(text, width) {
|
|
1604
|
+
const visLen = [...text].length;
|
|
1605
|
+
if (visLen >= width) return text.slice(0, width);
|
|
1606
|
+
const left = Math.floor((width - visLen) / 2);
|
|
1607
|
+
const right = width - visLen - left;
|
|
1608
|
+
return " ".repeat(left) + text + " ".repeat(right);
|
|
1609
|
+
}
|
|
1610
|
+
function leftPad(text, width) {
|
|
1611
|
+
const visLen = [...text].length;
|
|
1612
|
+
if (visLen >= width) return text.slice(0, width);
|
|
1613
|
+
return text + " ".repeat(width - visLen);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/utils/abstract-builder.ts
|
|
1617
|
+
var AbstractBuilder = class {
|
|
1618
|
+
build() {
|
|
1619
|
+
this.validate();
|
|
1620
|
+
return this.construct();
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// src/adapters/filesystem/glob-utils.ts
|
|
1625
|
+
var regexCache = /* @__PURE__ */ new Map();
|
|
1626
|
+
var MAX_CACHE_SIZE = 500;
|
|
1627
|
+
function globToRegex(pattern) {
|
|
1628
|
+
const cached = regexCache.get(pattern);
|
|
1629
|
+
if (cached) return cached;
|
|
1630
|
+
let result = "";
|
|
1631
|
+
let i = 0;
|
|
1632
|
+
while (i < pattern.length) {
|
|
1633
|
+
const char = pattern[i];
|
|
1634
|
+
if (char === "*" && pattern[i + 1] === "*") {
|
|
1635
|
+
result += ".*";
|
|
1636
|
+
i += pattern[i + 2] === "/" ? 3 : 2;
|
|
1637
|
+
} else if (char === "*") {
|
|
1638
|
+
result += "[^/]*";
|
|
1639
|
+
i++;
|
|
1640
|
+
} else if (char === "?") {
|
|
1641
|
+
result += "[^/]";
|
|
1642
|
+
i++;
|
|
1643
|
+
} else if (".+^${}()|[]\\".includes(char)) {
|
|
1644
|
+
result += "\\" + char;
|
|
1645
|
+
i++;
|
|
1646
|
+
} else {
|
|
1647
|
+
result += char;
|
|
1648
|
+
i++;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const regex = new RegExp("^" + result + "$");
|
|
1652
|
+
if (regexCache.size >= MAX_CACHE_SIZE) {
|
|
1653
|
+
const oldest = regexCache.keys().next().value;
|
|
1654
|
+
regexCache.delete(oldest);
|
|
1655
|
+
}
|
|
1656
|
+
regexCache.set(pattern, regex);
|
|
1657
|
+
return regex;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/adapters/filesystem/virtual-fs.adapter.ts
|
|
1661
|
+
var VirtualFilesystem = class {
|
|
1662
|
+
zones = {
|
|
1663
|
+
transient: /* @__PURE__ */ new Map(),
|
|
1664
|
+
persistent: /* @__PURE__ */ new Map()
|
|
1665
|
+
};
|
|
1666
|
+
basePath;
|
|
1667
|
+
onSync;
|
|
1668
|
+
constructor(options = {}) {
|
|
1669
|
+
this.basePath = options.basePath ?? "";
|
|
1670
|
+
this.onSync = options.onSync;
|
|
1671
|
+
}
|
|
1672
|
+
async read(path, zone = "transient") {
|
|
1673
|
+
const entry = this.zones[zone].get(normalizePath(path));
|
|
1674
|
+
if (!entry || entry.isDirectory) {
|
|
1675
|
+
throw new Error(`File not found: ${path}`);
|
|
1676
|
+
}
|
|
1677
|
+
return entry.content;
|
|
1678
|
+
}
|
|
1679
|
+
async write(path, content, zone = "transient") {
|
|
1680
|
+
const normalized = normalizePath(path);
|
|
1681
|
+
this.ensureParentDirs(normalized, zone);
|
|
1682
|
+
const now = Date.now();
|
|
1683
|
+
const existing = this.zones[zone].get(normalized);
|
|
1684
|
+
this.zones[zone].set(normalized, {
|
|
1685
|
+
content,
|
|
1686
|
+
isDirectory: false,
|
|
1687
|
+
createdAt: existing?.createdAt ?? now,
|
|
1688
|
+
modifiedAt: now
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
async exists(path, zone = "transient") {
|
|
1692
|
+
return this.zones[zone].has(normalizePath(path));
|
|
1693
|
+
}
|
|
1694
|
+
async delete(path, zone = "transient") {
|
|
1695
|
+
const normalized = normalizePath(path);
|
|
1696
|
+
const store = this.zones[zone];
|
|
1697
|
+
const prefix = normalized.endsWith("/") ? normalized : normalized + "/";
|
|
1698
|
+
for (const key of store.keys()) {
|
|
1699
|
+
if (key === normalized || key.startsWith(prefix)) {
|
|
1700
|
+
store.delete(key);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
async list(path, options = {}, zone = "transient") {
|
|
1705
|
+
const normalized = normalizePath(path);
|
|
1706
|
+
const prefix = normalized === "" ? "" : normalized + "/";
|
|
1707
|
+
const maxDepth = options.maxDepth ?? (options.recursive ? Infinity : 1);
|
|
1708
|
+
const results = [];
|
|
1709
|
+
for (const [key, entry] of this.zones[zone]) {
|
|
1710
|
+
if (!key.startsWith(prefix) || key === normalized) continue;
|
|
1711
|
+
const relative = key.slice(prefix.length);
|
|
1712
|
+
if (!options.includeHidden && relative.split("/").some((s) => s.startsWith("."))) continue;
|
|
1713
|
+
const depth = relative.split("/").length;
|
|
1714
|
+
if (depth > maxDepth) continue;
|
|
1715
|
+
results.push({
|
|
1716
|
+
name: relative.split("/").pop(),
|
|
1717
|
+
path: key,
|
|
1718
|
+
isDirectory: entry.isDirectory,
|
|
1719
|
+
size: entry.content.length,
|
|
1720
|
+
modifiedAt: entry.modifiedAt
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
return results;
|
|
1724
|
+
}
|
|
1725
|
+
async stat(path, zone = "transient") {
|
|
1726
|
+
const entry = this.zones[zone].get(normalizePath(path));
|
|
1727
|
+
if (!entry) throw new Error(`Not found: ${path}`);
|
|
1728
|
+
return {
|
|
1729
|
+
size: entry.content.length,
|
|
1730
|
+
isDirectory: entry.isDirectory,
|
|
1731
|
+
isFile: !entry.isDirectory,
|
|
1732
|
+
createdAt: entry.createdAt,
|
|
1733
|
+
modifiedAt: entry.modifiedAt
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
async glob(pattern, zone = "transient") {
|
|
1737
|
+
const regex = globToRegex(pattern);
|
|
1738
|
+
const results = [];
|
|
1739
|
+
for (const [key, entry] of this.zones[zone]) {
|
|
1740
|
+
if (!entry.isDirectory && regex.test(key)) {
|
|
1741
|
+
results.push(key);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return results;
|
|
1745
|
+
}
|
|
1746
|
+
async search(pattern, options = {}, zone = "transient") {
|
|
1747
|
+
const flags = options.caseSensitive === false ? "gi" : "g";
|
|
1748
|
+
const regex = new RegExp(pattern, flags);
|
|
1749
|
+
const fileRegex = options.filePattern ? globToRegex(options.filePattern) : null;
|
|
1750
|
+
const results = [];
|
|
1751
|
+
const max = options.maxResults ?? Infinity;
|
|
1752
|
+
for (const [filePath, entry] of this.zones[zone]) {
|
|
1753
|
+
if (entry.isDirectory) continue;
|
|
1754
|
+
if (fileRegex && !fileRegex.test(filePath)) continue;
|
|
1755
|
+
const lines = entry.content.split("\n");
|
|
1756
|
+
for (let i = 0; i < lines.length && results.length < max; i++) {
|
|
1757
|
+
regex.lastIndex = 0;
|
|
1758
|
+
const match = regex.exec(lines[i]);
|
|
1759
|
+
if (match) {
|
|
1760
|
+
results.push({
|
|
1761
|
+
filePath,
|
|
1762
|
+
lineNumber: i + 1,
|
|
1763
|
+
lineContent: lines[i],
|
|
1764
|
+
matchStart: match.index,
|
|
1765
|
+
matchEnd: match.index + match[0].length
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (results.length >= max) break;
|
|
1770
|
+
}
|
|
1771
|
+
return results;
|
|
1772
|
+
}
|
|
1773
|
+
async syncToPersistent() {
|
|
1774
|
+
if (!this.onSync) {
|
|
1775
|
+
throw new Error("syncToPersistent requires an onSync callback");
|
|
1776
|
+
}
|
|
1777
|
+
for (const [filePath, entry] of this.zones.persistent) {
|
|
1778
|
+
if (entry.isDirectory) continue;
|
|
1779
|
+
const fullPath = this.basePath ? this.basePath + "/" + filePath : filePath;
|
|
1780
|
+
await this.onSync(fullPath, entry.content);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async clearTransient() {
|
|
1784
|
+
this.zones.transient.clear();
|
|
1785
|
+
}
|
|
1786
|
+
ensureParentDirs(filePath, zone) {
|
|
1787
|
+
const parts = filePath.split("/");
|
|
1788
|
+
const now = Date.now();
|
|
1789
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1790
|
+
const dirPath = parts.slice(0, i).join("/");
|
|
1791
|
+
if (dirPath && !this.zones[zone].has(dirPath)) {
|
|
1792
|
+
this.zones[zone].set(dirPath, {
|
|
1793
|
+
content: "",
|
|
1794
|
+
isDirectory: true,
|
|
1795
|
+
createdAt: now,
|
|
1796
|
+
modifiedAt: now
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
function normalizePath(p) {
|
|
1803
|
+
const segments = p.replace(/\/+/g, "/").split("/");
|
|
1804
|
+
const resolved = [];
|
|
1805
|
+
for (const seg of segments) {
|
|
1806
|
+
if (seg === "." || seg === "") continue;
|
|
1807
|
+
if (seg === "..") {
|
|
1808
|
+
resolved.pop();
|
|
1809
|
+
} else {
|
|
1810
|
+
resolved.push(seg);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return resolved.join("/");
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/tools/planning/index.ts
|
|
1817
|
+
function createPlanningTools(fs) {
|
|
1818
|
+
return {
|
|
1819
|
+
// Legacy (retrocompatibilità)
|
|
1820
|
+
write_todos: createWriteTodosTool(fs),
|
|
1821
|
+
review_todos: createReviewTodosTool(fs),
|
|
1822
|
+
// Structured planning
|
|
1823
|
+
plan_create: createPlanCreateTool(fs),
|
|
1824
|
+
plan_update: createPlanUpdateTool(fs),
|
|
1825
|
+
plan_status: createPlanStatusTool(fs),
|
|
1826
|
+
plan_visualize: createPlanVisualizeTool(fs)
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// src/tools/policy/index.ts
|
|
1831
|
+
import { z as z14 } from "zod";
|
|
1832
|
+
var policyRuleSchema = z14.object({
|
|
1833
|
+
id: z14.string().min(1),
|
|
1834
|
+
effect: z14.enum(["allow", "deny"]),
|
|
1835
|
+
resourcePattern: z14.string().min(1),
|
|
1836
|
+
serverPattern: z14.string().optional(),
|
|
1837
|
+
toolPattern: z14.string().optional(),
|
|
1838
|
+
priority: z14.number().int().optional(),
|
|
1839
|
+
reason: z14.string().optional(),
|
|
1840
|
+
context: z14.object({
|
|
1841
|
+
sessionId: z14.string().optional(),
|
|
1842
|
+
userId: z14.string().optional(),
|
|
1843
|
+
tenantId: z14.string().optional()
|
|
1844
|
+
}).optional()
|
|
1845
|
+
});
|
|
1846
|
+
function normalizeRule(input) {
|
|
1847
|
+
const context = input.context ? {
|
|
1848
|
+
...input.context.sessionId ? { sessionId: input.context.sessionId } : {},
|
|
1849
|
+
...input.context.userId ? { userId: input.context.userId } : {},
|
|
1850
|
+
...input.context.tenantId ? { tenantId: input.context.tenantId } : {}
|
|
1851
|
+
} : void 0;
|
|
1852
|
+
return {
|
|
1853
|
+
id: input.id,
|
|
1854
|
+
effect: input.effect,
|
|
1855
|
+
resourcePattern: input.resourcePattern,
|
|
1856
|
+
...input.serverPattern ? { serverPattern: input.serverPattern } : {},
|
|
1857
|
+
...input.toolPattern ? { toolPattern: input.toolPattern } : {},
|
|
1858
|
+
...input.priority !== void 0 ? { priority: input.priority } : {},
|
|
1859
|
+
...input.reason ? { reason: input.reason } : {},
|
|
1860
|
+
...context ? { context } : {}
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function createPolicyTools(policyEngine) {
|
|
1864
|
+
return {
|
|
1865
|
+
policy_list_rules: tool({
|
|
1866
|
+
description: "List current policy rules sorted by priority",
|
|
1867
|
+
inputSchema: z14.object({}),
|
|
1868
|
+
execute: async () => {
|
|
1869
|
+
const rules = await policyEngine.listRules();
|
|
1870
|
+
return { rules, count: rules.length };
|
|
1871
|
+
}
|
|
1872
|
+
}),
|
|
1873
|
+
policy_add_rule: tool({
|
|
1874
|
+
description: "Add a new policy rule (allow or deny)",
|
|
1875
|
+
inputSchema: policyRuleSchema,
|
|
1876
|
+
execute: async (input) => {
|
|
1877
|
+
const rule = normalizeRule(input);
|
|
1878
|
+
await policyEngine.addRule(rule);
|
|
1879
|
+
return { ok: true, ruleId: rule.id };
|
|
1880
|
+
}
|
|
1881
|
+
}),
|
|
1882
|
+
policy_remove_rule: tool({
|
|
1883
|
+
description: "Remove an existing policy rule by id",
|
|
1884
|
+
inputSchema: z14.object({ id: z14.string().min(1) }),
|
|
1885
|
+
execute: async ({ id }) => {
|
|
1886
|
+
const removed = await policyEngine.removeRule(id);
|
|
1887
|
+
return { ok: true, removed, ruleId: id };
|
|
1888
|
+
}
|
|
1889
|
+
}),
|
|
1890
|
+
policy_list_audit: tool({
|
|
1891
|
+
description: "List policy audit records (most recent first)",
|
|
1892
|
+
inputSchema: z14.object({ limit: z14.number().int().positive().optional() }),
|
|
1893
|
+
execute: async ({ limit }) => {
|
|
1894
|
+
const audit = await policyEngine.getAuditLog(limit);
|
|
1895
|
+
return { audit, count: audit.length };
|
|
1896
|
+
}
|
|
1897
|
+
}),
|
|
1898
|
+
policy_clear_audit: tool({
|
|
1899
|
+
description: "Clear policy audit records",
|
|
1900
|
+
inputSchema: z14.object({}),
|
|
1901
|
+
execute: async () => {
|
|
1902
|
+
await policyEngine.clearAuditLog();
|
|
1903
|
+
return { ok: true };
|
|
1904
|
+
}
|
|
1905
|
+
})
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// src/tools/subagent/dispatch.tool.ts
|
|
1910
|
+
import { z as z15 } from "zod";
|
|
1911
|
+
|
|
1912
|
+
// src/tools/subagent/subagent-registry.ts
|
|
1913
|
+
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
1914
|
+
"completed",
|
|
1915
|
+
"failed",
|
|
1916
|
+
"timeout",
|
|
1917
|
+
"cancelled"
|
|
1918
|
+
]);
|
|
1919
|
+
var VALID_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
1920
|
+
["queued", /* @__PURE__ */ new Set(["running", "cancelled"])],
|
|
1921
|
+
["running", /* @__PURE__ */ new Set(["streaming", "completed", "failed", "timeout", "cancelled"])],
|
|
1922
|
+
["streaming", /* @__PURE__ */ new Set(["completed", "failed", "timeout", "cancelled"])],
|
|
1923
|
+
["completed", /* @__PURE__ */ new Set()],
|
|
1924
|
+
["failed", /* @__PURE__ */ new Set()],
|
|
1925
|
+
["timeout", /* @__PURE__ */ new Set()],
|
|
1926
|
+
["cancelled", /* @__PURE__ */ new Set()]
|
|
1927
|
+
]);
|
|
1928
|
+
var DEFAULT_LIMITS = {
|
|
1929
|
+
maxConcurrentPerParent: 5,
|
|
1930
|
+
maxConcurrentGlobal: 50,
|
|
1931
|
+
maxDepth: 3,
|
|
1932
|
+
defaultTimeoutMs: 3e5,
|
|
1933
|
+
maxTimeoutMs: 6e5,
|
|
1934
|
+
maxQueueSize: 100,
|
|
1935
|
+
gcTtlMs: 6e4,
|
|
1936
|
+
gcIntervalMs: 3e4,
|
|
1937
|
+
maxStepsPerSubagent: 20
|
|
1938
|
+
};
|
|
1939
|
+
var SubagentQueueFullError = class extends Error {
|
|
1940
|
+
constructor(queueSize, maxSize) {
|
|
1941
|
+
super(
|
|
1942
|
+
`Subagent queue is full (${queueSize}/${maxSize}). Try again after some tasks complete.`
|
|
1943
|
+
);
|
|
1944
|
+
this.name = "SubagentQueueFullError";
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
var SubagentDepthExceededError = class extends Error {
|
|
1948
|
+
constructor(currentDepth, maxDepth) {
|
|
1949
|
+
super(
|
|
1950
|
+
`Maximum subagent nesting depth exceeded (${currentDepth}/${maxDepth}).`
|
|
1951
|
+
);
|
|
1952
|
+
this.name = "SubagentDepthExceededError";
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
var SubagentQuotaExceededError = class extends Error {
|
|
1956
|
+
constructor(parentId, reason) {
|
|
1957
|
+
super(`Quota exceeded for parent "${parentId}": ${reason}`);
|
|
1958
|
+
this.name = "SubagentQuotaExceededError";
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
var SubagentRegistry = class {
|
|
1962
|
+
handles = /* @__PURE__ */ new Map();
|
|
1963
|
+
parentIndex = /* @__PURE__ */ new Map();
|
|
1964
|
+
limits;
|
|
1965
|
+
eventBus;
|
|
1966
|
+
telemetry;
|
|
1967
|
+
hooks;
|
|
1968
|
+
generateId;
|
|
1969
|
+
gcTimer = null;
|
|
1970
|
+
/** Listeners waiting for a specific task to reach terminal state */
|
|
1971
|
+
completionListeners = /* @__PURE__ */ new Map();
|
|
1972
|
+
/** Scheduler reference — set via setScheduler() after construction */
|
|
1973
|
+
scheduler = null;
|
|
1974
|
+
constructor(eventBus, options) {
|
|
1975
|
+
this.eventBus = eventBus;
|
|
1976
|
+
this.limits = { ...DEFAULT_LIMITS, ...options?.limits };
|
|
1977
|
+
this.telemetry = options?.telemetry;
|
|
1978
|
+
this.hooks = options?.hooks;
|
|
1979
|
+
this.generateId = options?.generateId ?? (() => crypto.randomUUID());
|
|
1980
|
+
}
|
|
1981
|
+
/** Wire the scheduler (called during setup) */
|
|
1982
|
+
setScheduler(scheduler) {
|
|
1983
|
+
this.scheduler = scheduler;
|
|
1984
|
+
}
|
|
1985
|
+
get resourceLimits() {
|
|
1986
|
+
return this.limits;
|
|
1987
|
+
}
|
|
1988
|
+
// -------------------------------------------------------------------------
|
|
1989
|
+
// Lifecycle
|
|
1990
|
+
// -------------------------------------------------------------------------
|
|
1991
|
+
start() {
|
|
1992
|
+
this.gcTimer = setInterval(() => this.gc(), this.limits.gcIntervalMs);
|
|
1993
|
+
}
|
|
1994
|
+
async shutdown() {
|
|
1995
|
+
if (this.gcTimer) {
|
|
1996
|
+
clearInterval(this.gcTimer);
|
|
1997
|
+
this.gcTimer = null;
|
|
1998
|
+
}
|
|
1999
|
+
for (const handle of this.handles.values()) {
|
|
2000
|
+
if (!TERMINAL_STATES.has(handle.status)) {
|
|
2001
|
+
this.cancel(handle.taskId, "registry-shutdown");
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
this.handles.clear();
|
|
2005
|
+
this.parentIndex.clear();
|
|
2006
|
+
this.completionListeners.clear();
|
|
2007
|
+
}
|
|
2008
|
+
// -------------------------------------------------------------------------
|
|
2009
|
+
// Dispatch
|
|
2010
|
+
// -------------------------------------------------------------------------
|
|
2011
|
+
dispatch(parentId, currentDepth, params) {
|
|
2012
|
+
if (currentDepth >= this.limits.maxDepth) {
|
|
2013
|
+
throw new SubagentDepthExceededError(currentDepth, this.limits.maxDepth);
|
|
2014
|
+
}
|
|
2015
|
+
const parentHandles = this.parentIndex.get(parentId);
|
|
2016
|
+
const parentActive = parentHandles ? [...parentHandles].filter((id) => {
|
|
2017
|
+
const h = this.handles.get(id);
|
|
2018
|
+
return h && !TERMINAL_STATES.has(h.status);
|
|
2019
|
+
}).length : 0;
|
|
2020
|
+
if (parentActive >= this.limits.maxConcurrentPerParent) {
|
|
2021
|
+
throw new SubagentQuotaExceededError(
|
|
2022
|
+
parentId,
|
|
2023
|
+
`max concurrent per parent (${this.limits.maxConcurrentPerParent}) reached`
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
const queuedCount = [...this.handles.values()].filter(
|
|
2027
|
+
(h) => h.status === "queued"
|
|
2028
|
+
).length;
|
|
2029
|
+
if (queuedCount >= this.limits.maxQueueSize) {
|
|
2030
|
+
throw new SubagentQueueFullError(queuedCount, this.limits.maxQueueSize);
|
|
2031
|
+
}
|
|
2032
|
+
const timeoutMs = Math.min(
|
|
2033
|
+
params.timeoutMs ?? this.limits.defaultTimeoutMs,
|
|
2034
|
+
this.limits.maxTimeoutMs
|
|
2035
|
+
);
|
|
2036
|
+
const handle = {
|
|
2037
|
+
taskId: this.generateId(),
|
|
2038
|
+
parentId,
|
|
2039
|
+
depth: currentDepth,
|
|
2040
|
+
createdAt: Date.now(),
|
|
2041
|
+
status: "queued",
|
|
2042
|
+
statusChangedAt: Date.now(),
|
|
2043
|
+
priority: params.priority ?? 5,
|
|
2044
|
+
partialOutput: "",
|
|
2045
|
+
finalOutput: null,
|
|
2046
|
+
error: null,
|
|
2047
|
+
abortController: new AbortController(),
|
|
2048
|
+
prompt: params.prompt,
|
|
2049
|
+
instructions: params.instructions ?? null,
|
|
2050
|
+
timeoutMs,
|
|
2051
|
+
timeoutTimer: null,
|
|
2052
|
+
tokenUsage: { input: 0, output: 0 },
|
|
2053
|
+
metadata: params.metadata ?? {}
|
|
2054
|
+
};
|
|
2055
|
+
this.handles.set(handle.taskId, handle);
|
|
2056
|
+
if (!this.parentIndex.has(parentId)) {
|
|
2057
|
+
this.parentIndex.set(parentId, /* @__PURE__ */ new Set());
|
|
2058
|
+
}
|
|
2059
|
+
this.parentIndex.get(parentId).add(handle.taskId);
|
|
2060
|
+
this.eventBus.emit("subagent:spawn", {
|
|
2061
|
+
taskId: handle.taskId,
|
|
2062
|
+
parentId,
|
|
2063
|
+
depth: currentDepth,
|
|
2064
|
+
prompt: params.prompt,
|
|
2065
|
+
priority: handle.priority
|
|
2066
|
+
});
|
|
2067
|
+
this.eventBus.emit("delegation:start", {
|
|
2068
|
+
taskId: handle.taskId,
|
|
2069
|
+
parentId,
|
|
2070
|
+
depth: currentDepth,
|
|
2071
|
+
prompt: params.prompt,
|
|
2072
|
+
priority: handle.priority,
|
|
2073
|
+
timeoutMs: handle.timeoutMs,
|
|
2074
|
+
metadata: handle.metadata
|
|
2075
|
+
});
|
|
2076
|
+
this.telemetry?.recordMetric("subagent.dispatch.count", 1);
|
|
2077
|
+
this.scheduler?.enqueue(handle);
|
|
2078
|
+
return handle;
|
|
2079
|
+
}
|
|
2080
|
+
// -------------------------------------------------------------------------
|
|
2081
|
+
// Status Transitions
|
|
2082
|
+
// -------------------------------------------------------------------------
|
|
2083
|
+
transition(taskId, newStatus, data) {
|
|
2084
|
+
const handle = this.handles.get(taskId);
|
|
2085
|
+
if (!handle) return;
|
|
2086
|
+
if (TERMINAL_STATES.has(handle.status)) return;
|
|
2087
|
+
const allowedTargets = VALID_TRANSITIONS.get(handle.status);
|
|
2088
|
+
if (!allowedTargets || !allowedTargets.has(newStatus)) return;
|
|
2089
|
+
const previousStatus = handle.status;
|
|
2090
|
+
handle.status = newStatus;
|
|
2091
|
+
handle.statusChangedAt = Date.now();
|
|
2092
|
+
if (data?.partialOutput !== void 0) {
|
|
2093
|
+
handle.partialOutput += data.partialOutput;
|
|
2094
|
+
}
|
|
2095
|
+
if (data?.finalOutput !== void 0) {
|
|
2096
|
+
handle.finalOutput = data.finalOutput;
|
|
2097
|
+
}
|
|
2098
|
+
if (data?.error !== void 0) {
|
|
2099
|
+
handle.error = data.error;
|
|
2100
|
+
}
|
|
2101
|
+
this.eventBus.emit("subagent:status-change", {
|
|
2102
|
+
taskId,
|
|
2103
|
+
previousStatus,
|
|
2104
|
+
newStatus,
|
|
2105
|
+
parentId: handle.parentId
|
|
2106
|
+
});
|
|
2107
|
+
this.eventBus.emit("delegation:iteration", {
|
|
2108
|
+
taskId,
|
|
2109
|
+
parentId: handle.parentId,
|
|
2110
|
+
previousStatus,
|
|
2111
|
+
status: newStatus,
|
|
2112
|
+
durationMs: Date.now() - handle.createdAt,
|
|
2113
|
+
tokenUsage: handle.tokenUsage
|
|
2114
|
+
});
|
|
2115
|
+
this.invokeIterationHook(handle, previousStatus, newStatus);
|
|
2116
|
+
if (TERMINAL_STATES.has(newStatus)) {
|
|
2117
|
+
if (handle.timeoutTimer) {
|
|
2118
|
+
clearTimeout(handle.timeoutTimer);
|
|
2119
|
+
handle.timeoutTimer = null;
|
|
2120
|
+
}
|
|
2121
|
+
this.eventBus.emit("subagent:complete", {
|
|
2122
|
+
taskId,
|
|
2123
|
+
status: newStatus,
|
|
2124
|
+
parentId: handle.parentId,
|
|
2125
|
+
durationMs: Date.now() - handle.createdAt,
|
|
2126
|
+
tokenUsage: handle.tokenUsage
|
|
2127
|
+
});
|
|
2128
|
+
this.eventBus.emit("delegation:complete", {
|
|
2129
|
+
taskId,
|
|
2130
|
+
status: newStatus,
|
|
2131
|
+
parentId: handle.parentId,
|
|
2132
|
+
durationMs: Date.now() - handle.createdAt,
|
|
2133
|
+
tokenUsage: handle.tokenUsage,
|
|
2134
|
+
error: handle.error
|
|
2135
|
+
});
|
|
2136
|
+
this.telemetry?.recordMetric(`subagent.status.${newStatus}`, 1);
|
|
2137
|
+
this.telemetry?.recordMetric(
|
|
2138
|
+
"subagent.completion.duration_ms",
|
|
2139
|
+
Date.now() - handle.createdAt
|
|
2140
|
+
);
|
|
2141
|
+
this.invokeCompletionHook(handle);
|
|
2142
|
+
const listeners = this.completionListeners.get(taskId);
|
|
2143
|
+
if (listeners) {
|
|
2144
|
+
for (const cb of listeners) cb(handle);
|
|
2145
|
+
this.completionListeners.delete(taskId);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
// -------------------------------------------------------------------------
|
|
2150
|
+
// Query
|
|
2151
|
+
// -------------------------------------------------------------------------
|
|
2152
|
+
invokeIterationHook(handle, previousStatus, status) {
|
|
2153
|
+
if (!this.hooks?.onIterationComplete) return;
|
|
2154
|
+
void Promise.resolve(
|
|
2155
|
+
this.hooks.onIterationComplete({
|
|
2156
|
+
taskId: handle.taskId,
|
|
2157
|
+
parentId: handle.parentId,
|
|
2158
|
+
previousStatus,
|
|
2159
|
+
status,
|
|
2160
|
+
partialOutput: handle.partialOutput,
|
|
2161
|
+
finalOutput: handle.finalOutput,
|
|
2162
|
+
error: handle.error,
|
|
2163
|
+
durationMs: Date.now() - handle.createdAt,
|
|
2164
|
+
tokenUsage: handle.tokenUsage,
|
|
2165
|
+
metadata: handle.metadata
|
|
2166
|
+
})
|
|
2167
|
+
).then((result) => {
|
|
2168
|
+
if (!result) return;
|
|
2169
|
+
if (result.shouldEscalate) {
|
|
2170
|
+
this.eventBus.emit("delegation:blocked", {
|
|
2171
|
+
taskId: handle.taskId,
|
|
2172
|
+
parentId: handle.parentId,
|
|
2173
|
+
reason: result.reason ?? "delegation escalation requested",
|
|
2174
|
+
score: result.score
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
}).catch(() => {
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
invokeCompletionHook(handle) {
|
|
2181
|
+
if (!this.hooks?.onDelegationComplete) return;
|
|
2182
|
+
void Promise.resolve(
|
|
2183
|
+
this.hooks.onDelegationComplete({
|
|
2184
|
+
taskId: handle.taskId,
|
|
2185
|
+
parentId: handle.parentId,
|
|
2186
|
+
status: handle.status,
|
|
2187
|
+
finalOutput: handle.finalOutput,
|
|
2188
|
+
error: handle.error,
|
|
2189
|
+
durationMs: Date.now() - handle.createdAt,
|
|
2190
|
+
tokenUsage: handle.tokenUsage,
|
|
2191
|
+
metadata: handle.metadata
|
|
2192
|
+
})
|
|
2193
|
+
).catch(() => {
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
get(taskId) {
|
|
2197
|
+
return this.handles.get(taskId);
|
|
2198
|
+
}
|
|
2199
|
+
getByParent(parentId) {
|
|
2200
|
+
const ids = this.parentIndex.get(parentId);
|
|
2201
|
+
if (!ids) return [];
|
|
2202
|
+
return [...ids].map((id) => this.handles.get(id)).filter(Boolean);
|
|
2203
|
+
}
|
|
2204
|
+
get activeCount() {
|
|
2205
|
+
return [...this.handles.values()].filter(
|
|
2206
|
+
(h) => h.status === "running" || h.status === "streaming"
|
|
2207
|
+
).length;
|
|
2208
|
+
}
|
|
2209
|
+
get queuedCount() {
|
|
2210
|
+
return [...this.handles.values()].filter(
|
|
2211
|
+
(h) => h.status === "queued"
|
|
2212
|
+
).length;
|
|
2213
|
+
}
|
|
2214
|
+
get totalCount() {
|
|
2215
|
+
return this.handles.size;
|
|
2216
|
+
}
|
|
2217
|
+
// -------------------------------------------------------------------------
|
|
2218
|
+
// Await
|
|
2219
|
+
// -------------------------------------------------------------------------
|
|
2220
|
+
waitForCompletion(taskId, timeoutMs) {
|
|
2221
|
+
const handle = this.handles.get(taskId);
|
|
2222
|
+
if (!handle) {
|
|
2223
|
+
return Promise.reject(new Error(`Task "${taskId}" not found`));
|
|
2224
|
+
}
|
|
2225
|
+
if (TERMINAL_STATES.has(handle.status)) {
|
|
2226
|
+
return Promise.resolve(handle);
|
|
2227
|
+
}
|
|
2228
|
+
return new Promise((resolve) => {
|
|
2229
|
+
const timer = setTimeout(() => {
|
|
2230
|
+
const listeners = this.completionListeners.get(taskId);
|
|
2231
|
+
if (listeners) {
|
|
2232
|
+
const idx = listeners.indexOf(onComplete);
|
|
2233
|
+
if (idx !== -1) listeners.splice(idx, 1);
|
|
2234
|
+
}
|
|
2235
|
+
resolve(handle);
|
|
2236
|
+
}, timeoutMs);
|
|
2237
|
+
const onComplete = (h) => {
|
|
2238
|
+
clearTimeout(timer);
|
|
2239
|
+
resolve(h);
|
|
2240
|
+
};
|
|
2241
|
+
if (!this.completionListeners.has(taskId)) {
|
|
2242
|
+
this.completionListeners.set(taskId, []);
|
|
2243
|
+
}
|
|
2244
|
+
this.completionListeners.get(taskId).push(onComplete);
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
// -------------------------------------------------------------------------
|
|
2248
|
+
// Cancellation
|
|
2249
|
+
// -------------------------------------------------------------------------
|
|
2250
|
+
cancel(taskId, reason = "cancelled") {
|
|
2251
|
+
const handle = this.handles.get(taskId);
|
|
2252
|
+
if (!handle || TERMINAL_STATES.has(handle.status)) return false;
|
|
2253
|
+
handle.abortController.abort(reason);
|
|
2254
|
+
this.transition(taskId, "cancelled", { error: reason });
|
|
2255
|
+
for (const h of this.handles.values()) {
|
|
2256
|
+
if (h.parentId === taskId && !TERMINAL_STATES.has(h.status)) {
|
|
2257
|
+
this.cancel(h.taskId, `parent-cancelled:${reason}`);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return true;
|
|
2261
|
+
}
|
|
2262
|
+
cancelAll(parentId) {
|
|
2263
|
+
let count = 0;
|
|
2264
|
+
const ids = this.parentIndex.get(parentId);
|
|
2265
|
+
if (!ids) return 0;
|
|
2266
|
+
for (const id of ids) {
|
|
2267
|
+
if (this.cancel(id, "parent-shutdown")) count++;
|
|
2268
|
+
}
|
|
2269
|
+
return count;
|
|
2270
|
+
}
|
|
2271
|
+
// -------------------------------------------------------------------------
|
|
2272
|
+
// Garbage Collection
|
|
2273
|
+
// -------------------------------------------------------------------------
|
|
2274
|
+
/** Exposed for testing */
|
|
2275
|
+
gc() {
|
|
2276
|
+
const now = Date.now();
|
|
2277
|
+
let collected = 0;
|
|
2278
|
+
for (const [taskId, handle] of this.handles) {
|
|
2279
|
+
if (TERMINAL_STATES.has(handle.status) && now - handle.statusChangedAt > this.limits.gcTtlMs) {
|
|
2280
|
+
this.handles.delete(taskId);
|
|
2281
|
+
this.parentIndex.get(handle.parentId)?.delete(taskId);
|
|
2282
|
+
this.completionListeners.delete(taskId);
|
|
2283
|
+
collected++;
|
|
2284
|
+
}
|
|
2285
|
+
if ((handle.status === "running" || handle.status === "streaming") && now - handle.createdAt > handle.timeoutMs * 2) {
|
|
2286
|
+
this.cancel(taskId, "watchdog-timeout");
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (this.handles.size > this.limits.maxConcurrentGlobal * 10) {
|
|
2290
|
+
for (const [taskId, handle] of this.handles) {
|
|
2291
|
+
if (TERMINAL_STATES.has(handle.status)) {
|
|
2292
|
+
this.handles.delete(taskId);
|
|
2293
|
+
this.parentIndex.get(handle.parentId)?.delete(taskId);
|
|
2294
|
+
this.completionListeners.delete(taskId);
|
|
2295
|
+
collected++;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
if (collected > 0) {
|
|
2300
|
+
this.telemetry?.recordMetric("subagent.gc.collected", collected);
|
|
2301
|
+
}
|
|
2302
|
+
this.telemetry?.recordMetric("subagent.queue.depth", this.queuedCount);
|
|
2303
|
+
this.telemetry?.recordMetric("subagent.active.count", this.activeCount);
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
function isTerminalStatus(status) {
|
|
2307
|
+
return TERMINAL_STATES.has(status);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// src/tools/subagent/dispatch.tool.ts
|
|
2311
|
+
var DispatchSubagentInputSchema = z15.object({
|
|
2312
|
+
prompt: z15.string().min(1).max(1e4).describe("Task description for the subagent"),
|
|
2313
|
+
instructions: z15.string().max(5e3).optional().describe("Optional system instructions for the subagent"),
|
|
2314
|
+
priority: z15.number().int().min(1).max(10).default(5).describe("Priority (1=highest, 10=lowest). Default: 5"),
|
|
2315
|
+
timeoutMs: z15.number().int().min(5e3).max(6e5).optional().describe("Timeout in milliseconds. Default: 300000 (5 min)"),
|
|
2316
|
+
metadata: z15.record(z15.string(), z15.unknown()).optional().describe("Optional metadata to pass to the subagent")
|
|
2317
|
+
});
|
|
2318
|
+
function createDispatchTool(config) {
|
|
2319
|
+
const { registry, parentId, currentDepth, hooks } = config;
|
|
2320
|
+
return tool({
|
|
2321
|
+
description: "Dispatch a subtask to a specialized subagent. Returns immediately with a taskId. Use poll_subagent to check progress, or await_subagent to wait for completion. You can dispatch multiple tasks in a single step for parallel execution.",
|
|
2322
|
+
inputSchema: DispatchSubagentInputSchema,
|
|
2323
|
+
execute: async (input) => {
|
|
2324
|
+
try {
|
|
2325
|
+
let prompt = input.prompt;
|
|
2326
|
+
let instructions = input.instructions;
|
|
2327
|
+
let priority = input.priority;
|
|
2328
|
+
let timeoutMs = input.timeoutMs;
|
|
2329
|
+
let metadata = input.metadata;
|
|
2330
|
+
if (hooks?.onDelegationStart) {
|
|
2331
|
+
const hookResult = await hooks.onDelegationStart({
|
|
2332
|
+
parentId,
|
|
2333
|
+
currentDepth,
|
|
2334
|
+
prompt,
|
|
2335
|
+
instructions,
|
|
2336
|
+
priority,
|
|
2337
|
+
timeoutMs,
|
|
2338
|
+
metadata
|
|
2339
|
+
});
|
|
2340
|
+
if (hookResult?.allow === false) {
|
|
2341
|
+
return JSON.stringify({
|
|
2342
|
+
blocked: true,
|
|
2343
|
+
error: hookResult.reason ?? "Delegation blocked by supervisor hook"
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
if (hookResult?.prompt !== void 0) {
|
|
2347
|
+
prompt = hookResult.prompt;
|
|
2348
|
+
}
|
|
2349
|
+
if (hookResult?.instructions !== void 0) {
|
|
2350
|
+
instructions = hookResult.instructions;
|
|
2351
|
+
}
|
|
2352
|
+
if (hookResult?.priority !== void 0) {
|
|
2353
|
+
priority = hookResult.priority;
|
|
2354
|
+
}
|
|
2355
|
+
if (hookResult?.timeoutMs !== void 0) {
|
|
2356
|
+
timeoutMs = hookResult.timeoutMs;
|
|
2357
|
+
}
|
|
2358
|
+
if (hookResult?.metadata !== void 0) {
|
|
2359
|
+
metadata = hookResult.metadata;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
const handle = registry.dispatch(parentId, currentDepth, {
|
|
2363
|
+
prompt,
|
|
2364
|
+
instructions,
|
|
2365
|
+
priority,
|
|
2366
|
+
timeoutMs,
|
|
2367
|
+
metadata
|
|
2368
|
+
});
|
|
2369
|
+
return JSON.stringify({
|
|
2370
|
+
taskId: handle.taskId,
|
|
2371
|
+
status: "queued",
|
|
2372
|
+
queuePosition: registry.queuedCount,
|
|
2373
|
+
message: "Task dispatched. Use poll_subagent or await_subagent to get results."
|
|
2374
|
+
});
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
if (error instanceof SubagentQueueFullError || error instanceof SubagentDepthExceededError || error instanceof SubagentQuotaExceededError) {
|
|
2377
|
+
return JSON.stringify({ error: error.message });
|
|
2378
|
+
}
|
|
2379
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2380
|
+
return JSON.stringify({ error: `Dispatch failed: ${msg}` });
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/tools/subagent/poll.tool.ts
|
|
2387
|
+
import { z as z16 } from "zod";
|
|
2388
|
+
var PollSubagentInputSchema = z16.object({
|
|
2389
|
+
taskIds: z16.array(z16.string()).min(1).max(50).describe("Task IDs to check status for"),
|
|
2390
|
+
includePartialOutput: z16.boolean().default(true).describe("Whether to include partial output from streaming tasks"),
|
|
2391
|
+
maxPartialOutputLength: z16.number().int().min(0).max(1e4).default(2e3).describe("Maximum characters of partial output to return per task")
|
|
2392
|
+
});
|
|
2393
|
+
function createPollTool(config) {
|
|
2394
|
+
const { registry } = config;
|
|
2395
|
+
return tool({
|
|
2396
|
+
description: "Check the status of dispatched subtasks. Returns current status and partial output. Never blocks \u2014 returns immediately.",
|
|
2397
|
+
inputSchema: PollSubagentInputSchema,
|
|
2398
|
+
execute: async (input) => {
|
|
2399
|
+
const summary = {
|
|
2400
|
+
total: 0,
|
|
2401
|
+
queued: 0,
|
|
2402
|
+
running: 0,
|
|
2403
|
+
streaming: 0,
|
|
2404
|
+
completed: 0,
|
|
2405
|
+
failed: 0,
|
|
2406
|
+
timeout: 0,
|
|
2407
|
+
cancelled: 0
|
|
2408
|
+
};
|
|
2409
|
+
const tasks = input.taskIds.map((taskId) => {
|
|
2410
|
+
summary.total++;
|
|
2411
|
+
const handle = registry.get(taskId);
|
|
2412
|
+
if (!handle) {
|
|
2413
|
+
return {
|
|
2414
|
+
taskId,
|
|
2415
|
+
status: "not_found",
|
|
2416
|
+
error: "Task not found",
|
|
2417
|
+
durationMs: 0
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
const status = handle.status;
|
|
2421
|
+
if (status in summary) summary[status]++;
|
|
2422
|
+
const result = {
|
|
2423
|
+
taskId,
|
|
2424
|
+
status,
|
|
2425
|
+
durationMs: Date.now() - handle.createdAt
|
|
2426
|
+
};
|
|
2427
|
+
if (input.includePartialOutput && (status === "streaming" || status === "running") && handle.partialOutput) {
|
|
2428
|
+
result.partialOutput = handle.partialOutput.slice(
|
|
2429
|
+
-input.maxPartialOutputLength
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
if (status === "completed" && handle.finalOutput) {
|
|
2433
|
+
result.finalOutput = handle.finalOutput;
|
|
2434
|
+
}
|
|
2435
|
+
if ((status === "failed" || status === "timeout" || status === "cancelled") && handle.error) {
|
|
2436
|
+
result.error = handle.error;
|
|
2437
|
+
}
|
|
2438
|
+
if (handle.tokenUsage.input > 0 || handle.tokenUsage.output > 0) {
|
|
2439
|
+
result.tokenUsage = handle.tokenUsage;
|
|
2440
|
+
}
|
|
2441
|
+
return result;
|
|
2442
|
+
});
|
|
2443
|
+
return JSON.stringify({ tasks, summary }, null, 2);
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// src/tools/subagent/await.tool.ts
|
|
2449
|
+
import { z as z17 } from "zod";
|
|
2450
|
+
var AwaitSubagentInputSchema = z17.object({
|
|
2451
|
+
taskIds: z17.array(z17.string()).min(1).max(50).describe("Task IDs to wait for"),
|
|
2452
|
+
timeoutMs: z17.number().int().min(1e3).max(6e5).default(6e4).describe("Maximum time to wait in milliseconds. Default: 60000"),
|
|
2453
|
+
pollIntervalMs: z17.number().int().min(50).max(5e3).default(200).describe("Polling interval used for optional completion checks")
|
|
2454
|
+
});
|
|
2455
|
+
function createAwaitTool(config) {
|
|
2456
|
+
const { registry, hooks } = config;
|
|
2457
|
+
const wait = (ms) => new Promise((resolve) => {
|
|
2458
|
+
setTimeout(resolve, ms);
|
|
2459
|
+
});
|
|
2460
|
+
const normalizeCompletionResult = (result) => {
|
|
2461
|
+
if (typeof result === "boolean") {
|
|
2462
|
+
return { isComplete: result };
|
|
2463
|
+
}
|
|
2464
|
+
if (!result) {
|
|
2465
|
+
return { isComplete: false };
|
|
2466
|
+
}
|
|
2467
|
+
return {
|
|
2468
|
+
isComplete: result.isComplete,
|
|
2469
|
+
reason: result.reason
|
|
2470
|
+
};
|
|
2471
|
+
};
|
|
2472
|
+
return tool({
|
|
2473
|
+
description: "Wait for one or more subtasks to complete. Blocks until all are done, timed out, or cancelled. Returns partial results if timeout is reached. Use poll_subagent for non-blocking checks.",
|
|
2474
|
+
inputSchema: AwaitSubagentInputSchema,
|
|
2475
|
+
execute: async (input) => {
|
|
2476
|
+
const results = await Promise.allSettled(
|
|
2477
|
+
input.taskIds.map(async (taskId) => {
|
|
2478
|
+
const handle = registry.get(taskId);
|
|
2479
|
+
if (!handle) {
|
|
2480
|
+
return {
|
|
2481
|
+
taskId,
|
|
2482
|
+
status: "not_found",
|
|
2483
|
+
error: "Task not found. It may have been garbage collected."
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
let completionOverride = false;
|
|
2487
|
+
let completionReason;
|
|
2488
|
+
let resolved = handle;
|
|
2489
|
+
if (!hooks?.isTaskComplete) {
|
|
2490
|
+
resolved = await registry.waitForCompletion(
|
|
2491
|
+
taskId,
|
|
2492
|
+
input.timeoutMs
|
|
2493
|
+
);
|
|
2494
|
+
} else {
|
|
2495
|
+
const startedAt = Date.now();
|
|
2496
|
+
let iterations = 0;
|
|
2497
|
+
while (Date.now() - startedAt < input.timeoutMs) {
|
|
2498
|
+
const current = registry.get(taskId);
|
|
2499
|
+
if (!current) {
|
|
2500
|
+
return {
|
|
2501
|
+
taskId,
|
|
2502
|
+
status: "not_found",
|
|
2503
|
+
error: "Task not found. It may have been garbage collected."
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
resolved = current;
|
|
2507
|
+
if (isTerminalStatus(current.status)) {
|
|
2508
|
+
break;
|
|
2509
|
+
}
|
|
2510
|
+
try {
|
|
2511
|
+
const decision = normalizeCompletionResult(
|
|
2512
|
+
await hooks.isTaskComplete({
|
|
2513
|
+
taskId: current.taskId,
|
|
2514
|
+
parentId: current.parentId,
|
|
2515
|
+
status: current.status,
|
|
2516
|
+
partialOutput: current.partialOutput,
|
|
2517
|
+
finalOutput: current.finalOutput,
|
|
2518
|
+
error: current.error,
|
|
2519
|
+
elapsedMs: Date.now() - current.createdAt,
|
|
2520
|
+
iterations,
|
|
2521
|
+
tokenUsage: current.tokenUsage,
|
|
2522
|
+
metadata: current.metadata
|
|
2523
|
+
})
|
|
2524
|
+
);
|
|
2525
|
+
if (decision.isComplete) {
|
|
2526
|
+
completionOverride = true;
|
|
2527
|
+
completionReason = decision.reason;
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
} catch {
|
|
2531
|
+
}
|
|
2532
|
+
iterations++;
|
|
2533
|
+
await wait(input.pollIntervalMs);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
const response = {
|
|
2537
|
+
taskId: resolved.taskId,
|
|
2538
|
+
status: resolved.status,
|
|
2539
|
+
output: resolved.finalOutput ?? void 0,
|
|
2540
|
+
error: resolved.error ?? void 0,
|
|
2541
|
+
durationMs: Date.now() - resolved.createdAt,
|
|
2542
|
+
tokenUsage: resolved.tokenUsage
|
|
2543
|
+
};
|
|
2544
|
+
if (completionOverride) {
|
|
2545
|
+
response.completionOverride = true;
|
|
2546
|
+
if (completionReason) {
|
|
2547
|
+
response.completionReason = completionReason;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return response;
|
|
2551
|
+
})
|
|
2552
|
+
);
|
|
2553
|
+
const formatted = results.map((r) => {
|
|
2554
|
+
if (r.status === "fulfilled") return r.value;
|
|
2555
|
+
return {
|
|
2556
|
+
status: "error",
|
|
2557
|
+
error: r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
2558
|
+
};
|
|
2559
|
+
});
|
|
2560
|
+
return JSON.stringify(formatted, null, 2);
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/tools/subagent/subagent-scheduler.ts
|
|
2566
|
+
var DEFAULT_POOL_CONFIG = {
|
|
2567
|
+
minWorkers: 2,
|
|
2568
|
+
maxWorkers: 20,
|
|
2569
|
+
scaleUpThreshold: 0.8,
|
|
2570
|
+
scaleDownThreshold: 0.3,
|
|
2571
|
+
resizeCooldownMs: 1e4
|
|
2572
|
+
};
|
|
2573
|
+
var PriorityQueue = class {
|
|
2574
|
+
heap = [];
|
|
2575
|
+
agingIntervalMs;
|
|
2576
|
+
constructor(agingIntervalMs = 5e3) {
|
|
2577
|
+
this.agingIntervalMs = agingIntervalMs;
|
|
2578
|
+
}
|
|
2579
|
+
get size() {
|
|
2580
|
+
return this.heap.length;
|
|
2581
|
+
}
|
|
2582
|
+
enqueue(handle) {
|
|
2583
|
+
const entry = {
|
|
2584
|
+
handle,
|
|
2585
|
+
effectivePriority: handle.priority,
|
|
2586
|
+
enqueuedAt: Date.now()
|
|
2587
|
+
};
|
|
2588
|
+
this.heap.push(entry);
|
|
2589
|
+
this.bubbleUp(this.heap.length - 1);
|
|
2590
|
+
}
|
|
2591
|
+
dequeue() {
|
|
2592
|
+
if (this.heap.length === 0) return null;
|
|
2593
|
+
this.refreshPriorities();
|
|
2594
|
+
const top = this.heap[0];
|
|
2595
|
+
const last = this.heap.pop();
|
|
2596
|
+
if (this.heap.length > 0) {
|
|
2597
|
+
this.heap[0] = last;
|
|
2598
|
+
this.sinkDown(0);
|
|
2599
|
+
}
|
|
2600
|
+
return top.handle;
|
|
2601
|
+
}
|
|
2602
|
+
peek() {
|
|
2603
|
+
return this.heap.length > 0 ? this.heap[0].handle : null;
|
|
2604
|
+
}
|
|
2605
|
+
remove(taskId) {
|
|
2606
|
+
const idx = this.heap.findIndex((e) => e.handle.taskId === taskId);
|
|
2607
|
+
if (idx === -1) return false;
|
|
2608
|
+
const last = this.heap.pop();
|
|
2609
|
+
if (idx < this.heap.length) {
|
|
2610
|
+
this.heap[idx] = last;
|
|
2611
|
+
this.bubbleUp(idx);
|
|
2612
|
+
this.sinkDown(idx);
|
|
2613
|
+
}
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
refreshPriorities() {
|
|
2617
|
+
const now = Date.now();
|
|
2618
|
+
let dirty = false;
|
|
2619
|
+
for (const entry of this.heap) {
|
|
2620
|
+
const ageBonus = Math.floor(
|
|
2621
|
+
(now - entry.enqueuedAt) / this.agingIntervalMs
|
|
2622
|
+
);
|
|
2623
|
+
const newPriority = Math.max(1, entry.handle.priority - ageBonus);
|
|
2624
|
+
if (newPriority !== entry.effectivePriority) {
|
|
2625
|
+
entry.effectivePriority = newPriority;
|
|
2626
|
+
dirty = true;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
if (dirty) {
|
|
2630
|
+
for (let i = Math.floor(this.heap.length / 2) - 1; i >= 0; i--) {
|
|
2631
|
+
this.sinkDown(i);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
bubbleUp(idx) {
|
|
2636
|
+
while (idx > 0) {
|
|
2637
|
+
const parentIdx = Math.floor((idx - 1) / 2);
|
|
2638
|
+
if (this.heap[parentIdx].effectivePriority <= this.heap[idx].effectivePriority)
|
|
2639
|
+
break;
|
|
2640
|
+
[this.heap[parentIdx], this.heap[idx]] = [
|
|
2641
|
+
this.heap[idx],
|
|
2642
|
+
this.heap[parentIdx]
|
|
2643
|
+
];
|
|
2644
|
+
idx = parentIdx;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
sinkDown(idx) {
|
|
2648
|
+
const length = this.heap.length;
|
|
2649
|
+
while (true) {
|
|
2650
|
+
let smallest = idx;
|
|
2651
|
+
const left = 2 * idx + 1;
|
|
2652
|
+
const right = 2 * idx + 2;
|
|
2653
|
+
if (left < length && this.heap[left].effectivePriority < this.heap[smallest].effectivePriority) {
|
|
2654
|
+
smallest = left;
|
|
2655
|
+
}
|
|
2656
|
+
if (right < length && this.heap[right].effectivePriority < this.heap[smallest].effectivePriority) {
|
|
2657
|
+
smallest = right;
|
|
2658
|
+
}
|
|
2659
|
+
if (smallest === idx) break;
|
|
2660
|
+
[this.heap[smallest], this.heap[idx]] = [
|
|
2661
|
+
this.heap[idx],
|
|
2662
|
+
this.heap[smallest]
|
|
2663
|
+
];
|
|
2664
|
+
idx = smallest;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
var SubagentScheduler = class {
|
|
2669
|
+
registry;
|
|
2670
|
+
queue;
|
|
2671
|
+
poolConfig;
|
|
2672
|
+
model;
|
|
2673
|
+
limits;
|
|
2674
|
+
telemetry;
|
|
2675
|
+
currentPoolSize;
|
|
2676
|
+
activeWorkers = 0;
|
|
2677
|
+
lastResizeAt = 0;
|
|
2678
|
+
drainTimer = null;
|
|
2679
|
+
circuitBreakers = /* @__PURE__ */ new Map();
|
|
2680
|
+
cbConfig = {
|
|
2681
|
+
failureThreshold: 3,
|
|
2682
|
+
monitorWindowMs: 6e4,
|
|
2683
|
+
resetTimeoutMs: 3e4
|
|
2684
|
+
};
|
|
2685
|
+
constructor(registry, model, limits, options) {
|
|
2686
|
+
this.registry = registry;
|
|
2687
|
+
this.model = model;
|
|
2688
|
+
this.limits = limits;
|
|
2689
|
+
this.poolConfig = { ...DEFAULT_POOL_CONFIG, ...options?.poolConfig };
|
|
2690
|
+
this.telemetry = options?.telemetry;
|
|
2691
|
+
this.queue = new PriorityQueue();
|
|
2692
|
+
this.currentPoolSize = this.poolConfig.minWorkers;
|
|
2693
|
+
registry.setScheduler(this);
|
|
2694
|
+
}
|
|
2695
|
+
// -------------------------------------------------------------------------
|
|
2696
|
+
// Lifecycle
|
|
2697
|
+
// -------------------------------------------------------------------------
|
|
2698
|
+
start() {
|
|
2699
|
+
this.drainTimer = setInterval(() => this.drain(), 100);
|
|
2700
|
+
}
|
|
2701
|
+
async shutdown() {
|
|
2702
|
+
if (this.drainTimer) {
|
|
2703
|
+
clearInterval(this.drainTimer);
|
|
2704
|
+
this.drainTimer = null;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
// -------------------------------------------------------------------------
|
|
2708
|
+
// Enqueue (called by registry on dispatch)
|
|
2709
|
+
// -------------------------------------------------------------------------
|
|
2710
|
+
enqueue(handle) {
|
|
2711
|
+
this.queue.enqueue(handle);
|
|
2712
|
+
this.drain();
|
|
2713
|
+
}
|
|
2714
|
+
// -------------------------------------------------------------------------
|
|
2715
|
+
// Metrics
|
|
2716
|
+
// -------------------------------------------------------------------------
|
|
2717
|
+
getMetrics() {
|
|
2718
|
+
return {
|
|
2719
|
+
activeWorkers: this.activeWorkers,
|
|
2720
|
+
poolSize: this.currentPoolSize,
|
|
2721
|
+
queueSize: this.queue.size
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
// -------------------------------------------------------------------------
|
|
2725
|
+
// Drain loop
|
|
2726
|
+
// -------------------------------------------------------------------------
|
|
2727
|
+
drain() {
|
|
2728
|
+
while (this.queue.size > 0 && this.activeWorkers < this.currentPoolSize) {
|
|
2729
|
+
const handle = this.queue.dequeue();
|
|
2730
|
+
if (!handle) break;
|
|
2731
|
+
if (handle.abortController.signal.aborted) {
|
|
2732
|
+
this.registry.transition(handle.taskId, "cancelled", {
|
|
2733
|
+
error: "cancelled-while-queued"
|
|
2734
|
+
});
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
const taskType = this.getTaskType(handle);
|
|
2738
|
+
if (this.isCircuitOpen(taskType)) {
|
|
2739
|
+
this.registry.transition(handle.taskId, "failed", {
|
|
2740
|
+
error: "Circuit breaker open for this task type. Too many recent failures."
|
|
2741
|
+
});
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
this.activeWorkers++;
|
|
2745
|
+
this.executeHandle(handle).finally(() => {
|
|
2746
|
+
this.activeWorkers--;
|
|
2747
|
+
this.maybeResize();
|
|
2748
|
+
this.drain();
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
// -------------------------------------------------------------------------
|
|
2753
|
+
// Execute a single handle
|
|
2754
|
+
// -------------------------------------------------------------------------
|
|
2755
|
+
async executeHandle(handle) {
|
|
2756
|
+
this.registry.transition(handle.taskId, "running");
|
|
2757
|
+
const subVfs = new VirtualFilesystem();
|
|
2758
|
+
const fsTools = createFilesystemTools(subVfs);
|
|
2759
|
+
handle.timeoutTimer = setTimeout(() => {
|
|
2760
|
+
handle.abortController.abort("timeout");
|
|
2761
|
+
this.registry.transition(handle.taskId, "timeout", {
|
|
2762
|
+
error: `Subagent timed out after ${handle.timeoutMs}ms`
|
|
2763
|
+
});
|
|
2764
|
+
}, handle.timeoutMs);
|
|
2765
|
+
try {
|
|
2766
|
+
const result = await generateText({
|
|
2767
|
+
model: this.model,
|
|
2768
|
+
system: handle.instructions ?? "You are a specialized subagent. Complete the task and return your findings.",
|
|
2769
|
+
tools: { ...fsTools },
|
|
2770
|
+
stopWhen: stepCountIs(this.limits.maxStepsPerSubagent),
|
|
2771
|
+
prompt: handle.prompt,
|
|
2772
|
+
abortSignal: handle.abortController.signal
|
|
2773
|
+
});
|
|
2774
|
+
const output = result.text || "[Subagent completed with no text output]";
|
|
2775
|
+
const usage = result.usage;
|
|
2776
|
+
if (usage) {
|
|
2777
|
+
handle.tokenUsage.input += usage.inputTokens ?? 0;
|
|
2778
|
+
handle.tokenUsage.output += usage.outputTokens ?? 0;
|
|
2779
|
+
}
|
|
2780
|
+
this.registry.transition(handle.taskId, "completed", {
|
|
2781
|
+
finalOutput: output
|
|
2782
|
+
});
|
|
2783
|
+
this.recordSuccess(this.getTaskType(handle));
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (handle.abortController.signal.aborted) return;
|
|
2786
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2787
|
+
this.registry.transition(handle.taskId, "failed", {
|
|
2788
|
+
error: message
|
|
2789
|
+
});
|
|
2790
|
+
this.recordFailure(this.getTaskType(handle));
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
// -------------------------------------------------------------------------
|
|
2794
|
+
// Pool Sizing
|
|
2795
|
+
// -------------------------------------------------------------------------
|
|
2796
|
+
maybeResize() {
|
|
2797
|
+
const now = Date.now();
|
|
2798
|
+
if (now - this.lastResizeAt < this.poolConfig.resizeCooldownMs) return;
|
|
2799
|
+
const utilization = this.currentPoolSize > 0 ? this.activeWorkers / this.currentPoolSize : 0;
|
|
2800
|
+
let newSize = this.currentPoolSize;
|
|
2801
|
+
if (utilization > this.poolConfig.scaleUpThreshold) {
|
|
2802
|
+
newSize = Math.min(
|
|
2803
|
+
Math.ceil(this.currentPoolSize * 1.5),
|
|
2804
|
+
this.poolConfig.maxWorkers
|
|
2805
|
+
);
|
|
2806
|
+
} else if (utilization < this.poolConfig.scaleDownThreshold) {
|
|
2807
|
+
newSize = Math.max(
|
|
2808
|
+
Math.floor(this.currentPoolSize * 0.75),
|
|
2809
|
+
this.poolConfig.minWorkers
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
if (newSize !== this.currentPoolSize) {
|
|
2813
|
+
this.currentPoolSize = newSize;
|
|
2814
|
+
this.lastResizeAt = now;
|
|
2815
|
+
this.telemetry?.recordMetric("subagent.pool.size", newSize);
|
|
2816
|
+
}
|
|
2817
|
+
this.telemetry?.recordMetric(
|
|
2818
|
+
"subagent.pool.utilization",
|
|
2819
|
+
utilization
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
// -------------------------------------------------------------------------
|
|
2823
|
+
// Circuit Breaker per task type
|
|
2824
|
+
// -------------------------------------------------------------------------
|
|
2825
|
+
getTaskType(handle) {
|
|
2826
|
+
return handle.instructions ?? "__default__";
|
|
2827
|
+
}
|
|
2828
|
+
isCircuitOpen(taskType) {
|
|
2829
|
+
const state = this.circuitBreakers.get(taskType);
|
|
2830
|
+
if (!state) return false;
|
|
2831
|
+
if (state.state === "open") {
|
|
2832
|
+
if (Date.now() - state.lastFailure > this.cbConfig.resetTimeoutMs) {
|
|
2833
|
+
state.state = "half-open";
|
|
2834
|
+
return false;
|
|
2835
|
+
}
|
|
2836
|
+
return true;
|
|
2837
|
+
}
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
recordFailure(taskType) {
|
|
2841
|
+
const now = Date.now();
|
|
2842
|
+
let state = this.circuitBreakers.get(taskType);
|
|
2843
|
+
if (!state) {
|
|
2844
|
+
state = { failures: [], state: "closed", lastFailure: 0 };
|
|
2845
|
+
this.circuitBreakers.set(taskType, state);
|
|
2846
|
+
}
|
|
2847
|
+
state.failures.push(now);
|
|
2848
|
+
state.lastFailure = now;
|
|
2849
|
+
state.failures = state.failures.filter(
|
|
2850
|
+
(t) => now - t < this.cbConfig.monitorWindowMs
|
|
2851
|
+
);
|
|
2852
|
+
if (state.failures.length >= this.cbConfig.failureThreshold) {
|
|
2853
|
+
state.state = "open";
|
|
2854
|
+
this.telemetry?.recordMetric(
|
|
2855
|
+
"subagent.circuit_breaker.open",
|
|
2856
|
+
1
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
recordSuccess(taskType) {
|
|
2861
|
+
const state = this.circuitBreakers.get(taskType);
|
|
2862
|
+
if (state?.state === "half-open") {
|
|
2863
|
+
state.state = "closed";
|
|
2864
|
+
state.failures = [];
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
|
|
2869
|
+
// src/tools/subagent/index.ts
|
|
2870
|
+
function createAsyncSubagentTools(config) {
|
|
2871
|
+
const { registry, parentId, currentDepth, hooks } = config;
|
|
2872
|
+
return {
|
|
2873
|
+
dispatch_subagent: createDispatchTool({
|
|
2874
|
+
registry,
|
|
2875
|
+
parentId,
|
|
2876
|
+
currentDepth,
|
|
2877
|
+
hooks
|
|
2878
|
+
}),
|
|
2879
|
+
poll_subagent: createPollTool({ registry }),
|
|
2880
|
+
await_subagent: createAwaitTool({ registry, hooks })
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/agent/tool-manager.ts
|
|
2885
|
+
var ToolManager = class {
|
|
2886
|
+
config;
|
|
2887
|
+
pluginManager;
|
|
2888
|
+
circuitBreaker;
|
|
2889
|
+
rateLimiter;
|
|
2890
|
+
toolCache;
|
|
2891
|
+
constructor(config, pluginManager, circuitBreaker, rateLimiter, toolCache) {
|
|
2892
|
+
this.config = config;
|
|
2893
|
+
this.pluginManager = pluginManager;
|
|
2894
|
+
this.circuitBreaker = circuitBreaker;
|
|
2895
|
+
this.rateLimiter = rateLimiter;
|
|
2896
|
+
this.toolCache = toolCache;
|
|
2897
|
+
}
|
|
2898
|
+
// ---------------------------------------------------------------------------
|
|
2899
|
+
// Tool registration
|
|
2900
|
+
// ---------------------------------------------------------------------------
|
|
2901
|
+
registerTools(target, source, sourceLabel) {
|
|
2902
|
+
for (const [toolName, toolDef] of Object.entries(source)) {
|
|
2903
|
+
if (toolName in target) {
|
|
2904
|
+
throw new Error(`Tool "${toolName}" already registered (source: ${sourceLabel})`);
|
|
2905
|
+
}
|
|
2906
|
+
target[toolName] = toolDef;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
// ---------------------------------------------------------------------------
|
|
2910
|
+
// Build catalog
|
|
2911
|
+
// ---------------------------------------------------------------------------
|
|
2912
|
+
async buildToolCatalog(runMetadata) {
|
|
2913
|
+
const tools = {};
|
|
2914
|
+
this.registerTools(tools, createFilesystemTools(this.config.fs), "filesystem");
|
|
2915
|
+
this.registerTools(tools, this.config.extraTools ?? {}, "builder.withTools");
|
|
2916
|
+
this.registerTools(tools, this.pluginManager.collectTools(), "plugins");
|
|
2917
|
+
if (this.config.planning) {
|
|
2918
|
+
this.registerTools(tools, createPlanningTools(this.config.fs), "planning");
|
|
2919
|
+
}
|
|
2920
|
+
if (this.config.subagents) {
|
|
2921
|
+
const limits = {
|
|
2922
|
+
...DEFAULT_LIMITS,
|
|
2923
|
+
maxDepth: this.config.subagentConfig?.maxDepth ?? DEFAULT_LIMITS.maxDepth,
|
|
2924
|
+
defaultTimeoutMs: this.config.subagentConfig?.timeoutMs ?? DEFAULT_LIMITS.defaultTimeoutMs
|
|
2925
|
+
};
|
|
2926
|
+
const eventBus = new EventBus("subagent-bus");
|
|
2927
|
+
const registry = new SubagentRegistry(eventBus, {
|
|
2928
|
+
limits,
|
|
2929
|
+
hooks: this.config.subagentConfig?.hooks
|
|
2930
|
+
});
|
|
2931
|
+
const scheduler = new SubagentScheduler(registry, this.config.model, limits);
|
|
2932
|
+
scheduler.start();
|
|
2933
|
+
this.registerTools(
|
|
2934
|
+
tools,
|
|
2935
|
+
createAsyncSubagentTools({
|
|
2936
|
+
registry,
|
|
2937
|
+
parentId: "root",
|
|
2938
|
+
maxDepth: limits.maxDepth,
|
|
2939
|
+
currentDepth: 0,
|
|
2940
|
+
hooks: this.config.subagentConfig?.hooks
|
|
2941
|
+
}),
|
|
2942
|
+
"subagents"
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
if (this.config.mcp) {
|
|
2946
|
+
const mcpDefs = await this.config.mcp.discoverTools();
|
|
2947
|
+
const mcp = this.config.mcp;
|
|
2948
|
+
const mcpTools = {};
|
|
2949
|
+
const selection = this.extractMcpToolsetSelection(runMetadata);
|
|
2950
|
+
for (const [name, def] of Object.entries(mcpDefs)) {
|
|
2951
|
+
const [serverName = "mcp", ...toolParts] = name.split(":");
|
|
2952
|
+
const shortName = toolParts.length > 0 ? toolParts.join(":") : name;
|
|
2953
|
+
if (!this.shouldIncludeMcpTool(selection, {
|
|
2954
|
+
serverName,
|
|
2955
|
+
fullName: name,
|
|
2956
|
+
shortName
|
|
2957
|
+
})) {
|
|
2958
|
+
continue;
|
|
2959
|
+
}
|
|
2960
|
+
mcpTools[`mcp:${name}`] = tool({
|
|
2961
|
+
description: def.description,
|
|
2962
|
+
inputSchema: z18.object({}).passthrough(),
|
|
2963
|
+
execute: async (args) => {
|
|
2964
|
+
const result = await mcp.executeTool(name, args);
|
|
2965
|
+
if (result.isError) throw new Error(result.content[0]?.text ?? "MCP tool error");
|
|
2966
|
+
return result.content.map((c) => c.text ?? "").join("\n");
|
|
2967
|
+
}
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
this.registerTools(tools, mcpTools, "mcp");
|
|
2971
|
+
}
|
|
2972
|
+
if (this.config.policyEngine) {
|
|
2973
|
+
this.registerTools(
|
|
2974
|
+
tools,
|
|
2975
|
+
createPolicyTools(this.config.policyEngine),
|
|
2976
|
+
"policy"
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
return tools;
|
|
2980
|
+
}
|
|
2981
|
+
// ---------------------------------------------------------------------------
|
|
2982
|
+
// Wrapping — delegates to standalone pure functions
|
|
2983
|
+
// ---------------------------------------------------------------------------
|
|
2984
|
+
wrapToolsWithApproval(tools, sessionId, eventBus, runtime) {
|
|
2985
|
+
if (!this.config.approvalConfig) return;
|
|
2986
|
+
const approval = new ApprovalManager(
|
|
2987
|
+
this.config.approvalConfig,
|
|
2988
|
+
sessionId,
|
|
2989
|
+
(evt) => eventBus.emit(evt.type, evt.data)
|
|
2990
|
+
);
|
|
2991
|
+
applyApprovalWrapping(tools, approval, runtime);
|
|
2992
|
+
}
|
|
2993
|
+
wrapToolsWithResilience(tools) {
|
|
2994
|
+
applyResilienceWrapping(tools, this.circuitBreaker, this.toolCache);
|
|
2995
|
+
}
|
|
2996
|
+
wrapToolsWithPolicy(tools, sessionId, runMetadata) {
|
|
2997
|
+
if (!this.config.policyEngine) return;
|
|
2998
|
+
const context = this.resolvePolicyContext(sessionId, runMetadata);
|
|
2999
|
+
applyPolicyWrapping(tools, this.config.policyEngine, context);
|
|
3000
|
+
}
|
|
3001
|
+
wrapToolsWithPlugins(tools, pluginCtx) {
|
|
3002
|
+
applyPluginWrapping(tools, this.pluginManager, pluginCtx);
|
|
3003
|
+
}
|
|
3004
|
+
resolvePolicyContext(sessionId, runMetadata) {
|
|
3005
|
+
const base = {
|
|
3006
|
+
sessionId,
|
|
3007
|
+
userId: this.config.userId
|
|
3008
|
+
};
|
|
3009
|
+
const raw = runMetadata?.policyContext;
|
|
3010
|
+
if (!raw || typeof raw !== "object") {
|
|
3011
|
+
return base;
|
|
3012
|
+
}
|
|
3013
|
+
const candidate = raw;
|
|
3014
|
+
return {
|
|
3015
|
+
...base,
|
|
3016
|
+
...candidate.sessionId ? { sessionId: candidate.sessionId } : {},
|
|
3017
|
+
...candidate.userId ? { userId: candidate.userId } : {},
|
|
3018
|
+
...candidate.tenantId ? { tenantId: candidate.tenantId } : {},
|
|
3019
|
+
...candidate.metadata && typeof candidate.metadata === "object" ? { metadata: candidate.metadata } : {}
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
createRateLimitedModel() {
|
|
3023
|
+
if (!this.rateLimiter) {
|
|
3024
|
+
return this.config.model;
|
|
3025
|
+
}
|
|
3026
|
+
const rateLimiter = this.rateLimiter;
|
|
3027
|
+
const originalModel = this.config.model;
|
|
3028
|
+
return new Proxy(originalModel, {
|
|
3029
|
+
get(target, prop, receiver) {
|
|
3030
|
+
const value = Reflect.get(target, prop, receiver);
|
|
3031
|
+
if (typeof value === "function" && prop === "doGenerate") {
|
|
3032
|
+
return async function(...args) {
|
|
3033
|
+
await rateLimiter.acquire();
|
|
3034
|
+
return value.apply(target, args);
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
if (typeof value === "function" && prop === "doStream") {
|
|
3038
|
+
return async function(...args) {
|
|
3039
|
+
await rateLimiter.acquire();
|
|
3040
|
+
return value.apply(target, args);
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
return value;
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
// ---------------------------------------------------------------------------
|
|
3048
|
+
// Full preparation pipeline
|
|
3049
|
+
// ---------------------------------------------------------------------------
|
|
3050
|
+
async prepareTools(sessionId, eventBus, runtime, runMetadata) {
|
|
3051
|
+
const tools = await this.buildToolCatalog(runMetadata);
|
|
3052
|
+
const toolNames = Object.keys(tools);
|
|
3053
|
+
const setupCtx = this.createPluginSetupContext(sessionId, toolNames, eventBus);
|
|
3054
|
+
await this.pluginManager.initialize(setupCtx);
|
|
3055
|
+
const pluginCtx = this.createPluginContext(sessionId, toolNames, runMetadata);
|
|
3056
|
+
this.wrapToolsWithApproval(tools, sessionId, eventBus, runtime);
|
|
3057
|
+
this.wrapToolsWithResilience(tools);
|
|
3058
|
+
if (this.config.policyEngine) {
|
|
3059
|
+
this.wrapToolsWithPolicy(tools, sessionId, runMetadata);
|
|
3060
|
+
}
|
|
3061
|
+
this.wrapToolsWithPlugins(tools, pluginCtx);
|
|
3062
|
+
return { tools, pluginCtx };
|
|
3063
|
+
}
|
|
3064
|
+
// ---------------------------------------------------------------------------
|
|
3065
|
+
// Plugin context factories
|
|
3066
|
+
// ---------------------------------------------------------------------------
|
|
3067
|
+
createPluginContext(sessionId, toolNames, runMetadata) {
|
|
3068
|
+
return {
|
|
3069
|
+
sessionId,
|
|
3070
|
+
agentName: this.config.name,
|
|
3071
|
+
config: {
|
|
3072
|
+
instructions: this.config.instructions,
|
|
3073
|
+
maxSteps: this.config.maxSteps
|
|
3074
|
+
},
|
|
3075
|
+
filesystem: this.config.fs,
|
|
3076
|
+
memory: this.config.memory,
|
|
3077
|
+
learning: this.config.learning,
|
|
3078
|
+
toolNames,
|
|
3079
|
+
runMetadata
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
createPluginSetupContext(sessionId, toolNames, eventBus) {
|
|
3083
|
+
return {
|
|
3084
|
+
...this.createPluginContext(sessionId, toolNames),
|
|
3085
|
+
on: (eventType, handler) => eventBus.on(eventType, handler)
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
extractMcpToolsetSelection(runMetadata) {
|
|
3089
|
+
const raw = runMetadata?.mcpToolset;
|
|
3090
|
+
if (!raw || typeof raw !== "object") {
|
|
3091
|
+
return void 0;
|
|
3092
|
+
}
|
|
3093
|
+
const normalize = (value) => {
|
|
3094
|
+
if (!Array.isArray(value)) return void 0;
|
|
3095
|
+
const cleaned = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
3096
|
+
return cleaned.length > 0 ? cleaned : void 0;
|
|
3097
|
+
};
|
|
3098
|
+
const candidate = raw;
|
|
3099
|
+
const selection = {
|
|
3100
|
+
includeServers: normalize(candidate.includeServers),
|
|
3101
|
+
excludeServers: normalize(candidate.excludeServers),
|
|
3102
|
+
includeTools: normalize(candidate.includeTools),
|
|
3103
|
+
excludeTools: normalize(candidate.excludeTools)
|
|
3104
|
+
};
|
|
3105
|
+
if (!selection.includeServers && !selection.excludeServers && !selection.includeTools && !selection.excludeTools) {
|
|
3106
|
+
return void 0;
|
|
3107
|
+
}
|
|
3108
|
+
return selection;
|
|
3109
|
+
}
|
|
3110
|
+
shouldIncludeMcpTool(selection, toolInfo) {
|
|
3111
|
+
if (!selection) {
|
|
3112
|
+
return true;
|
|
3113
|
+
}
|
|
3114
|
+
if (selection.includeServers && !selection.includeServers.includes(toolInfo.serverName)) {
|
|
3115
|
+
return false;
|
|
3116
|
+
}
|
|
3117
|
+
if (selection.excludeServers && selection.excludeServers.includes(toolInfo.serverName)) {
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
if (selection.includeTools) {
|
|
3121
|
+
const allowed = selection.includeTools.includes(toolInfo.fullName) || selection.includeTools.includes(toolInfo.shortName);
|
|
3122
|
+
if (!allowed) {
|
|
3123
|
+
return false;
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
if (selection.excludeTools && (selection.excludeTools.includes(toolInfo.fullName) || selection.excludeTools.includes(toolInfo.shortName))) {
|
|
3127
|
+
return false;
|
|
3128
|
+
}
|
|
3129
|
+
return true;
|
|
3130
|
+
}
|
|
3131
|
+
};
|
|
3132
|
+
function wrapAllTools(tools, wrapFn) {
|
|
3133
|
+
for (const [name, toolDef] of Object.entries(tools)) {
|
|
3134
|
+
if (!toolDef) continue;
|
|
3135
|
+
const maybeExecutable = toolDef;
|
|
3136
|
+
if (!maybeExecutable.execute) continue;
|
|
3137
|
+
const originalExecute = maybeExecutable.execute.bind(maybeExecutable);
|
|
3138
|
+
maybeExecutable.execute = wrapFn(name, originalExecute);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function applyApprovalWrapping(tools, approval, runtime) {
|
|
3142
|
+
let stepIndex = 0;
|
|
3143
|
+
wrapAllTools(tools, (name, originalExecute) => async (...args) => {
|
|
3144
|
+
const { approved, reason } = await approval.checkAndApprove(
|
|
3145
|
+
name,
|
|
3146
|
+
runtime.randomUUID(),
|
|
3147
|
+
args[0],
|
|
3148
|
+
stepIndex++
|
|
3149
|
+
);
|
|
3150
|
+
if (!approved) {
|
|
3151
|
+
return `Tool call denied: ${reason ?? "not approved"}`;
|
|
3152
|
+
}
|
|
3153
|
+
return originalExecute(...args);
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
function applyResilienceWrapping(tools, circuitBreaker, toolCache) {
|
|
3157
|
+
if (circuitBreaker) {
|
|
3158
|
+
wrapAllTools(tools, (_name, originalExecute) => async (...args) => {
|
|
3159
|
+
return circuitBreaker.execute(async () => originalExecute(...args));
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
if (toolCache) {
|
|
3163
|
+
wrapAllTools(tools, (name, originalExecute) => async (...args) => {
|
|
3164
|
+
const cacheKey = `${name}:${JSON.stringify(args)}`;
|
|
3165
|
+
if (toolCache.has(cacheKey)) {
|
|
3166
|
+
return toolCache.get(cacheKey);
|
|
3167
|
+
}
|
|
3168
|
+
const result = await originalExecute(...args);
|
|
3169
|
+
toolCache.set(cacheKey, result);
|
|
3170
|
+
return result;
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
function applyPolicyWrapping(tools, policyEngine, context) {
|
|
3175
|
+
wrapAllTools(tools, (name, originalExecute) => async (...args) => {
|
|
3176
|
+
if (!name.startsWith("mcp:")) {
|
|
3177
|
+
return originalExecute(...args);
|
|
3178
|
+
}
|
|
3179
|
+
const resource = name.slice("mcp:".length);
|
|
3180
|
+
const [serverName = "mcp", ...toolParts] = resource.split(":");
|
|
3181
|
+
const toolName = toolParts.length > 0 ? toolParts.join(":") : resource;
|
|
3182
|
+
const request = {
|
|
3183
|
+
action: "invoke",
|
|
3184
|
+
resource,
|
|
3185
|
+
serverName,
|
|
3186
|
+
toolName
|
|
3187
|
+
};
|
|
3188
|
+
const decision = await policyEngine.evaluate(request, context);
|
|
3189
|
+
if (!decision.allowed) {
|
|
3190
|
+
throw new Error(
|
|
3191
|
+
`MCP policy denied (${decision.auditId}) for ${resource}: ${decision.reason ?? "access denied"}`
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
return originalExecute(...args);
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
function applyPluginWrapping(tools, pluginManager, pluginCtx) {
|
|
3198
|
+
if (pluginManager.count === 0) return;
|
|
3199
|
+
wrapAllTools(tools, (name, originalExecute) => async (...args) => {
|
|
3200
|
+
const beforeResult = await pluginManager.runBeforeTool(pluginCtx, {
|
|
3201
|
+
toolName: name,
|
|
3202
|
+
args: args[0]
|
|
3203
|
+
});
|
|
3204
|
+
if (beforeResult.skip) {
|
|
3205
|
+
return beforeResult.result;
|
|
3206
|
+
}
|
|
3207
|
+
const finalArgs = beforeResult.args !== void 0 ? beforeResult.args : args[0];
|
|
3208
|
+
try {
|
|
3209
|
+
const result = await originalExecute(finalArgs, ...args.slice(1));
|
|
3210
|
+
await pluginManager.runAfterTool(pluginCtx, {
|
|
3211
|
+
toolName: name,
|
|
3212
|
+
args: finalArgs,
|
|
3213
|
+
result
|
|
3214
|
+
});
|
|
3215
|
+
return result;
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
const onErrorResult = await pluginManager.runOnError(pluginCtx, {
|
|
3218
|
+
error,
|
|
3219
|
+
phase: "tool"
|
|
3220
|
+
});
|
|
3221
|
+
if (onErrorResult.suppress) return void 0;
|
|
3222
|
+
throw error;
|
|
3223
|
+
}
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/agent/execution-engine.ts
|
|
3228
|
+
var ExecutionEngine = class {
|
|
3229
|
+
config;
|
|
3230
|
+
toolManager;
|
|
3231
|
+
pluginManager;
|
|
3232
|
+
eventBus;
|
|
3233
|
+
tokenTracker;
|
|
3234
|
+
middlewareChain;
|
|
3235
|
+
constructor(config, toolManager, pluginManager, eventBus, tokenTracker, middlewareChain) {
|
|
3236
|
+
this.config = config;
|
|
3237
|
+
this.toolManager = toolManager;
|
|
3238
|
+
this.pluginManager = pluginManager;
|
|
3239
|
+
this.eventBus = eventBus;
|
|
3240
|
+
this.tokenTracker = tokenTracker;
|
|
3241
|
+
this.middlewareChain = middlewareChain;
|
|
3242
|
+
}
|
|
3243
|
+
// ---------------------------------------------------------------------------
|
|
3244
|
+
// Middleware context factory
|
|
3245
|
+
// ---------------------------------------------------------------------------
|
|
3246
|
+
createMiddlewareContext(sessionId) {
|
|
3247
|
+
return {
|
|
3248
|
+
sessionId,
|
|
3249
|
+
agentName: this.config.agentName,
|
|
3250
|
+
timestamp: Date.now(),
|
|
3251
|
+
metadata: {}
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
// ---------------------------------------------------------------------------
|
|
3255
|
+
// Telemetry helper — DRY span lifecycle (try/catch/finally)
|
|
3256
|
+
// ---------------------------------------------------------------------------
|
|
3257
|
+
isDelegationTool(toolName) {
|
|
3258
|
+
return toolName === "dispatch_subagent" || toolName === "poll_subagent" || toolName === "await_subagent";
|
|
3259
|
+
}
|
|
3260
|
+
async applyDelegationMessageFilter(params) {
|
|
3261
|
+
const hook = this.config.delegationHooks?.messageFilter;
|
|
3262
|
+
if (!hook || !this.isDelegationTool(params.toolName)) {
|
|
3263
|
+
return {
|
|
3264
|
+
allow: true,
|
|
3265
|
+
payload: params.payload
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
try {
|
|
3269
|
+
const filtered = await hook(params);
|
|
3270
|
+
if (typeof filtered === "boolean") {
|
|
3271
|
+
return {
|
|
3272
|
+
allow: filtered,
|
|
3273
|
+
payload: params.payload
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
if (!filtered) {
|
|
3277
|
+
return {
|
|
3278
|
+
allow: true,
|
|
3279
|
+
payload: params.payload
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
return {
|
|
3283
|
+
allow: filtered.allow !== false,
|
|
3284
|
+
payload: filtered.payload !== void 0 ? filtered.payload : params.payload,
|
|
3285
|
+
reason: filtered.reason
|
|
3286
|
+
};
|
|
3287
|
+
} catch {
|
|
3288
|
+
return {
|
|
3289
|
+
allow: true,
|
|
3290
|
+
payload: params.payload
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
async withSpan(name, attrs, fn) {
|
|
3295
|
+
const span = this.config.telemetry?.startSpan(name, attrs);
|
|
3296
|
+
try {
|
|
3297
|
+
const result = await fn();
|
|
3298
|
+
span?.setStatus("OK");
|
|
3299
|
+
return result;
|
|
3300
|
+
} catch (error) {
|
|
3301
|
+
span?.setStatus("ERROR", error instanceof Error ? error.message : String(error));
|
|
3302
|
+
throw error;
|
|
3303
|
+
} finally {
|
|
3304
|
+
span?.end();
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
// ---------------------------------------------------------------------------
|
|
3308
|
+
// Run
|
|
3309
|
+
// ---------------------------------------------------------------------------
|
|
3310
|
+
async run(prompt, sessionId, runtime, options = {}) {
|
|
3311
|
+
const runMetadata = {
|
|
3312
|
+
...options.pluginMetadata ?? {},
|
|
3313
|
+
...options.mcpToolset ? { mcpToolset: options.mcpToolset } : {},
|
|
3314
|
+
...options.policyContext ? { policyContext: options.policyContext } : {}
|
|
3315
|
+
};
|
|
3316
|
+
let pluginCtx = this.toolManager.createPluginContext(
|
|
3317
|
+
sessionId,
|
|
3318
|
+
[],
|
|
3319
|
+
runMetadata
|
|
3320
|
+
);
|
|
3321
|
+
try {
|
|
3322
|
+
const prepared = await this.toolManager.prepareTools(
|
|
3323
|
+
sessionId,
|
|
3324
|
+
this.eventBus,
|
|
3325
|
+
runtime,
|
|
3326
|
+
runMetadata
|
|
3327
|
+
);
|
|
3328
|
+
const tools = prepared.tools;
|
|
3329
|
+
pluginCtx = prepared.pluginCtx;
|
|
3330
|
+
if (this.config.learning) {
|
|
3331
|
+
const userId = this.config.userId ?? sessionId;
|
|
3332
|
+
const [profile, memories] = await Promise.all([
|
|
3333
|
+
this.config.learning.getProfile(userId),
|
|
3334
|
+
this.config.learning.getMemories(userId, { limit: 10 })
|
|
3335
|
+
]);
|
|
3336
|
+
const learningContext = this.buildLearningContext(profile, memories);
|
|
3337
|
+
if (learningContext) prompt = `${learningContext}
|
|
3338
|
+
|
|
3339
|
+
${prompt}`;
|
|
3340
|
+
}
|
|
3341
|
+
const beforeRunResult = await this.pluginManager.runBeforeRun(pluginCtx, { prompt });
|
|
3342
|
+
if (beforeRunResult.prompt !== void 0) {
|
|
3343
|
+
prompt = beforeRunResult.prompt;
|
|
3344
|
+
}
|
|
3345
|
+
const mwCtx = this.createMiddlewareContext(sessionId);
|
|
3346
|
+
let instructions = this.config.instructions;
|
|
3347
|
+
if (this.middlewareChain) {
|
|
3348
|
+
const mwResult = await this.middlewareChain.runBeforeAgent(mwCtx, {
|
|
3349
|
+
prompt,
|
|
3350
|
+
instructions,
|
|
3351
|
+
tools
|
|
3352
|
+
});
|
|
3353
|
+
if (mwResult.aborted) {
|
|
3354
|
+
const earlyText = mwResult.earlyResult ?? "";
|
|
3355
|
+
this.eventBus.emit("agent:stop", {
|
|
3356
|
+
result: { text: earlyText, steps: [], sessionId, toolCalls: [] }
|
|
3357
|
+
});
|
|
3358
|
+
return { text: earlyText, steps: [], sessionId, toolCalls: [] };
|
|
3359
|
+
}
|
|
3360
|
+
prompt = mwResult.prompt;
|
|
3361
|
+
instructions = mwResult.instructions;
|
|
3362
|
+
}
|
|
3363
|
+
this.eventBus.emit("agent:start", { prompt });
|
|
3364
|
+
const cpConfig = this.config.checkpointConfig;
|
|
3365
|
+
if (cpConfig?.enabled) {
|
|
3366
|
+
const cp = await this.config.memory.loadLatestCheckpoint(sessionId);
|
|
3367
|
+
if (cp) this.eventBus.emit("checkpoint:load", { checkpoint: cp });
|
|
3368
|
+
}
|
|
3369
|
+
const agentOptions = {
|
|
3370
|
+
model: this.toolManager.createRateLimitedModel(),
|
|
3371
|
+
system: instructions,
|
|
3372
|
+
tools,
|
|
3373
|
+
stopWhen: stepCountIs(this.config.maxSteps),
|
|
3374
|
+
prompt
|
|
3375
|
+
};
|
|
3376
|
+
if (this.config.output) agentOptions.output = this.config.output;
|
|
3377
|
+
const result = await this.withSpan(
|
|
3378
|
+
"llm.generate",
|
|
3379
|
+
{ "llm.model": String(this.config.model) },
|
|
3380
|
+
() => generateText(agentOptions)
|
|
3381
|
+
);
|
|
3382
|
+
const resultObj = result;
|
|
3383
|
+
const usage = resultObj.usage;
|
|
3384
|
+
if (usage) {
|
|
3385
|
+
if (usage.promptTokens) {
|
|
3386
|
+
this.tokenTracker.addInput(usage.promptTokens);
|
|
3387
|
+
this.config.telemetry?.recordMetric("llm.tokens.input", usage.promptTokens);
|
|
3388
|
+
}
|
|
3389
|
+
if (usage.completionTokens) {
|
|
3390
|
+
this.tokenTracker.addOutput(usage.completionTokens);
|
|
3391
|
+
this.config.telemetry?.recordMetric("llm.tokens.output", usage.completionTokens);
|
|
3392
|
+
}
|
|
3393
|
+
if (this.config.costTracker && (usage.promptTokens || usage.completionTokens)) {
|
|
3394
|
+
const modelId = this.config.model.modelId ?? "unknown";
|
|
3395
|
+
const provider = this.config.model.provider ?? "unknown";
|
|
3396
|
+
this.config.costTracker.recordUsage({
|
|
3397
|
+
inputTokens: usage.promptTokens ?? 0,
|
|
3398
|
+
outputTokens: usage.completionTokens ?? 0,
|
|
3399
|
+
model: modelId,
|
|
3400
|
+
provider,
|
|
3401
|
+
timestamp: Date.now()
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
const steps = result.steps ?? [];
|
|
3406
|
+
for (let i = 0; i < steps.length; i++) {
|
|
3407
|
+
let step = steps[i];
|
|
3408
|
+
const stepObj = step;
|
|
3409
|
+
const toolName = stepObj?.toolName ?? `step.${i}`;
|
|
3410
|
+
const toolSpanStart = Date.now();
|
|
3411
|
+
try {
|
|
3412
|
+
await this.withSpan(`tool.${toolName}`, { "tool.name": toolName, "step.index": i }, async () => {
|
|
3413
|
+
const beforeStepResult = await this.pluginManager.runBeforeStep(pluginCtx, {
|
|
3414
|
+
stepIndex: i,
|
|
3415
|
+
step
|
|
3416
|
+
});
|
|
3417
|
+
if (beforeStepResult.skip) return;
|
|
3418
|
+
if (beforeStepResult.step !== void 0) {
|
|
3419
|
+
step = beforeStepResult.step;
|
|
3420
|
+
steps[i] = step;
|
|
3421
|
+
}
|
|
3422
|
+
this.eventBus.emit("step:start", { stepIndex: i, step });
|
|
3423
|
+
const toolCalls = stepObj?.toolCalls ?? [];
|
|
3424
|
+
for (const tc of toolCalls) {
|
|
3425
|
+
const filtered = await this.applyDelegationMessageFilter({
|
|
3426
|
+
direction: "tool:call",
|
|
3427
|
+
toolName: tc.toolName,
|
|
3428
|
+
stepIndex: i,
|
|
3429
|
+
payload: tc.args
|
|
3430
|
+
});
|
|
3431
|
+
if (!filtered.allow) {
|
|
3432
|
+
this.eventBus.emit("delegation:message-filtered", {
|
|
3433
|
+
direction: "tool:call",
|
|
3434
|
+
toolName: tc.toolName,
|
|
3435
|
+
stepIndex: i,
|
|
3436
|
+
reason: filtered.reason
|
|
3437
|
+
});
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
this.eventBus.emit("tool:call", {
|
|
3441
|
+
toolName: tc.toolName,
|
|
3442
|
+
toolCallId: tc.toolCallId,
|
|
3443
|
+
args: filtered.payload,
|
|
3444
|
+
stepIndex: i
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
const toolResults = stepObj?.toolResults ?? [];
|
|
3448
|
+
for (const tr of toolResults) {
|
|
3449
|
+
const filtered = await this.applyDelegationMessageFilter({
|
|
3450
|
+
direction: "tool:result",
|
|
3451
|
+
toolName: tr.toolName,
|
|
3452
|
+
stepIndex: i,
|
|
3453
|
+
payload: tr.result
|
|
3454
|
+
});
|
|
3455
|
+
if (!filtered.allow) {
|
|
3456
|
+
this.eventBus.emit("delegation:message-filtered", {
|
|
3457
|
+
direction: "tool:result",
|
|
3458
|
+
toolName: tr.toolName,
|
|
3459
|
+
stepIndex: i,
|
|
3460
|
+
reason: filtered.reason
|
|
3461
|
+
});
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
this.eventBus.emit("tool:result", {
|
|
3465
|
+
toolName: tr.toolName,
|
|
3466
|
+
toolCallId: tr.toolCallId,
|
|
3467
|
+
result: filtered.payload,
|
|
3468
|
+
stepIndex: i
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
this.eventBus.emit("step:end", { stepIndex: i, step });
|
|
3472
|
+
await this.pluginManager.runAfterStep(pluginCtx, {
|
|
3473
|
+
stepIndex: i,
|
|
3474
|
+
step
|
|
3475
|
+
});
|
|
3476
|
+
const toolDurationMs = Date.now() - toolSpanStart;
|
|
3477
|
+
this.config.telemetry?.recordMetric("tool.duration_ms", toolDurationMs, { tool: toolName });
|
|
3478
|
+
});
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
const onStepError = await this.pluginManager.runOnError(pluginCtx, {
|
|
3481
|
+
error,
|
|
3482
|
+
phase: "step"
|
|
3483
|
+
});
|
|
3484
|
+
if (!onStepError.suppress) throw error;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
if (cpConfig?.enabled) {
|
|
3488
|
+
const checkpoint = {
|
|
3489
|
+
id: runtime.randomUUID(),
|
|
3490
|
+
sessionId,
|
|
3491
|
+
stepIndex: steps.length,
|
|
3492
|
+
conversation: [
|
|
3493
|
+
{ role: "user", content: prompt },
|
|
3494
|
+
{ role: "assistant", content: result.text ?? "" }
|
|
3495
|
+
],
|
|
3496
|
+
todos: await this.config.memory.loadTodos(sessionId),
|
|
3497
|
+
filesSnapshot: {},
|
|
3498
|
+
toolResults: {},
|
|
3499
|
+
generatedTokens: 0,
|
|
3500
|
+
lastToolCallId: null,
|
|
3501
|
+
metadata: {},
|
|
3502
|
+
createdAt: Date.now()
|
|
3503
|
+
};
|
|
3504
|
+
await this.config.memory.saveCheckpoint(sessionId, checkpoint);
|
|
3505
|
+
this.eventBus.emit("checkpoint:save", { checkpoint });
|
|
3506
|
+
}
|
|
3507
|
+
const allToolCalls = [];
|
|
3508
|
+
for (let i = 0; i < steps.length; i++) {
|
|
3509
|
+
const s = steps[i];
|
|
3510
|
+
const calls = s?.toolCalls ?? [];
|
|
3511
|
+
for (const tc of calls) {
|
|
3512
|
+
allToolCalls.push({ name: tc.toolName, args: tc.args, stepIndex: i });
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
const agentResult = {
|
|
3516
|
+
text: result.text ?? "",
|
|
3517
|
+
steps,
|
|
3518
|
+
sessionId,
|
|
3519
|
+
output: result.output,
|
|
3520
|
+
toolCalls: allToolCalls
|
|
3521
|
+
};
|
|
3522
|
+
if (this.middlewareChain) {
|
|
3523
|
+
const mwAfterResult = await this.middlewareChain.runAfterAgent(mwCtx, {
|
|
3524
|
+
prompt,
|
|
3525
|
+
result: { text: agentResult.text, steps: agentResult.steps, sessionId }
|
|
3526
|
+
});
|
|
3527
|
+
agentResult.text = mwAfterResult.result.text;
|
|
3528
|
+
}
|
|
3529
|
+
this.eventBus.emit("agent:stop", { result: agentResult });
|
|
3530
|
+
await this.pluginManager.runAfterRun(pluginCtx, { result: agentResult });
|
|
3531
|
+
return agentResult;
|
|
3532
|
+
} catch (error) {
|
|
3533
|
+
const onErrorResult = await this.pluginManager.runOnError(pluginCtx, {
|
|
3534
|
+
error,
|
|
3535
|
+
phase: "run"
|
|
3536
|
+
});
|
|
3537
|
+
if (onErrorResult.suppress) {
|
|
3538
|
+
return { text: "", steps: [], sessionId, toolCalls: [] };
|
|
3539
|
+
}
|
|
3540
|
+
this.eventBus.emit("error", { error });
|
|
3541
|
+
throw error;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
// ---------------------------------------------------------------------------
|
|
3545
|
+
// Stream
|
|
3546
|
+
// ---------------------------------------------------------------------------
|
|
3547
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3548
|
+
async stream(params, sessionId, runtime, options = {}) {
|
|
3549
|
+
let pluginCtx = this.toolManager.createPluginContext(sessionId, [], options.pluginMetadata);
|
|
3550
|
+
try {
|
|
3551
|
+
const prepared = await this.toolManager.prepareTools(
|
|
3552
|
+
sessionId,
|
|
3553
|
+
this.eventBus,
|
|
3554
|
+
runtime,
|
|
3555
|
+
options.pluginMetadata
|
|
3556
|
+
);
|
|
3557
|
+
const tools = prepared.tools;
|
|
3558
|
+
pluginCtx = prepared.pluginCtx;
|
|
3559
|
+
if (this.config.learning) {
|
|
3560
|
+
const userId = this.config.userId ?? sessionId;
|
|
3561
|
+
const [profile, memories] = await Promise.all([
|
|
3562
|
+
this.config.learning.getProfile(userId),
|
|
3563
|
+
this.config.learning.getMemories(userId, { limit: 10 })
|
|
3564
|
+
]);
|
|
3565
|
+
const learningContext = this.buildLearningContext(profile, memories);
|
|
3566
|
+
if (learningContext) {
|
|
3567
|
+
params = {
|
|
3568
|
+
...params,
|
|
3569
|
+
messages: [
|
|
3570
|
+
{ role: "system", content: learningContext },
|
|
3571
|
+
...params.messages
|
|
3572
|
+
]
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
this.eventBus.emit("agent:start", { messages: params.messages });
|
|
3577
|
+
return this.withSpan("stream", { "stream.messages": params.messages.length }, async () => {
|
|
3578
|
+
const streamAgentOptions = {
|
|
3579
|
+
model: this.toolManager.createRateLimitedModel(),
|
|
3580
|
+
system: this.config.instructions,
|
|
3581
|
+
tools,
|
|
3582
|
+
stopWhen: stepCountIs(this.config.maxSteps)
|
|
3583
|
+
};
|
|
3584
|
+
if (this.config.output) streamAgentOptions.output = this.config.output;
|
|
3585
|
+
const streamResult = streamText(streamAgentOptions);
|
|
3586
|
+
if (this.config.costTracker) {
|
|
3587
|
+
const costTracker = this.config.costTracker;
|
|
3588
|
+
const model = this.config.model;
|
|
3589
|
+
const tokenTracker = this.tokenTracker;
|
|
3590
|
+
return Promise.resolve(streamResult).then((stream) => {
|
|
3591
|
+
const usagePromise = stream.usage ?? stream.totalUsage;
|
|
3592
|
+
if (usagePromise && typeof usagePromise.then === "function") {
|
|
3593
|
+
usagePromise.then((u) => {
|
|
3594
|
+
if (u) {
|
|
3595
|
+
if (u.promptTokens) tokenTracker.addInput(u.promptTokens);
|
|
3596
|
+
if (u.completionTokens) tokenTracker.addOutput(u.completionTokens);
|
|
3597
|
+
if (u.promptTokens || u.completionTokens) {
|
|
3598
|
+
const modelId = model.modelId ?? "unknown";
|
|
3599
|
+
const provider = model.provider ?? "unknown";
|
|
3600
|
+
costTracker.recordUsage({
|
|
3601
|
+
inputTokens: u.promptTokens ?? 0,
|
|
3602
|
+
outputTokens: u.completionTokens ?? 0,
|
|
3603
|
+
model: modelId,
|
|
3604
|
+
provider,
|
|
3605
|
+
timestamp: Date.now()
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
}).catch((err) => {
|
|
3610
|
+
console.warn("[usage-tracking] Failed to record usage:", err instanceof Error ? err.message : String(err));
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
return stream;
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
return streamResult;
|
|
3617
|
+
});
|
|
3618
|
+
} catch (error) {
|
|
3619
|
+
const onErrorResult = await this.pluginManager.runOnError(pluginCtx, {
|
|
3620
|
+
error,
|
|
3621
|
+
phase: "stream"
|
|
3622
|
+
});
|
|
3623
|
+
if (onErrorResult.suppress) {
|
|
3624
|
+
return new ReadableStream({ start(c) {
|
|
3625
|
+
c.close();
|
|
3626
|
+
} });
|
|
3627
|
+
}
|
|
3628
|
+
this.eventBus.emit("error", { error });
|
|
3629
|
+
throw error;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
// ---------------------------------------------------------------------------
|
|
3633
|
+
// Learning context
|
|
3634
|
+
// ---------------------------------------------------------------------------
|
|
3635
|
+
buildLearningContext(profile, memories) {
|
|
3636
|
+
const parts = [];
|
|
3637
|
+
if (profile) {
|
|
3638
|
+
if (profile.context) parts.push(`User context: ${profile.context}`);
|
|
3639
|
+
if (profile.style) parts.push(`Preferred style: ${profile.style}`);
|
|
3640
|
+
if (profile.language) parts.push(`Language: ${profile.language}`);
|
|
3641
|
+
}
|
|
3642
|
+
if (memories.length > 0) {
|
|
3643
|
+
parts.push(`Known facts about this user:
|
|
3644
|
+
${memories.map((m) => `- ${m.content}`).join("\n")}`);
|
|
3645
|
+
}
|
|
3646
|
+
return parts.length > 0 ? `[Learning Context]
|
|
3647
|
+
${parts.join("\n")}` : "";
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
|
|
3651
|
+
// src/agent/lifecycle.ts
|
|
3652
|
+
var SHUTDOWN_TIMEOUT_MS = 3e4;
|
|
3653
|
+
var LifecycleManager = class {
|
|
3654
|
+
_isReady = false;
|
|
3655
|
+
_isShuttingDown = false;
|
|
3656
|
+
hooks;
|
|
3657
|
+
runningOperations = /* @__PURE__ */ new Set();
|
|
3658
|
+
constructor(hooks) {
|
|
3659
|
+
this.hooks = hooks;
|
|
3660
|
+
}
|
|
3661
|
+
async startup() {
|
|
3662
|
+
if (this._isReady) return;
|
|
3663
|
+
try {
|
|
3664
|
+
if (this.hooks.onStartup) {
|
|
3665
|
+
await this.hooks.onStartup();
|
|
3666
|
+
}
|
|
3667
|
+
this._isReady = true;
|
|
3668
|
+
} catch (error) {
|
|
3669
|
+
this._isReady = false;
|
|
3670
|
+
throw error;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
async shutdown() {
|
|
3674
|
+
if (this._isShuttingDown) return;
|
|
3675
|
+
this._isShuttingDown = true;
|
|
3676
|
+
this._isReady = false;
|
|
3677
|
+
if (this.runningOperations.size > 0) {
|
|
3678
|
+
const controller = new AbortController();
|
|
3679
|
+
const timeout = new Promise((resolve) => {
|
|
3680
|
+
const timer = setTimeout(resolve, SHUTDOWN_TIMEOUT_MS);
|
|
3681
|
+
controller.signal.addEventListener("abort", () => {
|
|
3682
|
+
clearTimeout(timer);
|
|
3683
|
+
resolve();
|
|
3684
|
+
});
|
|
3685
|
+
});
|
|
3686
|
+
const allOperations = Promise.all(Array.from(this.runningOperations)).then(() => {
|
|
3687
|
+
}).catch((err) => {
|
|
3688
|
+
console.warn("[shutdown] Error in running operation:", err instanceof Error ? err.message : String(err));
|
|
3689
|
+
});
|
|
3690
|
+
await Promise.race([allOperations, timeout]);
|
|
3691
|
+
controller.abort();
|
|
3692
|
+
}
|
|
3693
|
+
try {
|
|
3694
|
+
if (this.hooks.onShutdown) {
|
|
3695
|
+
await this.hooks.onShutdown();
|
|
3696
|
+
}
|
|
3697
|
+
} finally {
|
|
3698
|
+
this.runningOperations.clear();
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
async healthCheck() {
|
|
3702
|
+
if (this.hooks.onHealthCheck) {
|
|
3703
|
+
return this.hooks.onHealthCheck();
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
healthy: this._isReady && !this._isShuttingDown,
|
|
3707
|
+
details: {
|
|
3708
|
+
lifecycle: {
|
|
3709
|
+
status: this._isReady && !this._isShuttingDown ? "up" : "down",
|
|
3710
|
+
message: this._isShuttingDown ? "Agent is shutting down" : this._isReady ? "Agent is ready" : "Agent not started"
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3715
|
+
get isReady() {
|
|
3716
|
+
return this._isReady;
|
|
3717
|
+
}
|
|
3718
|
+
get isShuttingDown() {
|
|
3719
|
+
return this._isShuttingDown;
|
|
3720
|
+
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Track a running operation for graceful shutdown.
|
|
3723
|
+
* Returns a cleanup function to remove the operation from tracking.
|
|
3724
|
+
*/
|
|
3725
|
+
trackOperation(operation) {
|
|
3726
|
+
this.runningOperations.add(operation);
|
|
3727
|
+
const cleanup = () => {
|
|
3728
|
+
this.runningOperations.delete(operation);
|
|
3729
|
+
};
|
|
3730
|
+
operation.finally(cleanup);
|
|
3731
|
+
return operation;
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
|
|
3735
|
+
// src/middleware/chain.ts
|
|
3736
|
+
var MiddlewareChain = class {
|
|
3737
|
+
middlewares = [];
|
|
3738
|
+
sorted = true;
|
|
3739
|
+
// ---------------------------------------------------------------------------
|
|
3740
|
+
// Registration
|
|
3741
|
+
// ---------------------------------------------------------------------------
|
|
3742
|
+
use(middleware) {
|
|
3743
|
+
if (this.middlewares.some((m) => m.name === middleware.name)) {
|
|
3744
|
+
throw new Error(`Middleware "${middleware.name}" is already registered`);
|
|
3745
|
+
}
|
|
3746
|
+
this.middlewares.push(middleware);
|
|
3747
|
+
this.sorted = false;
|
|
3748
|
+
}
|
|
3749
|
+
remove(name) {
|
|
3750
|
+
const idx = this.middlewares.findIndex((m) => m.name === name);
|
|
3751
|
+
if (idx === -1) return false;
|
|
3752
|
+
this.middlewares.splice(idx, 1);
|
|
3753
|
+
return true;
|
|
3754
|
+
}
|
|
3755
|
+
list() {
|
|
3756
|
+
this.ensureSorted();
|
|
3757
|
+
return this.middlewares;
|
|
3758
|
+
}
|
|
3759
|
+
// ---------------------------------------------------------------------------
|
|
3760
|
+
// Lifecycle
|
|
3761
|
+
// ---------------------------------------------------------------------------
|
|
3762
|
+
async setup(ctx) {
|
|
3763
|
+
this.ensureSorted();
|
|
3764
|
+
for (const mw of this.middlewares) {
|
|
3765
|
+
if (mw.setup) {
|
|
3766
|
+
await mw.setup(ctx);
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
async teardown(ctx) {
|
|
3771
|
+
for (let i = this.middlewares.length - 1; i >= 0; i--) {
|
|
3772
|
+
const mw = this.middlewares[i];
|
|
3773
|
+
if (mw.teardown) {
|
|
3774
|
+
try {
|
|
3775
|
+
await mw.teardown(ctx);
|
|
3776
|
+
} catch {
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
// ---------------------------------------------------------------------------
|
|
3782
|
+
// beforeAgent — forward order, accumulate mutations
|
|
3783
|
+
// ---------------------------------------------------------------------------
|
|
3784
|
+
async runBeforeAgent(ctx, params) {
|
|
3785
|
+
this.ensureSorted();
|
|
3786
|
+
let current = { ...params };
|
|
3787
|
+
for (const mw of this.middlewares) {
|
|
3788
|
+
if (!mw.beforeAgent) continue;
|
|
3789
|
+
let result;
|
|
3790
|
+
try {
|
|
3791
|
+
result = await mw.beforeAgent(ctx, current);
|
|
3792
|
+
} catch (error) {
|
|
3793
|
+
const shouldContinue = await this.handleError(ctx, {
|
|
3794
|
+
error,
|
|
3795
|
+
phase: "beforeAgent",
|
|
3796
|
+
middlewareName: mw.name
|
|
3797
|
+
});
|
|
3798
|
+
if (!shouldContinue) throw error;
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
if (!result) continue;
|
|
3802
|
+
if (result.abort) {
|
|
3803
|
+
return {
|
|
3804
|
+
...current,
|
|
3805
|
+
aborted: true,
|
|
3806
|
+
earlyResult: result.earlyResult
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
current = {
|
|
3810
|
+
prompt: result.prompt ?? current.prompt,
|
|
3811
|
+
instructions: result.instructions ?? current.instructions,
|
|
3812
|
+
tools: result.tools ? { ...current.tools, ...result.tools } : current.tools
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
return current;
|
|
3816
|
+
}
|
|
3817
|
+
// ---------------------------------------------------------------------------
|
|
3818
|
+
// afterAgent — reverse order, accumulate mutations
|
|
3819
|
+
// ---------------------------------------------------------------------------
|
|
3820
|
+
async runAfterAgent(ctx, params) {
|
|
3821
|
+
this.ensureSorted();
|
|
3822
|
+
let current = { ...params };
|
|
3823
|
+
for (let i = this.middlewares.length - 1; i >= 0; i--) {
|
|
3824
|
+
const mw = this.middlewares[i];
|
|
3825
|
+
if (!mw.afterAgent) continue;
|
|
3826
|
+
let result;
|
|
3827
|
+
try {
|
|
3828
|
+
result = await mw.afterAgent(ctx, current);
|
|
3829
|
+
} catch (error) {
|
|
3830
|
+
const shouldContinue = await this.handleError(ctx, {
|
|
3831
|
+
error,
|
|
3832
|
+
phase: "afterAgent",
|
|
3833
|
+
middlewareName: mw.name
|
|
3834
|
+
});
|
|
3835
|
+
if (!shouldContinue) throw error;
|
|
3836
|
+
continue;
|
|
3837
|
+
}
|
|
3838
|
+
if (result?.text !== void 0) {
|
|
3839
|
+
current = {
|
|
3840
|
+
...current,
|
|
3841
|
+
result: { ...current.result, text: result.text }
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
return current;
|
|
3846
|
+
}
|
|
3847
|
+
// ---------------------------------------------------------------------------
|
|
3848
|
+
// beforeTool — forward order
|
|
3849
|
+
// ---------------------------------------------------------------------------
|
|
3850
|
+
async runBeforeTool(ctx, params) {
|
|
3851
|
+
this.ensureSorted();
|
|
3852
|
+
let currentArgs = params.args;
|
|
3853
|
+
for (const mw of this.middlewares) {
|
|
3854
|
+
if (!mw.beforeTool) continue;
|
|
3855
|
+
let result;
|
|
3856
|
+
try {
|
|
3857
|
+
result = await mw.beforeTool(ctx, {
|
|
3858
|
+
...params,
|
|
3859
|
+
args: currentArgs
|
|
3860
|
+
});
|
|
3861
|
+
} catch (error) {
|
|
3862
|
+
const shouldContinue = await this.handleError(ctx, {
|
|
3863
|
+
error,
|
|
3864
|
+
phase: "beforeTool",
|
|
3865
|
+
middlewareName: mw.name
|
|
3866
|
+
});
|
|
3867
|
+
if (!shouldContinue) throw error;
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
if (!result) continue;
|
|
3871
|
+
if (result.skip) {
|
|
3872
|
+
return { skip: true, mockResult: result.mockResult };
|
|
3873
|
+
}
|
|
3874
|
+
if (result.args !== void 0) {
|
|
3875
|
+
currentArgs = result.args;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
return { args: currentArgs };
|
|
3879
|
+
}
|
|
3880
|
+
// ---------------------------------------------------------------------------
|
|
3881
|
+
// afterTool — reverse order
|
|
3882
|
+
// ---------------------------------------------------------------------------
|
|
3883
|
+
async runAfterTool(ctx, params) {
|
|
3884
|
+
this.ensureSorted();
|
|
3885
|
+
let current = { ...params };
|
|
3886
|
+
for (let i = this.middlewares.length - 1; i >= 0; i--) {
|
|
3887
|
+
const mw = this.middlewares[i];
|
|
3888
|
+
if (!mw.afterTool) continue;
|
|
3889
|
+
let result;
|
|
3890
|
+
try {
|
|
3891
|
+
result = await mw.afterTool(ctx, current);
|
|
3892
|
+
} catch (error) {
|
|
3893
|
+
const shouldContinue = await this.handleError(ctx, {
|
|
3894
|
+
error,
|
|
3895
|
+
phase: "afterTool",
|
|
3896
|
+
middlewareName: mw.name
|
|
3897
|
+
});
|
|
3898
|
+
if (!shouldContinue) throw error;
|
|
3899
|
+
continue;
|
|
3900
|
+
}
|
|
3901
|
+
if (result?.result !== void 0) {
|
|
3902
|
+
current = { ...current, result: result.result };
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
return current;
|
|
3906
|
+
}
|
|
3907
|
+
// ---------------------------------------------------------------------------
|
|
3908
|
+
// Error handling — delegates to middleware onError hooks
|
|
3909
|
+
// ---------------------------------------------------------------------------
|
|
3910
|
+
async handleError(ctx, params) {
|
|
3911
|
+
for (const mw of this.middlewares) {
|
|
3912
|
+
if (!mw.onError) continue;
|
|
3913
|
+
try {
|
|
3914
|
+
const result = await mw.onError(ctx, params);
|
|
3915
|
+
if (result?.suppress) return true;
|
|
3916
|
+
} catch {
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
return false;
|
|
3920
|
+
}
|
|
3921
|
+
// ---------------------------------------------------------------------------
|
|
3922
|
+
// Internal — sort by priority (stable sort)
|
|
3923
|
+
// ---------------------------------------------------------------------------
|
|
3924
|
+
ensureSorted() {
|
|
3925
|
+
if (this.sorted) return;
|
|
3926
|
+
this.middlewares.sort((a, b) => a.priority - b.priority);
|
|
3927
|
+
this.sorted = true;
|
|
3928
|
+
}
|
|
3929
|
+
};
|
|
3930
|
+
|
|
3931
|
+
// src/agent/agent-config.ts
|
|
3932
|
+
var DEFAULT_APPROVAL_CONFIG = {
|
|
3933
|
+
defaultMode: "approve-all",
|
|
3934
|
+
requireApproval: [],
|
|
3935
|
+
autoApprove: [],
|
|
3936
|
+
onApprovalRequired: async () => true
|
|
3937
|
+
};
|
|
3938
|
+
var DEFAULT_CHECKPOINT_CONFIG = {
|
|
3939
|
+
enabled: true,
|
|
3940
|
+
baseStepInterval: 5,
|
|
3941
|
+
maxCheckpoints: 10
|
|
3942
|
+
};
|
|
3943
|
+
function resolveApprovalConfig(partial) {
|
|
3944
|
+
if (!partial) return { ...DEFAULT_APPROVAL_CONFIG };
|
|
3945
|
+
return { ...DEFAULT_APPROVAL_CONFIG, ...partial };
|
|
3946
|
+
}
|
|
3947
|
+
function resolveCheckpointConfig(partial) {
|
|
3948
|
+
if (!partial) return { ...DEFAULT_CHECKPOINT_CONFIG };
|
|
3949
|
+
return { ...DEFAULT_CHECKPOINT_CONFIG, ...partial };
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
// src/adapters/memory/in-memory.adapter.ts
|
|
3953
|
+
var InMemoryAdapter = class _InMemoryAdapter {
|
|
3954
|
+
static MAX_SESSIONS = 1e3;
|
|
3955
|
+
sessionOrder = /* @__PURE__ */ new Map();
|
|
3956
|
+
todosMap = /* @__PURE__ */ new Map();
|
|
3957
|
+
checkpointsMap = /* @__PURE__ */ new Map();
|
|
3958
|
+
conversationMap = /* @__PURE__ */ new Map();
|
|
3959
|
+
metadataMap = /* @__PURE__ */ new Map();
|
|
3960
|
+
trackSession(sessionId) {
|
|
3961
|
+
this.sessionOrder.delete(sessionId);
|
|
3962
|
+
this.sessionOrder.set(sessionId, true);
|
|
3963
|
+
while (this.sessionOrder.size > _InMemoryAdapter.MAX_SESSIONS) {
|
|
3964
|
+
const oldest = this.sessionOrder.keys().next().value;
|
|
3965
|
+
if (oldest !== void 0) {
|
|
3966
|
+
this.sessionOrder.delete(oldest);
|
|
3967
|
+
this.todosMap.delete(oldest);
|
|
3968
|
+
this.checkpointsMap.delete(oldest);
|
|
3969
|
+
this.conversationMap.delete(oldest);
|
|
3970
|
+
this.metadataMap.delete(oldest);
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
// -- Todos ------------------------------------------------------------------
|
|
3975
|
+
async saveTodos(sessionId, todos) {
|
|
3976
|
+
this.todosMap.set(sessionId, [...todos]);
|
|
3977
|
+
this.trackSession(sessionId);
|
|
3978
|
+
}
|
|
3979
|
+
async loadTodos(sessionId) {
|
|
3980
|
+
return this.todosMap.get(sessionId) ?? [];
|
|
3981
|
+
}
|
|
3982
|
+
// -- Checkpoints ------------------------------------------------------------
|
|
3983
|
+
async saveCheckpoint(sessionId, checkpoint) {
|
|
3984
|
+
const list = this.checkpointsMap.get(sessionId) ?? [];
|
|
3985
|
+
list.push(checkpoint);
|
|
3986
|
+
this.checkpointsMap.set(sessionId, list);
|
|
3987
|
+
this.trackSession(sessionId);
|
|
3988
|
+
}
|
|
3989
|
+
async loadLatestCheckpoint(sessionId) {
|
|
3990
|
+
const list = this.checkpointsMap.get(sessionId);
|
|
3991
|
+
if (!list || list.length === 0) return null;
|
|
3992
|
+
return list[list.length - 1] ?? null;
|
|
3993
|
+
}
|
|
3994
|
+
async listCheckpoints(sessionId) {
|
|
3995
|
+
return this.checkpointsMap.get(sessionId) ?? [];
|
|
3996
|
+
}
|
|
3997
|
+
async deleteOldCheckpoints(sessionId, keepCount) {
|
|
3998
|
+
const list = this.checkpointsMap.get(sessionId);
|
|
3999
|
+
if (!list || list.length <= keepCount) return;
|
|
4000
|
+
this.checkpointsMap.set(sessionId, list.slice(-keepCount));
|
|
4001
|
+
}
|
|
4002
|
+
// -- Conversation -----------------------------------------------------------
|
|
4003
|
+
async saveConversation(sessionId, messages) {
|
|
4004
|
+
this.conversationMap.set(sessionId, [...messages]);
|
|
4005
|
+
this.trackSession(sessionId);
|
|
4006
|
+
}
|
|
4007
|
+
async loadConversation(sessionId) {
|
|
4008
|
+
return this.conversationMap.get(sessionId) ?? [];
|
|
4009
|
+
}
|
|
4010
|
+
// -- Metadata ---------------------------------------------------------------
|
|
4011
|
+
async saveMetadata(sessionId, key, value) {
|
|
4012
|
+
let map = this.metadataMap.get(sessionId);
|
|
4013
|
+
if (!map) {
|
|
4014
|
+
map = /* @__PURE__ */ new Map();
|
|
4015
|
+
this.metadataMap.set(sessionId, map);
|
|
4016
|
+
}
|
|
4017
|
+
map.set(key, value);
|
|
4018
|
+
this.trackSession(sessionId);
|
|
4019
|
+
}
|
|
4020
|
+
async loadMetadata(sessionId, key) {
|
|
4021
|
+
const map = this.metadataMap.get(sessionId);
|
|
4022
|
+
if (!map || !map.has(key)) return null;
|
|
4023
|
+
return map.get(key);
|
|
4024
|
+
}
|
|
4025
|
+
async deleteMetadata(sessionId, key) {
|
|
4026
|
+
this.metadataMap.get(sessionId)?.delete(key);
|
|
4027
|
+
}
|
|
4028
|
+
// -- Utility ----------------------------------------------------------------
|
|
4029
|
+
/** Clear all data for a session */
|
|
4030
|
+
clear(sessionId) {
|
|
4031
|
+
this.todosMap.delete(sessionId);
|
|
4032
|
+
this.checkpointsMap.delete(sessionId);
|
|
4033
|
+
this.conversationMap.delete(sessionId);
|
|
4034
|
+
this.metadataMap.delete(sessionId);
|
|
4035
|
+
this.sessionOrder.delete(sessionId);
|
|
4036
|
+
}
|
|
4037
|
+
/** Clear all sessions */
|
|
4038
|
+
clearAll() {
|
|
4039
|
+
this.todosMap.clear();
|
|
4040
|
+
this.checkpointsMap.clear();
|
|
4041
|
+
this.conversationMap.clear();
|
|
4042
|
+
this.metadataMap.clear();
|
|
4043
|
+
this.sessionOrder.clear();
|
|
4044
|
+
}
|
|
4045
|
+
};
|
|
4046
|
+
|
|
4047
|
+
// src/adapters/token-counter/approximate.adapter.ts
|
|
4048
|
+
var CHARS_PER_TOKEN = 4;
|
|
4049
|
+
var ROLE_OVERHEAD_TOKENS = 4;
|
|
4050
|
+
var CONTEXT_WINDOW_SIZES = {
|
|
4051
|
+
"gpt-5.2": 128e3,
|
|
4052
|
+
"gpt-5.2-mini": 128e3,
|
|
4053
|
+
"gpt-4-turbo": 128e3,
|
|
4054
|
+
"gpt-4": 8192,
|
|
4055
|
+
"gpt-3.5-turbo": 16385,
|
|
4056
|
+
"claude-3.5-sonnet": 2e5,
|
|
4057
|
+
"claude-3-sonnet": 2e5,
|
|
4058
|
+
"claude-3-haiku": 2e5,
|
|
4059
|
+
"claude-3-opus": 2e5,
|
|
4060
|
+
"claude-4-sonnet": 2e5,
|
|
4061
|
+
"gemini-2.5-flash-preview-05-20": 1e6,
|
|
4062
|
+
"gemini-2.5-flash": 1e6,
|
|
4063
|
+
"gemini-2.5-pro": 1e6,
|
|
4064
|
+
"gemini-1.5-flash": 1e6,
|
|
4065
|
+
"gemini-1.5-pro": 2e6
|
|
4066
|
+
};
|
|
4067
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
4068
|
+
var COST_TABLE = {
|
|
4069
|
+
"gpt-5.2": [2.5, 10],
|
|
4070
|
+
"gpt-5.2-mini": [0.15, 0.6],
|
|
4071
|
+
"claude-3.5-sonnet": [3, 15],
|
|
4072
|
+
"claude-3-haiku": [0.25, 1.25],
|
|
4073
|
+
"gemini-2.5-flash-preview-05-20": [0.075, 0.3],
|
|
4074
|
+
"gemini-2.5-flash": [0.15, 0.6],
|
|
4075
|
+
"gemini-2.5-pro": [1.25, 10]
|
|
4076
|
+
};
|
|
4077
|
+
var DEFAULT_COST = [1, 3];
|
|
4078
|
+
var ApproximateTokenCounter = class {
|
|
4079
|
+
count(text) {
|
|
4080
|
+
if (!text) return 0;
|
|
4081
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
4082
|
+
}
|
|
4083
|
+
countMessages(messages) {
|
|
4084
|
+
if (!messages.length) return 0;
|
|
4085
|
+
return messages.reduce(
|
|
4086
|
+
(sum, msg) => sum + this.count(msg.content) + ROLE_OVERHEAD_TOKENS,
|
|
4087
|
+
0
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
4090
|
+
getContextWindowSize(model) {
|
|
4091
|
+
return CONTEXT_WINDOW_SIZES[model] ?? DEFAULT_CONTEXT_WINDOW;
|
|
4092
|
+
}
|
|
4093
|
+
estimateCost(inputTokens, outputTokens, model) {
|
|
4094
|
+
const [inputRate, outputRate] = COST_TABLE[model] ?? DEFAULT_COST;
|
|
4095
|
+
return inputTokens / 1e6 * inputRate + outputTokens / 1e6 * outputRate;
|
|
4096
|
+
}
|
|
4097
|
+
truncate(text, maxTokens) {
|
|
4098
|
+
if (!text) return text;
|
|
4099
|
+
const maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
4100
|
+
if (text.length <= maxChars) return text;
|
|
4101
|
+
return text.slice(0, maxChars);
|
|
4102
|
+
}
|
|
4103
|
+
};
|
|
4104
|
+
|
|
4105
|
+
// src/agent/defaults.ts
|
|
4106
|
+
function defaultFilesystem() {
|
|
4107
|
+
return new VirtualFilesystem();
|
|
4108
|
+
}
|
|
4109
|
+
function defaultMemory() {
|
|
4110
|
+
return new InMemoryAdapter();
|
|
4111
|
+
}
|
|
4112
|
+
function defaultTokenCounter() {
|
|
4113
|
+
return new ApproximateTokenCounter();
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
// src/adapters/resilience/rate-limiter.ts
|
|
4117
|
+
var DEFAULT_RATE_LIMITER_CONFIG = {
|
|
4118
|
+
maxTokens: 10,
|
|
4119
|
+
refillRateMs: 1e3
|
|
4120
|
+
};
|
|
4121
|
+
var RateLimiter = class {
|
|
4122
|
+
constructor(config = DEFAULT_RATE_LIMITER_CONFIG) {
|
|
4123
|
+
this.config = config;
|
|
4124
|
+
this.tokens = config.maxTokens;
|
|
4125
|
+
this.lastRefillTime = Date.now();
|
|
4126
|
+
}
|
|
4127
|
+
tokens;
|
|
4128
|
+
lastRefillTime;
|
|
4129
|
+
waitQueue = [];
|
|
4130
|
+
processingScheduled = false;
|
|
4131
|
+
/**
|
|
4132
|
+
* Acquire a token, waiting if necessary
|
|
4133
|
+
*/
|
|
4134
|
+
async acquire() {
|
|
4135
|
+
if (this.tryAcquire()) {
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
return new Promise((resolve, reject) => {
|
|
4139
|
+
this.waitQueue.push({ resolve, reject });
|
|
4140
|
+
this.processQueue();
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
/**
|
|
4144
|
+
* Try to acquire a token immediately
|
|
4145
|
+
* @returns true if token acquired, false otherwise
|
|
4146
|
+
*/
|
|
4147
|
+
tryAcquire() {
|
|
4148
|
+
this.refillTokens();
|
|
4149
|
+
if (this.tokens > 0) {
|
|
4150
|
+
this.tokens--;
|
|
4151
|
+
return true;
|
|
4152
|
+
}
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Get number of available tokens
|
|
4157
|
+
*/
|
|
4158
|
+
get availableTokens() {
|
|
4159
|
+
this.refillTokens();
|
|
4160
|
+
return this.tokens;
|
|
4161
|
+
}
|
|
4162
|
+
/**
|
|
4163
|
+
* Reset the rate limiter to initial state
|
|
4164
|
+
*/
|
|
4165
|
+
reset() {
|
|
4166
|
+
this.tokens = this.config.maxTokens;
|
|
4167
|
+
this.lastRefillTime = Date.now();
|
|
4168
|
+
this.processingScheduled = false;
|
|
4169
|
+
const queue = [...this.waitQueue];
|
|
4170
|
+
this.waitQueue = [];
|
|
4171
|
+
queue.forEach(({ reject }) => {
|
|
4172
|
+
reject(new RateLimiterError("Rate limiter was reset"));
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
refillTokens() {
|
|
4176
|
+
const now = Date.now();
|
|
4177
|
+
const timePassed = now - this.lastRefillTime;
|
|
4178
|
+
if (timePassed >= this.config.refillRateMs) {
|
|
4179
|
+
const tokensToAdd = Math.floor(timePassed / this.config.refillRateMs);
|
|
4180
|
+
this.tokens = Math.min(this.config.maxTokens, this.tokens + tokensToAdd);
|
|
4181
|
+
this.lastRefillTime = now - timePassed % this.config.refillRateMs;
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
processQueue() {
|
|
4185
|
+
if (this.waitQueue.length === 0 || this.processingScheduled) {
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
this.processingScheduled = true;
|
|
4189
|
+
const timeUntilNextRefill = this.config.refillRateMs - (Date.now() - this.lastRefillTime);
|
|
4190
|
+
const delay = Math.max(0, timeUntilNextRefill);
|
|
4191
|
+
setTimeout(() => {
|
|
4192
|
+
this.processingScheduled = false;
|
|
4193
|
+
this.refillTokens();
|
|
4194
|
+
while (this.waitQueue.length > 0 && this.tokens > 0) {
|
|
4195
|
+
const waiter = this.waitQueue.shift();
|
|
4196
|
+
this.tokens--;
|
|
4197
|
+
waiter.resolve();
|
|
4198
|
+
}
|
|
4199
|
+
if (this.waitQueue.length > 0) {
|
|
4200
|
+
this.processQueue();
|
|
4201
|
+
}
|
|
4202
|
+
}, delay);
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
var RateLimiterError = class extends Error {
|
|
4206
|
+
constructor(message) {
|
|
4207
|
+
super(message);
|
|
4208
|
+
this.name = "RateLimiterError";
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
|
|
4212
|
+
// src/agent/agent-builder.ts
|
|
4213
|
+
var AgentBuilder = class extends AbstractBuilder {
|
|
4214
|
+
agentConfig;
|
|
4215
|
+
maxStepsOverride;
|
|
4216
|
+
fs;
|
|
4217
|
+
memory;
|
|
4218
|
+
learning;
|
|
4219
|
+
userId;
|
|
4220
|
+
tokenCounter;
|
|
4221
|
+
mcp;
|
|
4222
|
+
policyEngine;
|
|
4223
|
+
runtime;
|
|
4224
|
+
costTracker;
|
|
4225
|
+
planning = false;
|
|
4226
|
+
subagents = false;
|
|
4227
|
+
subagentConfig;
|
|
4228
|
+
approvalConfig;
|
|
4229
|
+
// Resilience patterns
|
|
4230
|
+
circuitBreaker;
|
|
4231
|
+
rateLimiter;
|
|
4232
|
+
toolCache;
|
|
4233
|
+
// Telemetry
|
|
4234
|
+
telemetryAdapter;
|
|
4235
|
+
extraTools = {};
|
|
4236
|
+
plugins = [];
|
|
4237
|
+
lifecycleHooks;
|
|
4238
|
+
eventHandlers = [];
|
|
4239
|
+
/** AI SDK Output specification (e.g. Output.object({schema})) */
|
|
4240
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4241
|
+
outputSpec;
|
|
4242
|
+
constructor(config) {
|
|
4243
|
+
super();
|
|
4244
|
+
this.agentConfig = config;
|
|
4245
|
+
}
|
|
4246
|
+
withFilesystem(fs) {
|
|
4247
|
+
this.fs = fs;
|
|
4248
|
+
return this;
|
|
4249
|
+
}
|
|
4250
|
+
withMemory(memory) {
|
|
4251
|
+
this.memory = memory;
|
|
4252
|
+
return this;
|
|
4253
|
+
}
|
|
4254
|
+
withLearning(learning, userId) {
|
|
4255
|
+
this.learning = learning;
|
|
4256
|
+
this.userId = userId;
|
|
4257
|
+
return this;
|
|
4258
|
+
}
|
|
4259
|
+
withTokenCounter(counter) {
|
|
4260
|
+
this.tokenCounter = counter;
|
|
4261
|
+
return this;
|
|
4262
|
+
}
|
|
4263
|
+
withMcp(mcp) {
|
|
4264
|
+
this.mcp = mcp;
|
|
4265
|
+
return this;
|
|
4266
|
+
}
|
|
4267
|
+
withPolicyEngine(policyEngine) {
|
|
4268
|
+
this.policyEngine = policyEngine;
|
|
4269
|
+
return this;
|
|
4270
|
+
}
|
|
4271
|
+
withRuntime(runtime) {
|
|
4272
|
+
this.runtime = runtime;
|
|
4273
|
+
return this;
|
|
4274
|
+
}
|
|
4275
|
+
withCostTracker(tracker) {
|
|
4276
|
+
this.costTracker = tracker;
|
|
4277
|
+
return this;
|
|
4278
|
+
}
|
|
4279
|
+
withPlanning() {
|
|
4280
|
+
this.planning = true;
|
|
4281
|
+
return this;
|
|
4282
|
+
}
|
|
4283
|
+
withSubagents(config) {
|
|
4284
|
+
this.subagents = true;
|
|
4285
|
+
this.subagentConfig = config;
|
|
4286
|
+
return this;
|
|
4287
|
+
}
|
|
4288
|
+
withApproval(config) {
|
|
4289
|
+
this.approvalConfig = config;
|
|
4290
|
+
return this;
|
|
4291
|
+
}
|
|
4292
|
+
withMaxSteps(steps) {
|
|
4293
|
+
this.maxStepsOverride = steps;
|
|
4294
|
+
return this;
|
|
4295
|
+
}
|
|
4296
|
+
withCircuitBreaker(config) {
|
|
4297
|
+
this.circuitBreaker = new CircuitBreaker({ ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config });
|
|
4298
|
+
return this;
|
|
4299
|
+
}
|
|
4300
|
+
withRateLimiter(config) {
|
|
4301
|
+
this.rateLimiter = new RateLimiter({ ...DEFAULT_RATE_LIMITER_CONFIG, ...config });
|
|
4302
|
+
return this;
|
|
4303
|
+
}
|
|
4304
|
+
withToolCache(config) {
|
|
4305
|
+
this.toolCache = new ToolCache({ ...DEFAULT_TOOL_CACHE_CONFIG, ...config });
|
|
4306
|
+
return this;
|
|
4307
|
+
}
|
|
4308
|
+
withTools(tools) {
|
|
4309
|
+
Object.assign(this.extraTools, tools);
|
|
4310
|
+
return this;
|
|
4311
|
+
}
|
|
4312
|
+
withTool(name, tool2) {
|
|
4313
|
+
this.extraTools[name] = tool2;
|
|
4314
|
+
return this;
|
|
4315
|
+
}
|
|
4316
|
+
withPlugin(plugin) {
|
|
4317
|
+
this.plugins.push(plugin);
|
|
4318
|
+
return this;
|
|
4319
|
+
}
|
|
4320
|
+
on(type, handler) {
|
|
4321
|
+
this.eventHandlers.push({ type, handler });
|
|
4322
|
+
return this;
|
|
4323
|
+
}
|
|
4324
|
+
withLifecycle(hooks) {
|
|
4325
|
+
this.lifecycleHooks = hooks;
|
|
4326
|
+
return this;
|
|
4327
|
+
}
|
|
4328
|
+
withTelemetry(adapter) {
|
|
4329
|
+
this.telemetryAdapter = adapter;
|
|
4330
|
+
return this;
|
|
4331
|
+
}
|
|
4332
|
+
/**
|
|
4333
|
+
* Set structured output specification (AI SDK passthrough).
|
|
4334
|
+
* Usage: `.withOutput(Output.object({ schema: z.object({...}) }))`
|
|
4335
|
+
*/
|
|
4336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4337
|
+
withOutput(output) {
|
|
4338
|
+
this.outputSpec = output;
|
|
4339
|
+
return this;
|
|
4340
|
+
}
|
|
4341
|
+
withInstructions(instructionsOrTemplate, variables) {
|
|
4342
|
+
if (typeof instructionsOrTemplate === "string") {
|
|
4343
|
+
this.agentConfig.instructions = instructionsOrTemplate;
|
|
4344
|
+
} else {
|
|
4345
|
+
this.agentConfig.instructions = instructionsOrTemplate.compile(variables);
|
|
4346
|
+
}
|
|
4347
|
+
return this;
|
|
4348
|
+
}
|
|
4349
|
+
validate() {
|
|
4350
|
+
if (!this.agentConfig.model) throw new Error("model is required");
|
|
4351
|
+
if (!this.agentConfig.instructions) throw new Error("instructions is required");
|
|
4352
|
+
}
|
|
4353
|
+
construct() {
|
|
4354
|
+
const agent = new Agent({
|
|
4355
|
+
model: this.agentConfig.model,
|
|
4356
|
+
instructions: this.agentConfig.instructions,
|
|
4357
|
+
id: this.agentConfig.id,
|
|
4358
|
+
name: this.agentConfig.name,
|
|
4359
|
+
maxSteps: this.maxStepsOverride ?? this.agentConfig.maxSteps ?? 30,
|
|
4360
|
+
fs: this.fs ?? defaultFilesystem(),
|
|
4361
|
+
memory: this.memory ?? defaultMemory(),
|
|
4362
|
+
learning: this.learning,
|
|
4363
|
+
userId: this.userId,
|
|
4364
|
+
tokenCounter: this.tokenCounter ?? defaultTokenCounter(),
|
|
4365
|
+
mcp: this.mcp,
|
|
4366
|
+
policyEngine: this.policyEngine,
|
|
4367
|
+
runtime: this.runtime,
|
|
4368
|
+
costTracker: this.costTracker,
|
|
4369
|
+
planning: this.planning,
|
|
4370
|
+
subagents: this.subagents,
|
|
4371
|
+
subagentConfig: this.subagentConfig,
|
|
4372
|
+
approvalConfig: this.approvalConfig ? resolveApprovalConfig(this.approvalConfig) : void 0,
|
|
4373
|
+
checkpointConfig: resolveCheckpointConfig(
|
|
4374
|
+
this.agentConfig.checkpoint
|
|
4375
|
+
),
|
|
4376
|
+
extraTools: this.extraTools,
|
|
4377
|
+
plugins: this.plugins,
|
|
4378
|
+
circuitBreaker: this.circuitBreaker,
|
|
4379
|
+
rateLimiter: this.rateLimiter,
|
|
4380
|
+
toolCache: this.toolCache,
|
|
4381
|
+
lifecycleHooks: this.lifecycleHooks,
|
|
4382
|
+
telemetry: this.telemetryAdapter,
|
|
4383
|
+
output: this.outputSpec
|
|
4384
|
+
});
|
|
4385
|
+
for (const { type, handler } of this.eventHandlers) {
|
|
4386
|
+
agent.eventBus.on(type, handler);
|
|
4387
|
+
}
|
|
4388
|
+
return agent;
|
|
4389
|
+
}
|
|
4390
|
+
build() {
|
|
4391
|
+
return super.build();
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
|
|
4395
|
+
// src/agent/agent.ts
|
|
4396
|
+
var Agent = class _Agent {
|
|
4397
|
+
sessionId;
|
|
4398
|
+
eventBus;
|
|
4399
|
+
config;
|
|
4400
|
+
_runtime;
|
|
4401
|
+
_runtimePromise = null;
|
|
4402
|
+
tokenTracker;
|
|
4403
|
+
pluginManager;
|
|
4404
|
+
middlewareChain;
|
|
4405
|
+
_toolManager;
|
|
4406
|
+
_executionEngine;
|
|
4407
|
+
lifecycleManager;
|
|
4408
|
+
// Resilience patterns
|
|
4409
|
+
circuitBreaker;
|
|
4410
|
+
rateLimiter;
|
|
4411
|
+
toolCache;
|
|
4412
|
+
// Telemetry
|
|
4413
|
+
telemetry;
|
|
4414
|
+
constructor(config) {
|
|
4415
|
+
this._runtime = config.runtime ?? null;
|
|
4416
|
+
this.sessionId = config.id ?? (this._runtime ? this._runtime.randomUUID() : crypto.randomUUID());
|
|
4417
|
+
this.eventBus = new EventBus(this.sessionId);
|
|
4418
|
+
this.config = config;
|
|
4419
|
+
this.tokenTracker = new TokenTracker(config.tokenCounter, {
|
|
4420
|
+
maxInputTokens: Infinity,
|
|
4421
|
+
maxOutputTokens: Infinity,
|
|
4422
|
+
maxTotalTokens: Infinity,
|
|
4423
|
+
warningThreshold: 0.9
|
|
4424
|
+
});
|
|
4425
|
+
this.pluginManager = new PluginManager();
|
|
4426
|
+
for (const plugin of config.plugins ?? []) {
|
|
4427
|
+
this.pluginManager.register(plugin);
|
|
4428
|
+
}
|
|
4429
|
+
this.middlewareChain = new MiddlewareChain();
|
|
4430
|
+
for (const mw of config.middleware ?? []) {
|
|
4431
|
+
this.middlewareChain.use(mw);
|
|
4432
|
+
}
|
|
4433
|
+
this.circuitBreaker = config.circuitBreaker;
|
|
4434
|
+
this.rateLimiter = config.rateLimiter;
|
|
4435
|
+
this.toolCache = config.toolCache;
|
|
4436
|
+
this.telemetry = config.telemetry;
|
|
4437
|
+
this.lifecycleManager = new LifecycleManager(config.lifecycleHooks ?? {});
|
|
4438
|
+
}
|
|
4439
|
+
// ---------------------------------------------------------------------------
|
|
4440
|
+
// Lazy-initialized heavy managers (created on first access)
|
|
4441
|
+
// ---------------------------------------------------------------------------
|
|
4442
|
+
get toolManager() {
|
|
4443
|
+
return this._toolManager ??= new ToolManager(
|
|
4444
|
+
{
|
|
4445
|
+
model: this.config.model,
|
|
4446
|
+
instructions: this.config.instructions,
|
|
4447
|
+
name: this.config.name,
|
|
4448
|
+
maxSteps: this.config.maxSteps,
|
|
4449
|
+
fs: this.config.fs,
|
|
4450
|
+
memory: this.config.memory,
|
|
4451
|
+
learning: this.config.learning,
|
|
4452
|
+
mcp: this.config.mcp,
|
|
4453
|
+
policyEngine: this.config.policyEngine,
|
|
4454
|
+
userId: this.config.userId,
|
|
4455
|
+
planning: this.config.planning,
|
|
4456
|
+
subagents: this.config.subagents,
|
|
4457
|
+
subagentConfig: this.config.subagentConfig,
|
|
4458
|
+
approvalConfig: this.config.approvalConfig,
|
|
4459
|
+
extraTools: this.config.extraTools
|
|
4460
|
+
},
|
|
4461
|
+
this.pluginManager,
|
|
4462
|
+
this.circuitBreaker,
|
|
4463
|
+
this.rateLimiter,
|
|
4464
|
+
this.toolCache
|
|
4465
|
+
);
|
|
4466
|
+
}
|
|
4467
|
+
get executionEngine() {
|
|
4468
|
+
return this._executionEngine ??= new ExecutionEngine(
|
|
4469
|
+
{
|
|
4470
|
+
model: this.config.model,
|
|
4471
|
+
instructions: this.config.instructions,
|
|
4472
|
+
maxSteps: this.config.maxSteps,
|
|
4473
|
+
memory: this.config.memory,
|
|
4474
|
+
learning: this.config.learning,
|
|
4475
|
+
userId: this.config.userId,
|
|
4476
|
+
agentName: this.config.name,
|
|
4477
|
+
delegationHooks: this.config.subagentConfig?.hooks,
|
|
4478
|
+
checkpointConfig: this.config.checkpointConfig,
|
|
4479
|
+
telemetry: this.config.telemetry,
|
|
4480
|
+
costTracker: this.config.costTracker,
|
|
4481
|
+
output: this.config.output
|
|
4482
|
+
},
|
|
4483
|
+
this.toolManager,
|
|
4484
|
+
this.pluginManager,
|
|
4485
|
+
this.eventBus,
|
|
4486
|
+
this.tokenTracker,
|
|
4487
|
+
this.middlewareChain
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
/** Lazy-initialized runtime adapter. Resolves on first use. */
|
|
4491
|
+
async ensureRuntime() {
|
|
4492
|
+
if (this._runtime) return this._runtime;
|
|
4493
|
+
if (!this._runtimePromise) {
|
|
4494
|
+
this._runtimePromise = createRuntimeAdapterAsync().then((rt) => {
|
|
4495
|
+
this._runtime = rt;
|
|
4496
|
+
return rt;
|
|
4497
|
+
});
|
|
4498
|
+
}
|
|
4499
|
+
return this._runtimePromise;
|
|
4500
|
+
}
|
|
4501
|
+
// ---------------------------------------------------------------------------
|
|
4502
|
+
// Static factories
|
|
4503
|
+
// ---------------------------------------------------------------------------
|
|
4504
|
+
static create(config) {
|
|
4505
|
+
return new AgentBuilder(config);
|
|
4506
|
+
}
|
|
4507
|
+
static minimal(config) {
|
|
4508
|
+
return _Agent.create(config).withPlanning().build();
|
|
4509
|
+
}
|
|
4510
|
+
static full(config) {
|
|
4511
|
+
const builder = _Agent.create(config).withPlanning().withSubagents();
|
|
4512
|
+
if (config.memory) builder.withMemory(config.memory);
|
|
4513
|
+
if (config.mcp) builder.withMcp(config.mcp);
|
|
4514
|
+
if (config.tokenCounter) builder.withTokenCounter(config.tokenCounter);
|
|
4515
|
+
return builder.build();
|
|
4516
|
+
}
|
|
4517
|
+
/**
|
|
4518
|
+
* Auto-configuring factory that works in any runtime.
|
|
4519
|
+
* Uses universal adapters (VirtualFilesystem, InMemoryAdapter, ApproximateTokenCounter)
|
|
4520
|
+
* that require zero platform-specific APIs.
|
|
4521
|
+
*
|
|
4522
|
+
* For runtime-specific adapters (LocalFilesystem, DenoFilesystem, OpfsFilesystem, etc.),
|
|
4523
|
+
* use `Agent.create()` and compose manually.
|
|
4524
|
+
*/
|
|
4525
|
+
static auto(config) {
|
|
4526
|
+
return _Agent.create(config).withPlanning().build();
|
|
4527
|
+
}
|
|
4528
|
+
// ---------------------------------------------------------------------------
|
|
4529
|
+
// Lifecycle Management
|
|
4530
|
+
// ---------------------------------------------------------------------------
|
|
4531
|
+
async startup() {
|
|
4532
|
+
return this.lifecycleManager.startup();
|
|
4533
|
+
}
|
|
4534
|
+
async shutdown() {
|
|
4535
|
+
return this.lifecycleManager.shutdown();
|
|
4536
|
+
}
|
|
4537
|
+
async healthCheck() {
|
|
4538
|
+
return this.lifecycleManager.healthCheck();
|
|
4539
|
+
}
|
|
4540
|
+
get isReady() {
|
|
4541
|
+
return this.lifecycleManager.isReady;
|
|
4542
|
+
}
|
|
4543
|
+
get isShuttingDown() {
|
|
4544
|
+
return this.lifecycleManager.isShuttingDown;
|
|
4545
|
+
}
|
|
4546
|
+
// ---------------------------------------------------------------------------
|
|
4547
|
+
// Run & Stream - Delegation to ExecutionEngine
|
|
4548
|
+
// ---------------------------------------------------------------------------
|
|
4549
|
+
async run(prompt, options = {}) {
|
|
4550
|
+
await this.ensureRuntime();
|
|
4551
|
+
return this.executionEngine.run(prompt, this.sessionId, this._runtime, options);
|
|
4552
|
+
}
|
|
4553
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4554
|
+
async stream(params, options = {}) {
|
|
4555
|
+
await this.ensureRuntime();
|
|
4556
|
+
return this.executionEngine.stream(params, this.sessionId, this._runtime, options);
|
|
4557
|
+
}
|
|
4558
|
+
// ---------------------------------------------------------------------------
|
|
4559
|
+
// Cleanup
|
|
4560
|
+
// ---------------------------------------------------------------------------
|
|
4561
|
+
async dispose() {
|
|
4562
|
+
try {
|
|
4563
|
+
await this.lifecycleManager.shutdown();
|
|
4564
|
+
} catch {
|
|
4565
|
+
}
|
|
4566
|
+
try {
|
|
4567
|
+
await this.config.telemetry?.flush();
|
|
4568
|
+
} catch {
|
|
4569
|
+
}
|
|
4570
|
+
try {
|
|
4571
|
+
await this.pluginManager.dispose();
|
|
4572
|
+
} finally {
|
|
4573
|
+
try {
|
|
4574
|
+
if (this.config.mcp) await this.config.mcp.closeAll();
|
|
4575
|
+
} finally {
|
|
4576
|
+
this.eventBus.removeAllListeners();
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
4581
|
+
|
|
4582
|
+
export {
|
|
4583
|
+
AbstractBuilder,
|
|
4584
|
+
VirtualFilesystem,
|
|
4585
|
+
AgentBuilder,
|
|
4586
|
+
Agent
|
|
4587
|
+
};
|
|
4588
|
+
//# sourceMappingURL=chunk-BI2G665F.js.map
|