@rebon/cli-darwin-arm64 0.17.2 → 0.18.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/bin/compose-runtime/package.json +17 -0
- package/bin/compose-runtime/payload/compose/credentials-runtime.js +38 -0
- package/bin/compose-runtime/payload/compose/llm-runtime.js +330 -0
- package/bin/compose-runtime/payload/compose/loop-assembly.js +149 -0
- package/bin/compose-runtime/payload/compose/serve-dispatch.js +82 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-llm.js +313 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-settings.js +43 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-tools.js +20 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-web.js +10 -0
- package/bin/compose-runtime/payload/compose/shims/llm-adapter.js +32 -0
- package/bin/compose-runtime/payload/compose/shims/zod-lite.js +49 -0
- package/bin/compose-runtime/payload/compose/systemprompt-runtime.js +74 -0
- package/bin/compose-runtime/payload/compose/tools-runtime.js +228 -0
- package/bin/compose-runtime/payload/compose/web-runtime.js +204 -0
- package/bin/compose-runtime/payload/vendor/cordis/index.js +1530 -0
- package/bin/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -0
- package/bin/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -0
- package/bin/compose-runtime/payload/vendor/dsh/agent.js +714 -0
- package/bin/compose-runtime/payload/vendor/dsh/llm-core.js +269 -0
- package/bin/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -0
- package/bin/compose-runtime/payload/vendor/dsh/logger-console.js +83 -0
- package/bin/compose-runtime/payload/vendor/dsh/scope.js +287 -0
- package/bin/compose-runtime/payload/vendor/dsh/session.js +1668 -0
- package/bin/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -0
- package/bin/compose-runtime/payload/vendor/dsh/timeout.js +100 -0
- package/bin/compose-runtime/payload/vendor/dsh/timer.js +128 -0
- package/bin/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -0
- package/bin/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -0
- package/bin/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -0
- package/bin/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -0
- package/bin/compose-runtime/payload/vendor/schemastery/index.mjs +656 -0
- package/bin/compose-runtime/payload-manifests.json +48 -0
- package/bin/compose-runtime/src/bridge.mjs +142 -0
- package/bin/compose-runtime/src/credentials-runtime.mjs +50 -0
- package/bin/compose-runtime/src/eventsource-stream.mjs +48 -0
- package/bin/compose-runtime/src/index.mjs +7 -0
- package/bin/compose-runtime/src/llm-runtime.mjs +326 -0
- package/bin/compose-runtime/src/loader.mjs +78 -0
- package/bin/compose-runtime/src/loop-assembly.mjs +237 -0
- package/bin/compose-runtime/src/payload.mjs +49 -0
- package/bin/compose-runtime/src/plugin.mjs +59 -0
- package/bin/compose-runtime/src/realm.mjs +223 -0
- package/bin/compose-runtime/src/registry.mjs +191 -0
- package/bin/compose-runtime/src/resolve.mjs +115 -0
- package/bin/compose-runtime/src/systemprompt-runtime.mjs +67 -0
- package/bin/compose-runtime/src/tools-runtime.mjs +239 -0
- package/bin/compose-runtime/src/web-runtime.mjs +209 -0
- package/bin/plugin-host/package.json +11 -0
- package/bin/plugin-host/src/bridge.mjs +64 -0
- package/bin/plugin-host/src/cli.mjs +105 -0
- package/bin/plugin-host/src/framing.mjs +113 -0
- package/bin/plugin-host/src/host.mjs +624 -0
- package/bin/plugin-host/src/json.mjs +196 -0
- package/bin/plugin-host/src/lifecycle.mjs +114 -0
- package/bin/plugin-host/src/loader.mjs +142 -0
- package/bin/plugin-host/src/methods.mjs +256 -0
- package/bin/plugin-host/src/protocol.mjs +129 -0
- package/bin/plugin-host/src/sdk.mjs +7 -0
- package/bin/rebon +0 -0
- package/bin/rebon-boa-helper +0 -0
- package/package.json +4 -1
|
@@ -0,0 +1,1193 @@
|
|
|
1
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
8
|
+
var dispose, inner;
|
|
9
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
dispose = value[__knownSymbol("dispose")];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
15
|
+
if (inner) dispose = function() {
|
|
16
|
+
try {
|
|
17
|
+
inner.call(this);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return Promise.reject(e);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
stack.push([async, dispose, value]);
|
|
23
|
+
} else if (async) {
|
|
24
|
+
stack.push([async]);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __callDispose = (stack, error, hasError) => {
|
|
29
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
30
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
31
|
+
};
|
|
32
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
33
|
+
var next = (it) => {
|
|
34
|
+
while (it = stack.pop()) {
|
|
35
|
+
try {
|
|
36
|
+
var result = it[1] && it[1].call(it[2]);
|
|
37
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hasError) throw error;
|
|
43
|
+
};
|
|
44
|
+
return next();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// packages/core/agent-loop/src/index.ts
|
|
48
|
+
import { FiberState, Service } from "@deepseek-ai/cordis";
|
|
49
|
+
import { randomUUID } from "node:crypto";
|
|
50
|
+
import z from "@deepseek-ai/schemastery";
|
|
51
|
+
import { emitAgentEvent } from "@deepseek-ai/dsh-agent";
|
|
52
|
+
import { errorChain as errorChain2 } from "@deepseek-ai/dsh-llm";
|
|
53
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
54
|
+
import { SessionId, SessionPreparation } from "@deepseek-ai/dsh-session";
|
|
55
|
+
|
|
56
|
+
// packages/core/agent-loop/src/agent.ts
|
|
57
|
+
import { Inbox, agentEvents, assembleContextFor } from "@deepseek-ai/dsh-agent";
|
|
58
|
+
import {
|
|
59
|
+
BlockAssembler,
|
|
60
|
+
LlmError,
|
|
61
|
+
createAssistantMessage,
|
|
62
|
+
deepFreeze,
|
|
63
|
+
errorChain,
|
|
64
|
+
markAgentLoopRequest
|
|
65
|
+
} from "@deepseek-ai/dsh-llm";
|
|
66
|
+
import { createScope } from "@deepseek-ai/dsh-scope";
|
|
67
|
+
import { canonicalHeader, headerEquals } from "@deepseek-ai/dsh-session";
|
|
68
|
+
import { joinContextSections, renderContextSections, renderPrompt } from "@deepseek-ai/dsh-system-prompt";
|
|
69
|
+
|
|
70
|
+
// packages/core/agent-loop/src/runtime-context.ts
|
|
71
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
72
|
+
import { isReplacementSurfaceEvent } from "@deepseek-ai/dsh-session";
|
|
73
|
+
var SOURCE = "@deepseek-ai/dsh-system-prompt";
|
|
74
|
+
var CLEARED = "Current runtime context: none. Earlier runtime-context snapshots no longer apply.";
|
|
75
|
+
function isOwned(message) {
|
|
76
|
+
return message.source.kind === "plugin" && message.source.plugin === SOURCE;
|
|
77
|
+
}
|
|
78
|
+
function textOf(message) {
|
|
79
|
+
const [block] = message.content;
|
|
80
|
+
return message.content.length === 1 && block?.type === "text" ? block.text : void 0;
|
|
81
|
+
}
|
|
82
|
+
var RuntimeContextProjection = class {
|
|
83
|
+
/** `undefined` means no snapshot ever existed; `null` means none is retained. */
|
|
84
|
+
retained;
|
|
85
|
+
/**
|
|
86
|
+
* Restore projection state once, then follow authoritative session events.
|
|
87
|
+
* @param ctx - agent-scoped event context.
|
|
88
|
+
* @param session - session receiving projected messages.
|
|
89
|
+
*/
|
|
90
|
+
constructor(ctx, session) {
|
|
91
|
+
const surface = new Set(session.surface.nodes);
|
|
92
|
+
for (let index = session.events.length - 1; index >= 0; index -= 1) {
|
|
93
|
+
const event = session.events[index];
|
|
94
|
+
if (event?.type !== "user/message" || !isOwned(event.data)) continue;
|
|
95
|
+
this.retained ??= null;
|
|
96
|
+
if (surface.has(event.seq)) {
|
|
97
|
+
this.retained = { seq: event.seq, text: textOf(event.data) };
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ctx.on("session/event", (subject, event) => {
|
|
102
|
+
if (subject !== session) return;
|
|
103
|
+
if (event.type === "user/message" && isOwned(event.data)) {
|
|
104
|
+
this.retained = { seq: event.seq, text: textOf(event.data) };
|
|
105
|
+
} else if (this.retained && isReplacementSurfaceEvent(event) && event.sourceEventSeqs?.includes(this.retained.seq) === true) {
|
|
106
|
+
this.retained = null;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create an uncommitted snapshot only when the retained value differs.
|
|
112
|
+
* @param current - fully rendered dynamic context.
|
|
113
|
+
* @param sections - named contributions that formed the current snapshot.
|
|
114
|
+
* @returns a candidate user message, or `undefined` when no update is needed.
|
|
115
|
+
*/
|
|
116
|
+
project(current, sections) {
|
|
117
|
+
if (this.retained === void 0 && current.length === 0) return;
|
|
118
|
+
const snapshot = current.length === 0 ? CLEARED : current;
|
|
119
|
+
if (this.retained?.text === snapshot) return;
|
|
120
|
+
return createUserMessage({
|
|
121
|
+
content: [{ type: "text", text: snapshot }],
|
|
122
|
+
// The cleared marker has no contributions left to attribute.
|
|
123
|
+
source: sections.length === 0 ? { kind: "plugin", plugin: SOURCE } : { kind: "plugin", plugin: SOURCE, form: "snapshot", sections }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// packages/core/agent-loop/src/tool-calls.ts
|
|
129
|
+
import { assertNever, createToolResultMessage } from "@deepseek-ai/dsh-llm";
|
|
130
|
+
import { TOOL_ABORTED_BEFORE_DISPATCH, TOOL_RUNTIME_SCHEDULER } from "@deepseek-ai/dsh-tools";
|
|
131
|
+
async function executeToolCalls(ctx, turn, step, toolCalls, signal, acceptContext) {
|
|
132
|
+
const agent = ctx.agents.requireInitiator();
|
|
133
|
+
const { session } = agent;
|
|
134
|
+
const planned = toolCalls.map((block) => ({
|
|
135
|
+
block,
|
|
136
|
+
exec: {
|
|
137
|
+
callId: block.id,
|
|
138
|
+
name: block.name,
|
|
139
|
+
arguments: parseArguments(block.arguments),
|
|
140
|
+
agent,
|
|
141
|
+
signal
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
144
|
+
let next = 0;
|
|
145
|
+
let concluded = false;
|
|
146
|
+
while (next < planned.length) {
|
|
147
|
+
const first = planned[next];
|
|
148
|
+
const mode = ctx.tools.executionMode(first.exec).kind;
|
|
149
|
+
const group = mode === "parallel" ? planned.slice(next) : [first];
|
|
150
|
+
const outcome = await runGroup(
|
|
151
|
+
ctx,
|
|
152
|
+
turn,
|
|
153
|
+
step,
|
|
154
|
+
group,
|
|
155
|
+
mode,
|
|
156
|
+
signal,
|
|
157
|
+
acceptContext
|
|
158
|
+
);
|
|
159
|
+
next += outcome.consumed;
|
|
160
|
+
concluded ||= outcome.concluded;
|
|
161
|
+
if (outcome.aborted) {
|
|
162
|
+
for (const call of planned.slice(next)) appendSkippedToolCall(session, turn, step, call.block);
|
|
163
|
+
return { concluded };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { concluded };
|
|
167
|
+
}
|
|
168
|
+
function parseArguments(raw) {
|
|
169
|
+
try {
|
|
170
|
+
return raw ? JSON.parse(raw) : {};
|
|
171
|
+
} catch {
|
|
172
|
+
return raw;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function runGroup(ctx, turn, step, group, mode, signal, acceptContext) {
|
|
176
|
+
const { session } = ctx.agents.requireInitiator();
|
|
177
|
+
const { maxParallelToolCalls } = ctx.agentLoop.config;
|
|
178
|
+
const slots = group.map(() => void 0);
|
|
179
|
+
const callSeqs = group.map(() => -1);
|
|
180
|
+
let nextToStart = 0;
|
|
181
|
+
let committed = 0;
|
|
182
|
+
let started = 0;
|
|
183
|
+
let aborted = signal.aborted;
|
|
184
|
+
let concluded = false;
|
|
185
|
+
let schedulerFailure;
|
|
186
|
+
const throwSchedulerFailure = () => {
|
|
187
|
+
if (schedulerFailure !== void 0) throw schedulerFailure.error;
|
|
188
|
+
};
|
|
189
|
+
const commitReady = async () => {
|
|
190
|
+
while (committed < group.length) {
|
|
191
|
+
const slot = slots[committed];
|
|
192
|
+
if (slot === void 0) break;
|
|
193
|
+
const call = group[committed];
|
|
194
|
+
const result = slot.needsPost ? await ctx.tools[TOOL_RUNTIME_SCHEDULER].finalize(slot.exec, slot.result) : ctx.tools[TOOL_RUNTIME_SCHEDULER].finish(slot.exec, slot.result);
|
|
195
|
+
appendToolResult(session, turn, step, call.block, result, callSeqs[committed]);
|
|
196
|
+
for (const context of result.additionalContexts ?? []) acceptContext(context);
|
|
197
|
+
concluded ||= result.concludesTurn === true;
|
|
198
|
+
committed++;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
202
|
+
const startCall = async (index) => {
|
|
203
|
+
const call = group[index];
|
|
204
|
+
callSeqs[index] = appendToolCall(session, turn, step, call.block);
|
|
205
|
+
started++;
|
|
206
|
+
const prepared = await ctx.tools[TOOL_RUNTIME_SCHEDULER].prepare(call.exec);
|
|
207
|
+
throwSchedulerFailure();
|
|
208
|
+
switch (prepared.kind) {
|
|
209
|
+
case "dispatch": {
|
|
210
|
+
const promise = ctx.tools[TOOL_RUNTIME_SCHEDULER].dispatch(prepared.exec).then(
|
|
211
|
+
(outcome) => {
|
|
212
|
+
slots[index] = { exec: prepared.exec, result: outcome.result, needsPost: outcome.kind === "post-result" };
|
|
213
|
+
return index;
|
|
214
|
+
},
|
|
215
|
+
(error) => {
|
|
216
|
+
schedulerFailure ??= { error };
|
|
217
|
+
return index;
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
inFlight.set(index, promise);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "post-result":
|
|
224
|
+
slots[index] = { exec: prepared.exec, result: prepared.result, needsPost: true };
|
|
225
|
+
break;
|
|
226
|
+
case "final-result":
|
|
227
|
+
slots[index] = { exec: prepared.exec, result: prepared.result, needsPost: false };
|
|
228
|
+
break;
|
|
229
|
+
/* v8 ignore next -- closed-union exhaustiveness guard */
|
|
230
|
+
default:
|
|
231
|
+
assertNever(prepared, "tool-call scheduler prepare result");
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const fillPool = async () => {
|
|
235
|
+
while (!aborted && nextToStart < group.length && inFlight.size < maxParallelToolCalls) {
|
|
236
|
+
const nextCall = group[nextToStart];
|
|
237
|
+
if (nextToStart > 0 && mode === "parallel" && ctx.tools.executionMode(nextCall.exec).kind !== "parallel") break;
|
|
238
|
+
await startCall(nextToStart);
|
|
239
|
+
nextToStart++;
|
|
240
|
+
throwSchedulerFailure();
|
|
241
|
+
await commitReady();
|
|
242
|
+
throwSchedulerFailure();
|
|
243
|
+
if (signal.aborted) aborted = true;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
await fillPool();
|
|
248
|
+
while (inFlight.size > 0) {
|
|
249
|
+
const settledIndex = await Promise.race(inFlight.values());
|
|
250
|
+
inFlight.delete(settledIndex);
|
|
251
|
+
throwSchedulerFailure();
|
|
252
|
+
await commitReady();
|
|
253
|
+
throwSchedulerFailure();
|
|
254
|
+
if (signal.aborted) aborted = true;
|
|
255
|
+
await fillPool();
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
schedulerFailure ??= { error };
|
|
259
|
+
await Promise.allSettled(inFlight.values());
|
|
260
|
+
throw schedulerFailure.error;
|
|
261
|
+
}
|
|
262
|
+
if (aborted) {
|
|
263
|
+
for (const call of group.slice(started)) appendSkippedToolCall(session, turn, step, call.block);
|
|
264
|
+
return { consumed: group.length, aborted: true, concluded };
|
|
265
|
+
}
|
|
266
|
+
if (committed !== started) throw new Error("tool-call scheduler: uncommitted settled calls");
|
|
267
|
+
return { consumed: started, aborted: false, concluded };
|
|
268
|
+
}
|
|
269
|
+
function appendSkippedToolCall(session, turn, step, block) {
|
|
270
|
+
const callSeq = appendToolCall(session, turn, step, block);
|
|
271
|
+
appendToolResult(session, turn, step, block, {
|
|
272
|
+
content: [{ type: "text", text: "Error: tool call aborted before dispatch" }],
|
|
273
|
+
isError: true,
|
|
274
|
+
error: {
|
|
275
|
+
message: "tool call aborted before dispatch",
|
|
276
|
+
info: { name: "AbortError", code: TOOL_ABORTED_BEFORE_DISPATCH }
|
|
277
|
+
}
|
|
278
|
+
}, callSeq);
|
|
279
|
+
}
|
|
280
|
+
function appendToolCall(session, turn, step, block) {
|
|
281
|
+
const event = session.append("tool/call", { turn, step, callId: block.id, name: block.name, arguments: block.arguments });
|
|
282
|
+
return event.seq;
|
|
283
|
+
}
|
|
284
|
+
function appendToolResult(session, turn, step, block, result, callSeq) {
|
|
285
|
+
const message = createToolResultMessage({
|
|
286
|
+
callId: block.id,
|
|
287
|
+
content: result.content,
|
|
288
|
+
isError: result.isError
|
|
289
|
+
});
|
|
290
|
+
session.append("tool/result", {
|
|
291
|
+
turn,
|
|
292
|
+
step,
|
|
293
|
+
message,
|
|
294
|
+
...result.error?.info ? { error: result.error.info } : {},
|
|
295
|
+
// The tool's private presentation payload (e.g. a result-time diff),
|
|
296
|
+
// persisted so a UI bridge reproduces the card on replay.
|
|
297
|
+
...result.meta !== void 0 ? { meta: result.meta } : {}
|
|
298
|
+
}, { surfaceOp: "append", sourceEventSeqs: [callSeq] });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// packages/core/agent-loop/src/agent.ts
|
|
302
|
+
function requestProposal(header) {
|
|
303
|
+
if (header.adapterDefaults === void 0) return header.config;
|
|
304
|
+
const proposal = { ...header.config };
|
|
305
|
+
if (header.adapterDefaults.reasoningEffort === true) delete proposal.reasoningEffort;
|
|
306
|
+
if (header.adapterDefaults.maxTokens === true) delete proposal.maxTokens;
|
|
307
|
+
return proposal;
|
|
308
|
+
}
|
|
309
|
+
var ReactLoopAgent = class {
|
|
310
|
+
constructor(loopCtx, id, options, session) {
|
|
311
|
+
this.loopCtx = loopCtx;
|
|
312
|
+
this.id = id;
|
|
313
|
+
this.options = options;
|
|
314
|
+
this.session = session;
|
|
315
|
+
this.dispatch = agentEvents(loopCtx, this);
|
|
316
|
+
this.inbox = new Inbox(session, {
|
|
317
|
+
inserted: (message) => {
|
|
318
|
+
this.dispatch.emit("agent/inbox/inserted", { message });
|
|
319
|
+
},
|
|
320
|
+
discarded: (message) => {
|
|
321
|
+
this.dispatch.emit("agent/inbox/discarded", { message });
|
|
322
|
+
},
|
|
323
|
+
claimed: (message, turn) => {
|
|
324
|
+
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const lastTurn = session.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
328
|
+
this.phase = { kind: "idle", lastTurn };
|
|
329
|
+
this.scope = createScope(loopCtx, this);
|
|
330
|
+
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
331
|
+
this.runtimeContext = new RuntimeContextProjection(this.ctx, session);
|
|
332
|
+
}
|
|
333
|
+
loopCtx;
|
|
334
|
+
id;
|
|
335
|
+
options;
|
|
336
|
+
session;
|
|
337
|
+
inbox;
|
|
338
|
+
phase;
|
|
339
|
+
activityDone = Promise.resolve();
|
|
340
|
+
/** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
|
|
341
|
+
scope;
|
|
342
|
+
ctx;
|
|
343
|
+
/** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
|
|
344
|
+
dispatch;
|
|
345
|
+
/** Whether this loop instance has appended its initial/resume request anchor. */
|
|
346
|
+
requestHeaderLogged = false;
|
|
347
|
+
runtimeContext;
|
|
348
|
+
get status() {
|
|
349
|
+
return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
|
|
350
|
+
}
|
|
351
|
+
/** Commit a phase and publish its externally visible status transition. */
|
|
352
|
+
setPhase(next) {
|
|
353
|
+
const previousStatus = this.status;
|
|
354
|
+
this.phase = next;
|
|
355
|
+
const status = this.status;
|
|
356
|
+
if (status !== previousStatus) {
|
|
357
|
+
this.dispatch.emit("agent/status", { status });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
send(message, target, wakeup) {
|
|
361
|
+
const wakingAfterAbort = wakeup && this.phase.kind !== "idle" && this.phase.abort.signal.aborted;
|
|
362
|
+
const resolvedTarget = wakingAfterAbort ? "next-turn" : target;
|
|
363
|
+
this.inbox.splice(resolvedTarget, Infinity, 0, [message]);
|
|
364
|
+
if (wakeup) this.wakeDriver(wakingAfterAbort);
|
|
365
|
+
}
|
|
366
|
+
followup(input) {
|
|
367
|
+
this.send(input, "next-turn", true);
|
|
368
|
+
}
|
|
369
|
+
steer(input) {
|
|
370
|
+
this.send(input, "next-step", true);
|
|
371
|
+
}
|
|
372
|
+
inject(input) {
|
|
373
|
+
this.send(input, "next-step", false);
|
|
374
|
+
}
|
|
375
|
+
cancel(cause, options = {}) {
|
|
376
|
+
if (!options.keepInbox) {
|
|
377
|
+
this.inbox.clear();
|
|
378
|
+
if (this.phase.kind !== "idle") this.phase.wakeRequested = false;
|
|
379
|
+
}
|
|
380
|
+
if (this.phase.kind !== "idle") this.phase.abort.abort(cause);
|
|
381
|
+
}
|
|
382
|
+
runMaintenance(job) {
|
|
383
|
+
if (this.phase.kind !== "idle") throw new Error(`agent "${this.id}" already has active work`);
|
|
384
|
+
const done = Promise.withResolvers();
|
|
385
|
+
const maintenance = {
|
|
386
|
+
kind: "maintenance",
|
|
387
|
+
abort: new AbortController(),
|
|
388
|
+
lastTurn: this.phase.lastTurn,
|
|
389
|
+
wakeRequested: false
|
|
390
|
+
};
|
|
391
|
+
this.setPhase(maintenance);
|
|
392
|
+
this.activityDone = done.promise;
|
|
393
|
+
return (async () => {
|
|
394
|
+
try {
|
|
395
|
+
return await job(maintenance.abort.signal);
|
|
396
|
+
} finally {
|
|
397
|
+
this.setPhase({ kind: "idle", lastTurn: maintenance.lastTurn });
|
|
398
|
+
if (maintenance.wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
399
|
+
done.resolve();
|
|
400
|
+
}
|
|
401
|
+
})();
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Start one driver, or latch its wake behind maintenance or an aborted
|
|
405
|
+
* activity. A wake sent while idle always opens its turn boundary, even
|
|
406
|
+
* when its message was cleared; only a latched replay is suppressed when
|
|
407
|
+
* the queue no longer holds the wake.
|
|
408
|
+
* @param wakeAfterAbort - the {@link send} classification, captured before
|
|
409
|
+
* the inbox insertion so a reentrant cancel cannot reclassify it.
|
|
410
|
+
*/
|
|
411
|
+
wakeDriver(wakeAfterAbort = false) {
|
|
412
|
+
if (this.phase.kind !== "idle") {
|
|
413
|
+
const reason = this.phase.abort.signal.reason;
|
|
414
|
+
if (reason?.kind !== "disposed" && (this.phase.kind === "maintenance" || wakeAfterAbort)) {
|
|
415
|
+
this.phase.wakeRequested = true;
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const driver = Promise.withResolvers();
|
|
420
|
+
this.activityDone = driver.promise;
|
|
421
|
+
this.setPhase({
|
|
422
|
+
kind: "running",
|
|
423
|
+
abort: new AbortController(),
|
|
424
|
+
turn: this.phase.lastTurn,
|
|
425
|
+
step: 0,
|
|
426
|
+
wakeRequested: false
|
|
427
|
+
});
|
|
428
|
+
this.loopCtx.agents.withInitiator(this, () => this.kick()).then(driver.resolve, driver.reject);
|
|
429
|
+
}
|
|
430
|
+
async whenIdle() {
|
|
431
|
+
let activity;
|
|
432
|
+
do {
|
|
433
|
+
await (activity = this.activityDone);
|
|
434
|
+
} while (activity !== this.activityDone);
|
|
435
|
+
}
|
|
436
|
+
/** Report one failure at its live boundary, then preserve it for driver containment. */
|
|
437
|
+
throwError(error) {
|
|
438
|
+
const turn = this.phase.kind === "running" ? this.phase.turn : this.phase.lastTurn;
|
|
439
|
+
const step = this.phase.kind === "running" ? this.phase.step : 0;
|
|
440
|
+
this.dispatch.emit("agent/error", { turn, step, error });
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
async kick() {
|
|
444
|
+
try {
|
|
445
|
+
while (await this.turn()) {
|
|
446
|
+
}
|
|
447
|
+
} catch (_error) {
|
|
448
|
+
} finally {
|
|
449
|
+
if (this.phase.kind === "running") {
|
|
450
|
+
const { turn, wakeRequested } = this.phase;
|
|
451
|
+
this.setPhase({ kind: "idle", lastTurn: turn });
|
|
452
|
+
if (wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async preStep(target, position) {
|
|
457
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": pre-step outside running phase`);
|
|
458
|
+
const signal = this.phase.abort.signal;
|
|
459
|
+
const claimed = this.inbox.claim(target, position.turn);
|
|
460
|
+
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal));
|
|
461
|
+
signal.throwIfAborted();
|
|
462
|
+
const sections = renderContextSections(assembly);
|
|
463
|
+
const context = this.runtimeContext.project(joinContextSections(sections), sections);
|
|
464
|
+
const decision = await this.dispatch.waterfall(
|
|
465
|
+
"agent/pre-step",
|
|
466
|
+
{ messages: claimed, ...position, signal },
|
|
467
|
+
() => Promise.resolve({
|
|
468
|
+
kind: "enter",
|
|
469
|
+
messages: context === void 0 ? claimed : [...claimed, context]
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
signal.throwIfAborted();
|
|
473
|
+
return decision.kind === "reject" ? decision : { ...decision, assembly };
|
|
474
|
+
}
|
|
475
|
+
/** Open one turn before claiming its first proposed step. */
|
|
476
|
+
async turn() {
|
|
477
|
+
if (this.phase.kind !== "running") {
|
|
478
|
+
this.throwError(new Error(`agent "${this.id}": turn without driver reservation`));
|
|
479
|
+
}
|
|
480
|
+
const phase = this.phase;
|
|
481
|
+
const { signal } = phase.abort;
|
|
482
|
+
signal.throwIfAborted();
|
|
483
|
+
const turn = phase.turn + 1;
|
|
484
|
+
try {
|
|
485
|
+
this.session.append("turn/start", { turn });
|
|
486
|
+
} catch (error) {
|
|
487
|
+
this.throwError(error);
|
|
488
|
+
}
|
|
489
|
+
phase.turn = turn;
|
|
490
|
+
let turnEnds = null;
|
|
491
|
+
let target = "next-turn";
|
|
492
|
+
try {
|
|
493
|
+
while (true) {
|
|
494
|
+
signal.throwIfAborted();
|
|
495
|
+
const step = phase.step + 1;
|
|
496
|
+
const decision = await this.preStep(target, { turn, step });
|
|
497
|
+
if (decision.kind === "reject") {
|
|
498
|
+
turnEnds = { kind: "blocked" };
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
if (turnEnds && decision.messages.length === 0) break;
|
|
502
|
+
if (phase.step === 0 && decision.messages.length === 0) {
|
|
503
|
+
turnEnds = { kind: "completed" };
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
signal.throwIfAborted();
|
|
507
|
+
this.session.append("step/start", { turn, step });
|
|
508
|
+
phase.step = step;
|
|
509
|
+
try {
|
|
510
|
+
for (const message of decision.messages) {
|
|
511
|
+
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
512
|
+
}
|
|
513
|
+
const stepEnd = await this.step(decision.assembly);
|
|
514
|
+
if (turnEnds === null || turnEnds.kind !== "max-tokens") turnEnds = stepEnd;
|
|
515
|
+
} finally {
|
|
516
|
+
this.session.append("step/end", { turn, step });
|
|
517
|
+
}
|
|
518
|
+
signal.throwIfAborted();
|
|
519
|
+
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
520
|
+
await this.dispatch.serial("agent/turn-stopping", { turn, signal });
|
|
521
|
+
signal.throwIfAborted();
|
|
522
|
+
}
|
|
523
|
+
if (turnEnds && this.inbox.nextStep.length === 0) break;
|
|
524
|
+
target = "next-step";
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (signal.aborted) {
|
|
528
|
+
turnEnds = { kind: "aborted", reason: signal.reason };
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
turnEnds = {
|
|
532
|
+
kind: "error",
|
|
533
|
+
error: error instanceof LlmError ? error.failure : { message: errorChain(error), code: "UNKNOWN" }
|
|
534
|
+
};
|
|
535
|
+
this.throwError(error);
|
|
536
|
+
} finally {
|
|
537
|
+
try {
|
|
538
|
+
this.session.append("turn/end", { turn, reason: turnEnds });
|
|
539
|
+
} catch (error) {
|
|
540
|
+
this.throwError(error);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (!this.inbox.hasPending) return false;
|
|
544
|
+
phase.abort = new AbortController();
|
|
545
|
+
phase.wakeRequested = false;
|
|
546
|
+
phase.step = 0;
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
async step(assembly) {
|
|
550
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
551
|
+
const { turn, step, abort: { signal } } = this.phase;
|
|
552
|
+
signal.throwIfAborted();
|
|
553
|
+
const system = renderPrompt(assembly);
|
|
554
|
+
while (true) {
|
|
555
|
+
const { request, preparedCall } = await this.buildRequest(
|
|
556
|
+
turn,
|
|
557
|
+
step,
|
|
558
|
+
assembly.tools,
|
|
559
|
+
system,
|
|
560
|
+
this.session.deriveMessages(),
|
|
561
|
+
signal
|
|
562
|
+
);
|
|
563
|
+
const assembler = new BlockAssembler();
|
|
564
|
+
const chunkSeqs = [];
|
|
565
|
+
const stream = preparedCall?.stream(request) ?? this.loopCtx.llm.stream(request);
|
|
566
|
+
signal.throwIfAborted();
|
|
567
|
+
for await (const chunk of stream) {
|
|
568
|
+
signal.throwIfAborted();
|
|
569
|
+
chunkSeqs.push(this.session.append("assistant/chunk", { turn, step, chunk }).seq);
|
|
570
|
+
assembler.push(chunk);
|
|
571
|
+
}
|
|
572
|
+
signal.throwIfAborted();
|
|
573
|
+
const finish = assembler.finish;
|
|
574
|
+
if (finish.kind === "error" || finish.kind === "aborted") {
|
|
575
|
+
const action = await this.dispatch.waterfall(
|
|
576
|
+
"agent/request-error",
|
|
577
|
+
{
|
|
578
|
+
turn,
|
|
579
|
+
step,
|
|
580
|
+
provider: request.provider,
|
|
581
|
+
failure: finish.failure,
|
|
582
|
+
retryPolicy: preparedCall?.retryPolicy,
|
|
583
|
+
signal
|
|
584
|
+
},
|
|
585
|
+
() => Promise.resolve(void 0)
|
|
586
|
+
);
|
|
587
|
+
signal.throwIfAborted();
|
|
588
|
+
if (action?.kind !== "retry") {
|
|
589
|
+
throw new LlmError(finish.failure.message, finish.failure.code, finish.failure);
|
|
590
|
+
}
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const message = createAssistantMessage({
|
|
594
|
+
content: assembler.blocks(),
|
|
595
|
+
source: {
|
|
596
|
+
provider: request.provider,
|
|
597
|
+
model: request.model,
|
|
598
|
+
...assembler.replayState !== void 0 ? { replayState: assembler.replayState } : {}
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
this.session.append(
|
|
602
|
+
"assistant/message",
|
|
603
|
+
{
|
|
604
|
+
turn,
|
|
605
|
+
step,
|
|
606
|
+
message,
|
|
607
|
+
...assembler.usage === void 0 ? {} : { usage: assembler.usage }
|
|
608
|
+
},
|
|
609
|
+
{ surfaceOp: "append", sourceEventSeqs: chunkSeqs }
|
|
610
|
+
);
|
|
611
|
+
if (finish.kind === "max-tokens") return { kind: "max-tokens" };
|
|
612
|
+
const toolCalls = message.content.filter((block) => block.type === "tool-call");
|
|
613
|
+
if (toolCalls.length === 0) return { kind: "completed" };
|
|
614
|
+
const { concluded } = await executeToolCalls(
|
|
615
|
+
this.loopCtx,
|
|
616
|
+
turn,
|
|
617
|
+
step,
|
|
618
|
+
toolCalls,
|
|
619
|
+
signal,
|
|
620
|
+
(context) => this.inbox.splice("next-step", this.inbox.nextStep.length, 0, [context])
|
|
621
|
+
);
|
|
622
|
+
return concluded ? { kind: "completed" } : null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Compose one frozen request and bind it to the adapter registration that
|
|
627
|
+
* resolved its exact-model defaults.
|
|
628
|
+
*/
|
|
629
|
+
async buildRequest(turn, step, tools, system, boundaryMessages, signal) {
|
|
630
|
+
const { session } = this;
|
|
631
|
+
const persistedHeader = session.requestHeader();
|
|
632
|
+
const persistedConfig = persistedHeader?.config;
|
|
633
|
+
const route = { provider: this.options.provider ?? "", model: this.options.model ?? "" };
|
|
634
|
+
const reasoningEffort = persistedConfig?.provider === route.provider && persistedConfig.model === route.model && persistedHeader?.adapterDefaults?.reasoningEffort !== true ? persistedConfig.reasoningEffort : void 0;
|
|
635
|
+
const maxTokens = this.options.maxTokens;
|
|
636
|
+
const seedConfig = deepFreeze(structuredClone(
|
|
637
|
+
this.requestHeaderLogged ? requestProposal(persistedHeader) : {
|
|
638
|
+
...route,
|
|
639
|
+
...reasoningEffort === void 0 ? {} : { reasoningEffort },
|
|
640
|
+
...maxTokens === void 0 ? {} : { maxTokens }
|
|
641
|
+
}
|
|
642
|
+
));
|
|
643
|
+
const proposedConfig = await this.dispatch.waterfall(
|
|
644
|
+
"agent/request",
|
|
645
|
+
{ turn, step, signal },
|
|
646
|
+
() => Promise.resolve(seedConfig)
|
|
647
|
+
);
|
|
648
|
+
signal.throwIfAborted();
|
|
649
|
+
if (!proposedConfig.provider || !proposedConfig.model) {
|
|
650
|
+
throw new Error(`agent "${this.id}" has no provider/model: set AgentOptions.provider and AgentOptions.model or supply both via the agent/request waterfall`);
|
|
651
|
+
}
|
|
652
|
+
let config;
|
|
653
|
+
let preparedCall;
|
|
654
|
+
try {
|
|
655
|
+
preparedCall = await this.loopCtx.llm.prepareCall(proposedConfig, signal);
|
|
656
|
+
config = preparedCall.config;
|
|
657
|
+
} catch (error) {
|
|
658
|
+
if (!(error instanceof LlmError) || error.code !== "NO_ADAPTER") throw error;
|
|
659
|
+
config = proposedConfig;
|
|
660
|
+
}
|
|
661
|
+
signal.throwIfAborted();
|
|
662
|
+
const header = canonicalHeader({
|
|
663
|
+
config,
|
|
664
|
+
...preparedCall === void 0 ? {} : { adapterDefaults: preparedCall.adapterDefaults },
|
|
665
|
+
...system ? { system } : {},
|
|
666
|
+
...tools.length > 0 ? { tools } : {}
|
|
667
|
+
});
|
|
668
|
+
const baseline = this.session.requestHeader();
|
|
669
|
+
if (!this.requestHeaderLogged) {
|
|
670
|
+
this.session.append("request/header", { header, reason: baseline === void 0 ? "initial" : "resume" });
|
|
671
|
+
this.requestHeaderLogged = true;
|
|
672
|
+
} else if (baseline === void 0 || !headerEquals(baseline, header)) {
|
|
673
|
+
this.session.append("request/header", { header, reason: "change" });
|
|
674
|
+
}
|
|
675
|
+
const contextWindow = preparedCall?.context?.contextWindow;
|
|
676
|
+
const requestContext = {
|
|
677
|
+
provider: config.provider,
|
|
678
|
+
model: config.model,
|
|
679
|
+
...contextWindow === void 0 ? {} : { contextWindow }
|
|
680
|
+
};
|
|
681
|
+
const previousContext = session.requestContext();
|
|
682
|
+
if (previousContext?.provider !== requestContext.provider || previousContext.model !== requestContext.model || previousContext.contextWindow !== requestContext.contextWindow) {
|
|
683
|
+
session.append("request/context", requestContext);
|
|
684
|
+
}
|
|
685
|
+
signal.throwIfAborted();
|
|
686
|
+
const request = markAgentLoopRequest(deepFreeze({
|
|
687
|
+
...header.config,
|
|
688
|
+
messages: boundaryMessages,
|
|
689
|
+
...header.system !== void 0 ? { system: header.system } : {},
|
|
690
|
+
...header.tools !== void 0 ? { tools: header.tools } : {},
|
|
691
|
+
sessionId: this.session.id,
|
|
692
|
+
signal
|
|
693
|
+
}));
|
|
694
|
+
return { request, ...preparedCall === void 0 ? {} : { preparedCall } };
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// packages/core/agent-loop/src/constants.ts
|
|
699
|
+
var DEFAULT_MAX_PARALLEL_TOOL_CALLS = 10;
|
|
700
|
+
|
|
701
|
+
// packages/core/agent-loop/src/index.ts
|
|
702
|
+
var INACTIVE_STATES = /* @__PURE__ */ new Set([
|
|
703
|
+
FiberState.UNLOADING,
|
|
704
|
+
FiberState.DISPOSED,
|
|
705
|
+
FiberState.FAILED
|
|
706
|
+
]);
|
|
707
|
+
var FactoryOwnership = class {
|
|
708
|
+
constructor(fiber) {
|
|
709
|
+
this.fiber = fiber;
|
|
710
|
+
}
|
|
711
|
+
fiber;
|
|
712
|
+
accepting = true;
|
|
713
|
+
teardown = new AbortController();
|
|
714
|
+
inactive = Promise.withResolvers();
|
|
715
|
+
liveAgents = /* @__PURE__ */ new Set();
|
|
716
|
+
startupTasks = /* @__PURE__ */ new Set();
|
|
717
|
+
/** Aborts (reason: `agent loop is not active` error) when factory teardown begins. */
|
|
718
|
+
get signal() {
|
|
719
|
+
return this.teardown.signal;
|
|
720
|
+
}
|
|
721
|
+
isActive() {
|
|
722
|
+
return this.accepting && !INACTIVE_STATES.has(this.fiber.state);
|
|
723
|
+
}
|
|
724
|
+
/** Track one live agent's shared teardown until it has run. */
|
|
725
|
+
track(dispose) {
|
|
726
|
+
this.liveAgents.add(dispose);
|
|
727
|
+
return () => {
|
|
728
|
+
this.liveAgents.delete(dispose);
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/** Join config startup work that begins before an agent exists. */
|
|
732
|
+
trackStartup(job) {
|
|
733
|
+
this.startupTasks.add(job);
|
|
734
|
+
const forget = () => {
|
|
735
|
+
this.startupTasks.delete(job);
|
|
736
|
+
};
|
|
737
|
+
void job.then(forget, forget);
|
|
738
|
+
}
|
|
739
|
+
/** Join one public create/resume continuation; factory dispose awaits its settlement. */
|
|
740
|
+
trackWrapper(job) {
|
|
741
|
+
this.trackStartup(job.then(() => void 0, () => void 0));
|
|
742
|
+
}
|
|
743
|
+
/** Resolve `task`, or stop waiting when factory teardown begins. */
|
|
744
|
+
async waitWhileActive(job) {
|
|
745
|
+
await Promise.race([job, this.inactive.promise]);
|
|
746
|
+
}
|
|
747
|
+
async dispose() {
|
|
748
|
+
this.accepting = false;
|
|
749
|
+
this.teardown.abort(new Error("agent loop is not active"));
|
|
750
|
+
this.inactive.resolve();
|
|
751
|
+
await Promise.all([
|
|
752
|
+
...[...this.liveAgents].map((dispose) => dispose()),
|
|
753
|
+
...this.startupTasks
|
|
754
|
+
]);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
async function raceAbort(operation, signal, id) {
|
|
758
|
+
const toAbortError = () => signal.reason instanceof Error ? signal.reason : new Error(`agent "${id}" creation aborted`, { cause: signal.reason });
|
|
759
|
+
if (signal.aborted) throw toAbortError();
|
|
760
|
+
const aborted = Promise.withResolvers();
|
|
761
|
+
const listener = () => {
|
|
762
|
+
aborted.reject(toAbortError());
|
|
763
|
+
};
|
|
764
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
765
|
+
try {
|
|
766
|
+
return await Promise.race([Promise.resolve(operation), aborted.promise]);
|
|
767
|
+
} finally {
|
|
768
|
+
signal.removeEventListener("abort", listener);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async function raceAbortCall(operation, signal, id, releaseAbandoned) {
|
|
772
|
+
if (signal.aborted) {
|
|
773
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(`agent "${id}" creation aborted`, { cause: signal.reason });
|
|
774
|
+
}
|
|
775
|
+
const pending = Promise.resolve().then(operation);
|
|
776
|
+
try {
|
|
777
|
+
return await raceAbort(pending, signal, id);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
if (signal.aborted && releaseAbandoned !== void 0) {
|
|
780
|
+
void pending.then(releaseAbandoned, () => void 0);
|
|
781
|
+
}
|
|
782
|
+
throw error;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function resolveMaxParallelToolCalls(value) {
|
|
786
|
+
const maxParallelToolCalls = value ?? DEFAULT_MAX_PARALLEL_TOOL_CALLS;
|
|
787
|
+
if (!Number.isInteger(maxParallelToolCalls) || maxParallelToolCalls < 1) {
|
|
788
|
+
throw new Error("maxParallelToolCalls must be a positive integer");
|
|
789
|
+
}
|
|
790
|
+
return maxParallelToolCalls;
|
|
791
|
+
}
|
|
792
|
+
function assertAgentOptions(options) {
|
|
793
|
+
if (options.maxTokens !== void 0 && (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {
|
|
794
|
+
throw new TypeError("agent maxTokens must be a positive safe integer");
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
var CONFIGURED_AGENT_IDENTITIES_KEY = "configuredAgentIdentities";
|
|
798
|
+
function applyLauncherIdentities(agents, identities) {
|
|
799
|
+
if (identities === void 0) return agents;
|
|
800
|
+
return agents.map((agent) => {
|
|
801
|
+
const identity = identities[agent.id];
|
|
802
|
+
if (identity === void 0) return agent;
|
|
803
|
+
const { sessionId: _sessionId, resumeSessionId: _resumeSessionId, ...rest } = agent;
|
|
804
|
+
return identity.resume ? { ...rest, resumeSessionId: identity.id } : { ...rest, sessionId: identity.id };
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
var AGENT_LOOP_SETTINGS_NAMESPACE = settingsNamespace("agent-loop");
|
|
808
|
+
var AGENT_LOOP_SETTINGS_SCHEMA = z.object({
|
|
809
|
+
maxParallelToolCalls: z.number().step(1).min(1).default(DEFAULT_MAX_PARALLEL_TOOL_CALLS)
|
|
810
|
+
});
|
|
811
|
+
function validateConfiguredAgents(agents) {
|
|
812
|
+
const exactIdentities = /* @__PURE__ */ new Map();
|
|
813
|
+
for (const { id, sessionId, resumeSessionId } of agents) {
|
|
814
|
+
const hasResumeId = resumeSessionId !== void 0 && resumeSessionId !== "";
|
|
815
|
+
if (sessionId !== void 0 && hasResumeId) {
|
|
816
|
+
throw new Error(`agent "${id}": sessionId and resumeSessionId are mutually exclusive`);
|
|
817
|
+
}
|
|
818
|
+
const exactIdentity = hasResumeId ? resumeSessionId : sessionId;
|
|
819
|
+
if (exactIdentity === void 0) continue;
|
|
820
|
+
const firstId = exactIdentities.get(exactIdentity);
|
|
821
|
+
if (firstId !== void 0) {
|
|
822
|
+
throw new Error(`agents "${firstId}" and "${id}" use duplicate exact session identity "${exactIdentity}"`);
|
|
823
|
+
}
|
|
824
|
+
exactIdentities.set(exactIdentity, id);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
var AgentLoop = class extends Service {
|
|
828
|
+
static inject = ["agents", "sessions", "llm", "tools", "systemPrompt"];
|
|
829
|
+
/** Runtime schema for declarative agents. */
|
|
830
|
+
static Config = z.object({
|
|
831
|
+
maxParallelToolCalls: z.number().step(1).min(1).default(DEFAULT_MAX_PARALLEL_TOOL_CALLS),
|
|
832
|
+
agents: z.array(z.object({
|
|
833
|
+
id: z.string().required(),
|
|
834
|
+
sessionId: z.string().min(1),
|
|
835
|
+
provider: z.string(),
|
|
836
|
+
model: z.string(),
|
|
837
|
+
maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
|
|
838
|
+
cwd: z.string(),
|
|
839
|
+
resumeSessionId: z.string()
|
|
840
|
+
})).default([])
|
|
841
|
+
});
|
|
842
|
+
/** Validated configuration owned by the agent-loop service. */
|
|
843
|
+
config;
|
|
844
|
+
ownership;
|
|
845
|
+
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
846
|
+
runtime;
|
|
847
|
+
constructor(ctx, config) {
|
|
848
|
+
super(ctx, "agentLoop");
|
|
849
|
+
const entry = {
|
|
850
|
+
maxParallelToolCalls: resolveMaxParallelToolCalls(config.maxParallelToolCalls)
|
|
851
|
+
};
|
|
852
|
+
let source = () => entry;
|
|
853
|
+
this.config = {
|
|
854
|
+
...config,
|
|
855
|
+
agents: applyLauncherIdentities(config.agents, ctx.get(CONFIGURED_AGENT_IDENTITIES_KEY)),
|
|
856
|
+
// Read through on every scheduler decision: `tool-calls.ts` destructures
|
|
857
|
+
// this at the start of each group, so a committed change caps the next
|
|
858
|
+
// group without disturbing the one in flight.
|
|
859
|
+
get maxParallelToolCalls() {
|
|
860
|
+
return source().maxParallelToolCalls;
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
installSettingsSection(ctx, AGENT_LOOP_SETTINGS_NAMESPACE, AGENT_LOOP_SETTINGS_SCHEMA, entry, {
|
|
864
|
+
// The schema admits any integer above zero; `resolveMaxParallelToolCalls`
|
|
865
|
+
// owns the whole rule, so refusing here keeps the running scheduler on
|
|
866
|
+
// its last good cap instead of failing at the next tool group.
|
|
867
|
+
validate: (value) => void resolveMaxParallelToolCalls(value.maxParallelToolCalls),
|
|
868
|
+
setSource: (current) => {
|
|
869
|
+
source = current;
|
|
870
|
+
},
|
|
871
|
+
// Nothing is derived from the cap: the getter above is the only reader.
|
|
872
|
+
onChange: () => {
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
validateConfiguredAgents(this.config.agents);
|
|
876
|
+
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
877
|
+
this.runtime = { ctx };
|
|
878
|
+
ctx.effect(() => () => this.ownership.dispose(), "agentLoop.transactions()");
|
|
879
|
+
ctx.effect(() => ctx.agents.setFactory(this), "agentLoop.setFactory()");
|
|
880
|
+
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
881
|
+
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
882
|
+
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
883
|
+
for (const { id, sessionId, cwd, resumeSessionId, ...options } of this.config.agents) {
|
|
884
|
+
const meta = cwd === void 0 ? {} : { cwd };
|
|
885
|
+
if (resumeSessionId === void 0 || resumeSessionId === "") {
|
|
886
|
+
const configuredId = sessionId ?? SessionId(`${id}-session-${randomUUID()}`);
|
|
887
|
+
const persistence = sessionId === void 0 ? void 0 : ctx.get("sessionPersistence");
|
|
888
|
+
if (persistence === void 0) {
|
|
889
|
+
this.create(configuredId, options, meta);
|
|
890
|
+
} else {
|
|
891
|
+
const startup = this.restoreOrCreateConfigured(ctx, persistence, configuredId, options, meta).catch((error) => {
|
|
892
|
+
this.reportConfiguredStartupFailure(id, "restore", configuredId, error);
|
|
893
|
+
});
|
|
894
|
+
this.ownership.trackStartup(startup);
|
|
895
|
+
}
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
ctx.effect(() => {
|
|
899
|
+
const fiber = ctx.inject(["sessionPersistence"], (childCtx) => {
|
|
900
|
+
void this.resumeWith(ctx, childCtx.sessionPersistence, {
|
|
901
|
+
resumeSessionId,
|
|
902
|
+
agentOptions: options
|
|
903
|
+
}).catch((error) => {
|
|
904
|
+
this.reportConfiguredStartupFailure(id, "resume", resumeSessionId, error);
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
return fiber.dispose;
|
|
908
|
+
}, `agentLoop.resume(${id})`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/** Report a contained declarative-start failure to identity-bound consumers. */
|
|
912
|
+
reportConfiguredStartupFailure(configId, action, sessionId, error) {
|
|
913
|
+
if (!this.ownership.isActive()) return;
|
|
914
|
+
this.ctx.logger.warn(`agent "${configId}": config-driven ${action} of "${sessionId}" failed: ${errorChain2(error)}`);
|
|
915
|
+
const args = ["agent-loop/config-start-failed", { sessionId, error }];
|
|
916
|
+
for (const callback of this.ctx.events.dispatch("emit", args)) {
|
|
917
|
+
try {
|
|
918
|
+
const returned = callback(...args);
|
|
919
|
+
void Promise.resolve(returned).catch((listenerError) => {
|
|
920
|
+
this.ctx.logger.warn(`agent "${configId}": config-start-failed listener rejected: ${errorChain2(listenerError)}`);
|
|
921
|
+
});
|
|
922
|
+
} catch (listenerError) {
|
|
923
|
+
this.ctx.logger.warn(`agent "${configId}": config-start-failed listener threw: ${errorChain2(listenerError)}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/** Restore a materialized exact config identity on remount, or create it on first use. */
|
|
928
|
+
async restoreOrCreateConfigured(ownerCtx, persistence, sessionId, agentOptions, meta) {
|
|
929
|
+
await this.waitForDrainingConfiguredIdentity(ownerCtx, sessionId);
|
|
930
|
+
if (!this.ownership.isActive()) return;
|
|
931
|
+
try {
|
|
932
|
+
await this.resumeWith(ownerCtx, persistence, { resumeSessionId: sessionId, agentOptions });
|
|
933
|
+
return;
|
|
934
|
+
} catch (error) {
|
|
935
|
+
if (!this.ownership.isActive()) return;
|
|
936
|
+
const exists = (await persistence.list()).some((header) => header.id === sessionId);
|
|
937
|
+
if (exists) throw error;
|
|
938
|
+
}
|
|
939
|
+
this.create(sessionId, agentOptions, meta);
|
|
940
|
+
}
|
|
941
|
+
/** Wait for a draining same-id lifecycle to finish registry teardown. */
|
|
942
|
+
async waitForDrainingConfiguredIdentity(ownerCtx, sessionId) {
|
|
943
|
+
if (ownerCtx.agents.get(sessionId) === void 0 && ownerCtx.sessions.get(sessionId) === void 0) return;
|
|
944
|
+
const released = Promise.withResolvers();
|
|
945
|
+
const checkReleased = () => {
|
|
946
|
+
if (ownerCtx.agents.get(sessionId) === void 0 && ownerCtx.sessions.get(sessionId) === void 0) {
|
|
947
|
+
released.resolve();
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
const disposeAgentListener = ownerCtx.on("agent/disposed", () => {
|
|
951
|
+
checkReleased();
|
|
952
|
+
});
|
|
953
|
+
const disposeSessionListener = ownerCtx.on("session/disposed", checkReleased);
|
|
954
|
+
try {
|
|
955
|
+
checkReleased();
|
|
956
|
+
await this.ownership.waitWhileActive(released.promise);
|
|
957
|
+
} finally {
|
|
958
|
+
disposeAgentListener();
|
|
959
|
+
disposeSessionListener();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
964
|
+
* agent. The teardown is registered with the factory and the owner fiber
|
|
965
|
+
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
966
|
+
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
967
|
+
*/
|
|
968
|
+
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
969
|
+
assertAgentOptions(options);
|
|
970
|
+
ownerCtx.fiber.assertActive();
|
|
971
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
972
|
+
if (callerSignal?.aborted) {
|
|
973
|
+
throw callerSignal.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal.reason });
|
|
974
|
+
}
|
|
975
|
+
const loopCtx = this.runtime.ctx;
|
|
976
|
+
const abort = new AbortController();
|
|
977
|
+
const onCallerAbort = () => {
|
|
978
|
+
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
979
|
+
};
|
|
980
|
+
const onFactoryTeardown = () => {
|
|
981
|
+
abort.abort(this.ownership.signal.reason);
|
|
982
|
+
};
|
|
983
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
984
|
+
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
985
|
+
let machine;
|
|
986
|
+
let detachSession;
|
|
987
|
+
let detachAgent;
|
|
988
|
+
let disposing;
|
|
989
|
+
const machineReady = Promise.withResolvers();
|
|
990
|
+
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
991
|
+
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
992
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
993
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
994
|
+
try {
|
|
995
|
+
if (machine === void 0) await machineReady.promise;
|
|
996
|
+
if (machine !== void 0) {
|
|
997
|
+
machine.cancel({ kind: "disposed" });
|
|
998
|
+
await machine.whenIdle();
|
|
999
|
+
await machine.scope.dispose();
|
|
1000
|
+
}
|
|
1001
|
+
} finally {
|
|
1002
|
+
try {
|
|
1003
|
+
detachAgent?.();
|
|
1004
|
+
detachSession?.();
|
|
1005
|
+
} finally {
|
|
1006
|
+
untrack();
|
|
1007
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
})();
|
|
1011
|
+
const untrack = this.ownership.track(dispose);
|
|
1012
|
+
let unfollowOwner;
|
|
1013
|
+
try {
|
|
1014
|
+
unfollowOwner = ownerCtx.effect(() => () => {
|
|
1015
|
+
if (disposing !== void 0) return;
|
|
1016
|
+
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
1017
|
+
return dispose(true);
|
|
1018
|
+
}, `agentLoop.lifecycle(${id})`);
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
untrack();
|
|
1021
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
1022
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
1023
|
+
throw error;
|
|
1024
|
+
}
|
|
1025
|
+
const assertLive = () => {
|
|
1026
|
+
if (!abort.signal.aborted) return;
|
|
1027
|
+
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
1028
|
+
};
|
|
1029
|
+
try {
|
|
1030
|
+
const agent = machine = new ReactLoopAgent(loopCtx, id, options, session);
|
|
1031
|
+
machineReady.resolve();
|
|
1032
|
+
assertLive();
|
|
1033
|
+
return {
|
|
1034
|
+
agent,
|
|
1035
|
+
signal: abort.signal,
|
|
1036
|
+
publish: (source) => {
|
|
1037
|
+
assertLive();
|
|
1038
|
+
detachSession = agent.ctx.sessions.enter(session);
|
|
1039
|
+
detachAgent = loopCtx.agents.enter(agent, ownerCtx.agent);
|
|
1040
|
+
agent.ctx.sessions.announce(session);
|
|
1041
|
+
assertLive();
|
|
1042
|
+
loopCtx.agents.announce(agent);
|
|
1043
|
+
assertLive();
|
|
1044
|
+
emitAgentEvent(loopCtx, agent, "agent/session-start", { source });
|
|
1045
|
+
assertLive();
|
|
1046
|
+
return { agent, dispose };
|
|
1047
|
+
},
|
|
1048
|
+
dispose
|
|
1049
|
+
};
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
machineReady.resolve();
|
|
1052
|
+
void dispose();
|
|
1053
|
+
throw error;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Create an agent and session under one caller-supplied identity, owned by
|
|
1058
|
+
* the accessing fiber. Constructor-driven config calls mint a fresh combined
|
|
1059
|
+
* id before entering this boundary.
|
|
1060
|
+
* @param id - shared agent/session identity.
|
|
1061
|
+
* @param options - concrete loop options.
|
|
1062
|
+
* @param meta - optional fresh-session workspace metadata.
|
|
1063
|
+
* @returns the published running agent.
|
|
1064
|
+
*/
|
|
1065
|
+
create(id, options = {}, meta = {}) {
|
|
1066
|
+
var _stack = [];
|
|
1067
|
+
try {
|
|
1068
|
+
const preparation = __using(_stack, SessionPreparation.create(this.runtime.ctx.sessions.prepare(id, { meta })));
|
|
1069
|
+
const prepared = this.prepare(this.ctx, id, options, preparation.session);
|
|
1070
|
+
try {
|
|
1071
|
+
return prepared.publish("startup").agent;
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
void prepared.dispose();
|
|
1074
|
+
throw error;
|
|
1075
|
+
}
|
|
1076
|
+
} catch (_) {
|
|
1077
|
+
var _error = _, _hasError = true;
|
|
1078
|
+
} finally {
|
|
1079
|
+
__callDispose(_stack, _error, _hasError);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Create an owned agent on a caller-supplied session id.
|
|
1084
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
1085
|
+
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
1086
|
+
* @returns the published handle.
|
|
1087
|
+
*/
|
|
1088
|
+
async createAgent(ownerCtx, options) {
|
|
1089
|
+
const preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
1090
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
1091
|
+
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
1092
|
+
}));
|
|
1093
|
+
const published = this.setupAndPublish(
|
|
1094
|
+
ownerCtx,
|
|
1095
|
+
options.sessionId,
|
|
1096
|
+
preparation,
|
|
1097
|
+
options.agentOptions ?? {},
|
|
1098
|
+
options.setup,
|
|
1099
|
+
options.signal,
|
|
1100
|
+
"startup"
|
|
1101
|
+
);
|
|
1102
|
+
this.ownership.trackWrapper(published);
|
|
1103
|
+
return published;
|
|
1104
|
+
}
|
|
1105
|
+
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
1106
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
1107
|
+
var _stack = [];
|
|
1108
|
+
try {
|
|
1109
|
+
const ownedPreparation = __using(_stack, preparation);
|
|
1110
|
+
const session = ownedPreparation.session;
|
|
1111
|
+
const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
|
|
1112
|
+
try {
|
|
1113
|
+
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
1114
|
+
setupCommit?.commit();
|
|
1115
|
+
return prepared.publish(source);
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
await prepared.dispose();
|
|
1118
|
+
throw error;
|
|
1119
|
+
}
|
|
1120
|
+
} catch (_) {
|
|
1121
|
+
var _error = _, _hasError = true;
|
|
1122
|
+
} finally {
|
|
1123
|
+
__callDispose(_stack, _error, _hasError);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Resume an owned agent from the configured persistence service.
|
|
1128
|
+
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
1129
|
+
* @param options - persisted identity, loop options, setup, and cancellation.
|
|
1130
|
+
* @returns the published handle.
|
|
1131
|
+
*/
|
|
1132
|
+
async resume(ownerCtx, options) {
|
|
1133
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
1134
|
+
if (persistence === void 0) {
|
|
1135
|
+
throw new Error("cannot resume: session persistence is not configured (load a dsh-session-persistence backend)");
|
|
1136
|
+
}
|
|
1137
|
+
return this.resumeWith(ownerCtx, persistence, options);
|
|
1138
|
+
}
|
|
1139
|
+
/** Resume through an explicit persistence handle used by the deferred config path. */
|
|
1140
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
1141
|
+
const id = options.resumeSessionId;
|
|
1142
|
+
const published = (async () => {
|
|
1143
|
+
const ownerAbort = new AbortController();
|
|
1144
|
+
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
1145
|
+
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
1146
|
+
}, `agentLoop.resume-load(${id})`);
|
|
1147
|
+
const fused = AbortSignal.any([
|
|
1148
|
+
...options.signal === void 0 ? [] : [options.signal],
|
|
1149
|
+
ownerAbort.signal,
|
|
1150
|
+
this.ownership.signal
|
|
1151
|
+
]);
|
|
1152
|
+
let preparation;
|
|
1153
|
+
try {
|
|
1154
|
+
try {
|
|
1155
|
+
preparation = await raceAbortCall(
|
|
1156
|
+
() => persistence.prepare(id, fused),
|
|
1157
|
+
fused,
|
|
1158
|
+
id,
|
|
1159
|
+
(abandoned) => {
|
|
1160
|
+
abandoned[Symbol.dispose]();
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
} finally {
|
|
1164
|
+
await unfollowOwner();
|
|
1165
|
+
}
|
|
1166
|
+
ownerCtx.fiber.assertActive();
|
|
1167
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
1168
|
+
return await this.setupAndPublish(
|
|
1169
|
+
ownerCtx,
|
|
1170
|
+
id,
|
|
1171
|
+
preparation,
|
|
1172
|
+
options.agentOptions ?? {},
|
|
1173
|
+
options.setup,
|
|
1174
|
+
options.signal,
|
|
1175
|
+
"resume"
|
|
1176
|
+
);
|
|
1177
|
+
} finally {
|
|
1178
|
+
preparation?.[Symbol.dispose]();
|
|
1179
|
+
}
|
|
1180
|
+
})();
|
|
1181
|
+
this.ownership.trackWrapper(published);
|
|
1182
|
+
return published;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
var index_default = AgentLoop;
|
|
1186
|
+
export {
|
|
1187
|
+
AGENT_LOOP_SETTINGS_NAMESPACE,
|
|
1188
|
+
AGENT_LOOP_SETTINGS_SCHEMA,
|
|
1189
|
+
AgentLoop,
|
|
1190
|
+
CONFIGURED_AGENT_IDENTITIES_KEY,
|
|
1191
|
+
DEFAULT_MAX_PARALLEL_TOOL_CALLS,
|
|
1192
|
+
index_default as default
|
|
1193
|
+
};
|