@schlenkr/ragents 0.1.6 → 0.1.7
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/apps/server/src/chat-handler.ts +2 -0
- package/apps/server/src/config.ts +6 -0
- package/apps/server/src/host-api.json +1 -0
- package/apps/server/src/layout.ts +0 -15
- package/apps/server/src/plugin-support/agent-tool.ts +2 -3
- package/apps/server/src/provider.ts +34 -11
- package/apps/server/src/ragents/actor-chat-history.ts +1 -0
- package/apps/server/src/ragents/engine.ts +5 -13
- package/apps/server/src/ragents/journal-chat-events.ts +2 -0
- package/apps/server/src/ragents/run-owner.ts +3 -2
- package/apps/server/src/ragents/session.ts +3 -3
- package/apps/server/src/run-preparation.ts +14 -18
- package/apps/server/src/run-transfer.ts +65 -4
- package/apps/server/src/settings.ts +2 -4
- package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-B9qYBARS.js → highlighted-body-KPVGNVTW-CFhTJEmH.js} +1 -1
- package/apps/web/dist/assets/{main-BCjvD2Hh.js → main-DlzW4t4c.js} +1 -1
- package/apps/web/dist/assets/{mermaid-HWGCJPDP-CvYYFK_n.js → mermaid-HWGCJPDP-kxkB991i.js} +69 -69
- package/apps/web/dist/assets/{runPanel-CG7kRMJh.js → runPanel-BJEFQ__w.js} +1 -1
- package/apps/web/dist/help/guide-clients.html +13 -4
- package/apps/web/dist/help/guide-clients.md +14 -4
- package/apps/web/dist/help/guide-distributed.html +8 -3
- package/apps/web/dist/help/guide-distributed.md +8 -3
- package/apps/web/dist/help/guide-runtime.html +18 -12
- package/apps/web/dist/help/guide-runtime.md +19 -12
- package/apps/web/dist/help/mini-app.css +23 -3
- package/apps/web/dist/host-web.json +3 -3
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/run-panel.html +2 -2
- package/apps/web/src/SessionList.tsx +16 -8
- package/apps/web/src/api.ts +2 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/RunLine.tsx +4 -2
- package/apps/web/src/panel/contract.ts +2 -0
- package/bundles/ragents.activity/ragents-bundle.json +1 -1
- package/bundles/ragents.actor-programs/ragents-bundle.json +1 -1
- package/bundles/ragents.ask/ragents-bundle.json +1 -1
- package/bundles/ragents.browser/ragents-bundle.json +1 -1
- package/bundles/ragents.documents/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
- package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
- package/bundles/ragents.orchestration/ragents-bundle.json +1 -1
- package/bundles/ragents.overseer/ragents-bundle.json +1 -1
- package/bundles/ragents.processes/ragents-bundle.json +1 -1
- package/bundles/ragents.product/ragents-bundle.json +1 -1
- package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
- package/bundles/ragents.reference/ragents-bundle.json +2 -2
- package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
- package/bundles/ragents.todo/ragents-bundle.json +1 -1
- package/bundles/ragents.transcript/ragents-bundle.json +1 -1
- package/bundles/ragents.watch/ragents-bundle.json +1 -1
- package/bundles/ragents.workspace/ragents-bundle.json +1 -1
- package/package.json +2 -2
- package/packages/agent/dist/core/agent-settings.d.ts +2 -2
- package/packages/agent/dist/core/compaction/compaction.d.ts +16 -50
- package/packages/agent/dist/core/context-log.d.ts +25 -0
- package/packages/agent/dist/core/messages.d.ts +3 -10
- package/packages/agent/dist/core/tool-definition.d.ts +17 -0
- package/packages/agent/dist/core/tools/bash.d.ts +2 -7
- package/packages/agent/dist/core/tools/edit.d.ts +1 -1
- package/packages/agent/dist/core/tools/read.d.ts +1 -1
- package/packages/agent/dist/core/tools/write.d.ts +1 -1
- package/packages/agent/dist/index.d.ts +7 -8
- package/packages/agent/dist/loop/agent.d.ts +2 -6
- package/packages/agent/package.json +1 -1
- package/packages/agent/src/core/agent-settings.ts +2 -2
- package/packages/agent/src/core/compaction/compaction.ts +49 -175
- package/packages/agent/src/core/context-log.ts +49 -0
- package/packages/agent/src/core/messages.ts +5 -11
- package/packages/agent/src/core/tool-definition.ts +29 -0
- package/packages/agent/src/core/tools/bash.ts +2 -9
- package/packages/agent/src/core/tools/edit.ts +2 -11
- package/packages/agent/src/core/tools/read.ts +3 -18
- package/packages/agent/src/core/tools/write.ts +1 -5
- package/packages/agent/src/index.ts +28 -14
- package/packages/agent/src/loop/agent-loop.ts +1 -2
- package/packages/agent/src/loop/agent.ts +8 -2
- package/packages/ai/dist/api/ai-sdk.d.ts +2 -0
- package/packages/ai/dist/types.d.ts +2 -0
- package/packages/ai/src/api/ai-sdk-messages.ts +15 -10
- package/packages/ai/src/api/ai-sdk.ts +11 -0
- package/packages/ai/src/api/transform-messages.ts +1 -1
- package/packages/ai/src/types.ts +2 -0
- package/packages/ai/src/utils/retry.ts +14 -0
- package/packages/ragents/src/agents/actor-input.ts +15 -0
- package/packages/ragents/src/agents/model-context.ts +360 -0
- package/packages/ragents/src/agents/scheduler.ts +3 -7
- package/packages/ragents/src/agents/tools.ts +2 -2
- package/packages/ragents/src/agents/toolset.ts +47 -20
- package/packages/ragents/src/agents/workspaces.ts +0 -6
- package/packages/ragents/src/domain/event-semantics.ts +51 -0
- package/packages/ragents/src/domain/event-validation.ts +166 -0
- package/packages/ragents/src/domain/events.ts +72 -0
- package/packages/ragents/src/domain/model.ts +4 -2
- package/packages/ragents/src/domain/projection.ts +9 -0
- package/packages/ragents/src/domain/tool-result-text.ts +9 -0
- package/packages/ragents/src/drivers/agent-hooks.ts +25 -38
- package/packages/ragents/src/drivers/agent-runtime.ts +118 -933
- package/packages/ragents/src/drivers/agent-turn.ts +735 -0
- package/packages/ragents/src/drivers/agent.ts +9 -7
- package/packages/ragents/src/drivers/skill-preload.ts +53 -63
- package/packages/ragents/src/drivers/types.ts +21 -5
- package/packages/ragents/src/index.ts +7 -6
- package/packages/ragents/src/plugin-host.ts +3 -4
- package/packages/ragents/src/runtime/decisions/actors.ts +10 -0
- package/packages/ragents/src/runtime/decisions/turns.ts +66 -7
- package/packages/ragents/src/runtime/journal-storage.ts +7 -4
- package/packages/ragents/src/runtime/journal.ts +35 -0
- package/packages/ragents/src/runtime/orchestration.ts +140 -9
- package/packages/workspace-executor/src/sandbox-tools.ts +10 -11
- package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
- package/scripts/agent/journal.ts +5 -3
- package/packages/agent/dist/core/agent-session-runtime.d.ts +0 -33
- package/packages/agent/dist/core/agent-session-services.d.ts +0 -35
- package/packages/agent/dist/core/agent-session.d.ts +0 -234
- package/packages/agent/dist/core/auth-guidance.d.ts +0 -4
- package/packages/agent/dist/core/defaults.d.ts +0 -3
- package/packages/agent/dist/core/extensions/index.d.ts +0 -11
- package/packages/agent/dist/core/extensions/loader.d.ts +0 -12
- package/packages/agent/dist/core/extensions/runner.d.ts +0 -39
- package/packages/agent/dist/core/extensions/types.d.ts +0 -156
- package/packages/agent/dist/core/extensions/wrapper.d.ts +0 -12
- package/packages/agent/dist/core/resource-loader.d.ts +0 -39
- package/packages/agent/dist/core/sdk.d.ts +0 -38
- package/packages/agent/dist/core/session-manager.d.ts +0 -189
- package/packages/agent/dist/core/source-info.d.ts +0 -16
- package/packages/agent/dist/core/system-prompt.d.ts +0 -21
- package/packages/agent/src/core/agent-session-runtime.ts +0 -94
- package/packages/agent/src/core/agent-session-services.ts +0 -74
- package/packages/agent/src/core/agent-session.ts +0 -1108
- package/packages/agent/src/core/auth-guidance.ts +0 -14
- package/packages/agent/src/core/defaults.ts +0 -3
- package/packages/agent/src/core/extensions/index.ts +0 -38
- package/packages/agent/src/core/extensions/loader.ts +0 -125
- package/packages/agent/src/core/extensions/runner.ts +0 -211
- package/packages/agent/src/core/extensions/types.ts +0 -197
- package/packages/agent/src/core/extensions/wrapper.ts +0 -24
- package/packages/agent/src/core/resource-loader.ts +0 -142
- package/packages/agent/src/core/sdk.ts +0 -123
- package/packages/agent/src/core/session-manager.ts +0 -743
- package/packages/agent/src/core/source-info.ts +0 -28
- package/packages/agent/src/core/system-prompt.ts +0 -108
|
@@ -1,38 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
createAgentSessionFromServices,
|
|
8
|
-
createAgentSessionRuntime,
|
|
9
|
-
createAgentSessionServices,
|
|
10
|
-
defineTool,
|
|
11
|
-
CURRENT_SESSION_VERSION,
|
|
12
|
-
SessionManager,
|
|
13
|
-
type AgentSession,
|
|
14
|
-
type SessionEntry,
|
|
15
|
-
type AgentSessionRuntime,
|
|
16
|
-
type CreateAgentSessionRuntimeFactory,
|
|
17
|
-
type ExtensionAPI,
|
|
18
|
-
type ExtensionContext,
|
|
19
|
-
type ExtensionError,
|
|
20
|
-
type InlineExtension,
|
|
21
|
-
type ModelRuntime,
|
|
22
|
-
type Skill,
|
|
23
|
-
} from "@ragents/agent";
|
|
24
|
-
import type { UserMessage } from "@ragents/ai";
|
|
25
|
-
|
|
26
|
-
import { ignoredFieldsNotice } from "../agents/toolset.ts";
|
|
27
|
-
import { assertJsonValue, type JsonValue } from "../domain/json.ts";
|
|
28
|
-
import { addUsage, emptyUsage, type TurnUsage } from "../domain/model.ts";
|
|
29
|
-
import { type TurnRequest, type TurnResult } from "./types.ts";
|
|
30
|
-
import {
|
|
31
|
-
createSkillPreloadExtension,
|
|
32
|
-
type SkillPreloadConfiguration,
|
|
33
|
-
} from "./skill-preload.ts";
|
|
34
|
-
import { prepareInputAttachments } from "./attachments.ts";
|
|
1
|
+
import { agentSettings, type AgentSettingsInput, type ModelRuntime, type Skill, type ThinkingLevel as LoopThinkingLevel } from "@ragents/agent";
|
|
2
|
+
import { clampThinkingLevel, getSupportedThinkingLevels } from "@ragents/ai";
|
|
3
|
+
|
|
4
|
+
import { emptyUsage, type TurnUsage } from "../domain/model.ts";
|
|
35
5
|
import type { ThinkingLevel } from "../domain/driver.ts";
|
|
6
|
+
import type { AgentHook } from "./agent-hooks.ts";
|
|
7
|
+
import { AgentTurn } from "./agent-turn.ts";
|
|
8
|
+
import { createSkillPreload, type SkillPreload, type SkillPreloadConfiguration } from "./skill-preload.ts";
|
|
9
|
+
import type { TurnRequest, TurnResult } from "./types.ts";
|
|
36
10
|
|
|
37
11
|
export type AgentRuntimeContext = {
|
|
38
12
|
runId: string;
|
|
@@ -40,7 +14,6 @@ export type AgentRuntimeContext = {
|
|
|
40
14
|
/** The working directory of the workspace tools; a runtime stays bound to it, and it is never a local path of this runtime. */
|
|
41
15
|
workspace: string;
|
|
42
16
|
allowedToolNames: readonly string[] | null;
|
|
43
|
-
forkOf: string | null;
|
|
44
17
|
};
|
|
45
18
|
|
|
46
19
|
export type AgentRuntimeDiagnostic = {
|
|
@@ -49,11 +22,6 @@ export type AgentRuntimeDiagnostic = {
|
|
|
49
22
|
context: AgentRuntimeContext;
|
|
50
23
|
};
|
|
51
24
|
|
|
52
|
-
export type AgentSessionStore = {
|
|
53
|
-
directory: (runId: string, agentId: string) => string | undefined;
|
|
54
|
-
directoryMode: number;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
25
|
export type AgentRuntimeManagerOptions = {
|
|
58
26
|
modelRuntime: ModelRuntime | Promise<ModelRuntime>;
|
|
59
27
|
/** The skills of an agent, read and checked by the host. */
|
|
@@ -63,29 +31,17 @@ export type AgentRuntimeManagerOptions = {
|
|
|
63
31
|
) => readonly Skill[] | Promise<readonly Skill[]>;
|
|
64
32
|
skillPreload?: false | SkillPreloadConfiguration;
|
|
65
33
|
thinkingLevel?: ThinkingLevel;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
resolveExtensionFactories?: (
|
|
34
|
+
/** The hooks of the plugins, bound to the agent. */
|
|
35
|
+
resolveHooks?: (
|
|
69
36
|
context: AgentRuntimeContext,
|
|
70
37
|
signal: AbortSignal,
|
|
71
|
-
) => readonly
|
|
38
|
+
) => readonly AgentHook[] | Promise<readonly AgentHook[]>;
|
|
39
|
+
/** Compaction, retry and provider request limits; unset values keep the defaults. */
|
|
40
|
+
settings?: AgentSettingsInput;
|
|
72
41
|
turnAbortTimeoutMs?: number;
|
|
73
42
|
onDiagnostic?: (diagnostic: AgentRuntimeDiagnostic) => void;
|
|
74
43
|
};
|
|
75
44
|
|
|
76
|
-
type AgentUsage = {
|
|
77
|
-
input: number;
|
|
78
|
-
output: number;
|
|
79
|
-
cacheRead: number;
|
|
80
|
-
cacheWrite: number;
|
|
81
|
-
cost?: { total?: number };
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
type RuntimeConfiguration = {
|
|
85
|
-
selection: TurnRequest<"agent">["selection"];
|
|
86
|
-
systemPrompt: string;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
45
|
const errorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error));
|
|
90
46
|
|
|
91
47
|
const sameToolNames = (left: readonly string[] | null, right: readonly string[] | null) =>
|
|
@@ -198,79 +154,6 @@ class TurnAbortDeadline {
|
|
|
198
154
|
}
|
|
199
155
|
}
|
|
200
156
|
|
|
201
|
-
const syncFile = (file: string) => {
|
|
202
|
-
const descriptor = openSync(file, "r+");
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
fsyncSync(descriptor);
|
|
206
|
-
} finally {
|
|
207
|
-
closeSync(descriptor);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const syncDirectory = async (directory: string) => {
|
|
212
|
-
const handle = await open(directory, "r");
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
await handle.sync();
|
|
216
|
-
} finally {
|
|
217
|
-
await handle.close();
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const writeDurably = async (file: string, content: string) => {
|
|
222
|
-
const handle = await open(file, "wx");
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
await handle.writeFile(content);
|
|
226
|
-
await handle.sync();
|
|
227
|
-
} finally {
|
|
228
|
-
await handle.close();
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const usageOf = (usage: AgentUsage | undefined): TurnUsage => ({
|
|
233
|
-
inputTokens: usage?.input ?? 0,
|
|
234
|
-
outputTokens: usage?.output ?? 0,
|
|
235
|
-
cacheReadTokens: usage?.cacheRead ?? 0,
|
|
236
|
-
cacheWriteTokens: usage?.cacheWrite ?? 0,
|
|
237
|
-
costUsd: usage?.cost?.total ?? 0,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const asJson = (value: unknown) => {
|
|
241
|
-
try {
|
|
242
|
-
return JSON.stringify(value ?? {});
|
|
243
|
-
} catch {
|
|
244
|
-
return String(value);
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const asJsonValue = (value: unknown): JsonValue => JSON.parse(asJson(value)) as JsonValue;
|
|
249
|
-
|
|
250
|
-
/** The conversation and its latest compaction; a new session or a compaction yields another model context. */
|
|
251
|
-
const modelContextOf = (branch: readonly SessionEntry[]): string =>
|
|
252
|
-
`${branch[0]?.id ?? ""}:${branch.findLast((entry) => entry.type === "compaction")?.id ?? ""}`;
|
|
253
|
-
|
|
254
|
-
const asText = (value: unknown): string => {
|
|
255
|
-
if (typeof value === "string")
|
|
256
|
-
return value;
|
|
257
|
-
|
|
258
|
-
const content = (value as { content?: unknown })?.content;
|
|
259
|
-
|
|
260
|
-
if (Array.isArray(content)) {
|
|
261
|
-
const texts = content
|
|
262
|
-
.filter((entry): entry is { type: string; text: string } => (entry as { type?: string })?.type === "text")
|
|
263
|
-
.map((entry) => entry.text);
|
|
264
|
-
|
|
265
|
-
if (texts.length > 0)
|
|
266
|
-
return texts.join("\n");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return asJson(value);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
export const turnDispatcherExtensionName = "ragents-turn-dispatcher";
|
|
273
|
-
|
|
274
157
|
export type TurnAbortLatch = {
|
|
275
158
|
request: () => Promise<void> | null;
|
|
276
159
|
accept: () => void;
|
|
@@ -311,542 +194,20 @@ export const createTurnAbortLatch = (abort: () => Promise<void>): TurnAbortLatch
|
|
|
311
194
|
};
|
|
312
195
|
};
|
|
313
196
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
readonly #unexecutedManagedCalls = new Map<string, { name: string; input: JsonValue }>();
|
|
322
|
-
#turn: {
|
|
323
|
-
request: TurnRequest<"agent">;
|
|
324
|
-
signal: AbortSignal;
|
|
325
|
-
usage: TurnUsage;
|
|
326
|
-
failure: string | null;
|
|
327
|
-
modelFailure: string | null;
|
|
328
|
-
pendingText: string;
|
|
329
|
-
} | null = null;
|
|
330
|
-
|
|
331
|
-
constructor(context: AgentRuntimeContext, onDiagnostic: ((diagnostic: AgentRuntimeDiagnostic) => void) | undefined) {
|
|
332
|
-
this.#context = context;
|
|
333
|
-
this.#onDiagnostic = onDiagnostic;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
extension(): InlineExtension {
|
|
337
|
-
return {
|
|
338
|
-
name: turnDispatcherExtensionName,
|
|
339
|
-
factory: (agent) => {
|
|
340
|
-
this.#api = agent;
|
|
341
|
-
},
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
initialize() {
|
|
346
|
-
const api = this.#requiredApi();
|
|
347
|
-
this.#registeredNames = new Set();
|
|
348
|
-
api.setActiveTools([]);
|
|
349
|
-
|
|
350
|
-
if (this.#turn)
|
|
351
|
-
this.#configureTools(this.#turn.request);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
unknownToolError(name: string): string | undefined {
|
|
355
|
-
const turn = this.#turn;
|
|
356
|
-
if (!turn || turn.signal.aborted)
|
|
357
|
-
return undefined;
|
|
358
|
-
|
|
359
|
-
return `Die Funktion ${name} ist kein natives Werkzeug. Suche ihren Vertrag mit typescript_api und rufe sie in typescript_eval über context.functions auf.`;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/** The running turn that may still take steering; null between turns and after an abort. */
|
|
363
|
-
steerableTurn(): TurnRequest<"agent"> | null {
|
|
364
|
-
const turn = this.#turn;
|
|
365
|
-
|
|
366
|
-
return turn && !turn.signal.aborted ? turn.request : null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
activate(request: TurnRequest<"agent">, signal: AbortSignal) {
|
|
370
|
-
if (this.#turn)
|
|
371
|
-
throw new Error(`agent ${request.agentId} already has an active turn.`);
|
|
372
|
-
|
|
373
|
-
this.#configureTools(request);
|
|
374
|
-
this.#turn = { request, signal, usage: emptyUsage(), failure: null, modelFailure: null, pendingText: "" };
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async refreshTools(): Promise<string | undefined> {
|
|
378
|
-
const turn = this.#turn;
|
|
379
|
-
if (!turn?.request.refreshTools) return undefined;
|
|
380
|
-
turn.signal.throwIfAborted();
|
|
381
|
-
const snapshot = await turn.request.refreshTools();
|
|
382
|
-
turn.signal.throwIfAborted();
|
|
383
|
-
if (this.#turn !== turn) throw new Error("The agent turn ended while refreshing tools.");
|
|
384
|
-
Object.assign(turn.request, snapshot);
|
|
385
|
-
this.#configureTools(turn.request);
|
|
386
|
-
return snapshot.systemPrompt;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
deactivate() {
|
|
390
|
-
if (
|
|
391
|
-
!this.#turn &&
|
|
392
|
-
this.#activeManagedNames.size === 0 &&
|
|
393
|
-
this.#directToolCalls.size === 0 &&
|
|
394
|
-
this.#unexecutedManagedCalls.size === 0
|
|
395
|
-
)
|
|
396
|
-
return;
|
|
397
|
-
|
|
398
|
-
const turn = this.#turn;
|
|
399
|
-
this.#turn = null;
|
|
400
|
-
this.#activeManagedNames.clear();
|
|
401
|
-
this.#directToolCalls.clear();
|
|
402
|
-
this.#unexecutedManagedCalls.clear();
|
|
403
|
-
|
|
404
|
-
this.#api?.setActiveTools([]);
|
|
405
|
-
|
|
406
|
-
if (turn?.signal.aborted)
|
|
407
|
-
this.#recordInterruptedOutput(turn);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
result(): { failure: string | null; usage: TurnUsage } {
|
|
411
|
-
return this.#turn
|
|
412
|
-
? { failure: this.#turn.failure ?? this.#turn.modelFailure, usage: this.#turn.usage }
|
|
413
|
-
: { failure: null, usage: emptyUsage() };
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
addUsage(usage: TurnUsage) {
|
|
417
|
-
if (this.#turn)
|
|
418
|
-
this.#turn.usage = addUsage(this.#turn.usage, usage);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
#recordInterruptedOutput(turn = this.#turn) {
|
|
422
|
-
if (!turn?.pendingText.trim())
|
|
423
|
-
return;
|
|
424
|
-
|
|
425
|
-
turn.request.emit({ kind: "assistant-interrupted", text: turn.pendingText });
|
|
426
|
-
turn.pendingText = "";
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
handleExtensionError(error: ExtensionError) {
|
|
430
|
-
const message = `agent extension ${error.extensionPath} failed during ${error.event}: ${error.error}`;
|
|
431
|
-
|
|
432
|
-
if (this.#turn)
|
|
433
|
-
this.#turn.failure ??= message;
|
|
434
|
-
else
|
|
435
|
-
this.#onDiagnostic?.({ type: "error", message, context: this.#context });
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
handleEvent(raw: unknown) {
|
|
439
|
-
const turn = this.#turn;
|
|
440
|
-
|
|
441
|
-
if (!turn)
|
|
442
|
-
return;
|
|
443
|
-
|
|
444
|
-
const event = raw as Record<string, unknown>;
|
|
445
|
-
|
|
446
|
-
if (event.type === "message_update") {
|
|
447
|
-
if (turn.signal.aborted)
|
|
448
|
-
return;
|
|
449
|
-
|
|
450
|
-
const delta = event.assistantMessageEvent as { type?: string; delta?: string } | undefined;
|
|
451
|
-
|
|
452
|
-
if (delta?.type === "text_delta" && delta.delta) {
|
|
453
|
-
turn.pendingText += delta.delta;
|
|
454
|
-
turn.request.publish({ kind: "text", delta: delta.delta });
|
|
455
|
-
} else if (delta?.type === "thinking_delta" && delta.delta)
|
|
456
|
-
turn.request.publish({ kind: "thinking", delta: delta.delta });
|
|
457
|
-
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (event.type === "tool_execution_start") {
|
|
462
|
-
const id = String(event.toolCallId);
|
|
463
|
-
const name = String(event.toolName);
|
|
464
|
-
turn.request.publish({
|
|
465
|
-
kind: "tool",
|
|
466
|
-
id,
|
|
467
|
-
name,
|
|
468
|
-
arguments: asJson(event.args),
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
if (this.#activeManagedNames.has(name)) {
|
|
472
|
-
this.#unexecutedManagedCalls.set(id, { name, input: asJsonValue(event.args) });
|
|
473
|
-
} else {
|
|
474
|
-
assertJsonValue(event.args, `agent tool ${name} input`);
|
|
475
|
-
this.#directToolCalls.set(id, { name, input: event.args });
|
|
476
|
-
turn.request.recordTool?.({ kind: "started", id, name, input: event.args });
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (event.type === "tool_execution_end") {
|
|
483
|
-
const id = String(event.toolCallId);
|
|
484
|
-
turn.request.publish({
|
|
485
|
-
kind: "tool-result",
|
|
486
|
-
id,
|
|
487
|
-
result: asText(event.result),
|
|
488
|
-
isError: event.isError === true,
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const unexecuted = this.#unexecutedManagedCalls.get(id);
|
|
492
|
-
|
|
493
|
-
if (unexecuted) {
|
|
494
|
-
this.#unexecutedManagedCalls.delete(id);
|
|
495
|
-
|
|
496
|
-
if (event.isError === true) {
|
|
497
|
-
turn.request.recordTool?.({
|
|
498
|
-
kind: "started",
|
|
499
|
-
id,
|
|
500
|
-
name: unexecuted.name,
|
|
501
|
-
input: unexecuted.input,
|
|
502
|
-
});
|
|
503
|
-
turn.request.recordTool?.({
|
|
504
|
-
kind: "failed",
|
|
505
|
-
id,
|
|
506
|
-
name: unexecuted.name,
|
|
507
|
-
error: asText(event.result),
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const direct = this.#directToolCalls.get(id);
|
|
513
|
-
|
|
514
|
-
if (direct) {
|
|
515
|
-
this.#directToolCalls.delete(id);
|
|
516
|
-
const output = asText(event.result);
|
|
517
|
-
turn.request.recordTool?.(event.isError === true
|
|
518
|
-
? { kind: "failed", id, name: direct.name, error: output }
|
|
519
|
-
: { kind: "completed", id, name: direct.name, output });
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (event.type !== "message_end")
|
|
526
|
-
return;
|
|
527
|
-
|
|
528
|
-
const message = event.message as {
|
|
529
|
-
role?: string;
|
|
530
|
-
usage?: AgentUsage;
|
|
531
|
-
content?: Array<{ type: string; text?: string; thinking?: string }>;
|
|
532
|
-
stopReason?: string;
|
|
533
|
-
errorMessage?: string;
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
if (message.role !== "assistant")
|
|
537
|
-
return;
|
|
538
|
-
|
|
539
|
-
turn.usage = addUsage(turn.usage, usageOf(message.usage));
|
|
540
|
-
|
|
541
|
-
if (turn.signal.aborted || message.stopReason === "aborted") {
|
|
542
|
-
this.#recordInterruptedOutput(turn);
|
|
543
|
-
turn.failure ??= "The agent turn was aborted.";
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
turn.pendingText = "";
|
|
548
|
-
for (const entry of message.content ?? []) {
|
|
549
|
-
if (entry.type === "text" && entry.text?.trim())
|
|
550
|
-
turn.request.emit({ kind: "assistant", text: entry.text.trim() });
|
|
551
|
-
else if (entry.type === "thinking" && entry.thinking?.trim())
|
|
552
|
-
turn.request.emit({ kind: "reasoning", text: entry.thinking.trim() });
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// The session retries or compacts after a provider error, so only the last answer decides.
|
|
556
|
-
turn.modelFailure = message.stopReason === "error"
|
|
557
|
-
? message.errorMessage ?? "The model provider returned an error."
|
|
558
|
-
: null;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
#configureTools(request: TurnRequest<"agent">) {
|
|
562
|
-
const api = this.#requiredApi();
|
|
563
|
-
const requestedNames = new Set<string>();
|
|
564
|
-
const allowedNames = request.allowedToolNames === null ? null : new Set(request.allowedToolNames);
|
|
565
|
-
|
|
566
|
-
if (allowedNames && allowedNames.size !== request.allowedToolNames?.length)
|
|
567
|
-
throw new Error(`agent ${request.agentId} received duplicate allowed tool names.`);
|
|
568
|
-
|
|
569
|
-
if (allowedNames) {
|
|
570
|
-
const outside = request.tools
|
|
571
|
-
.map((tool) => tool.name)
|
|
572
|
-
.filter((name) => !["typescript_api", "typescript_eval"].includes(name) && !allowedNames.has(name));
|
|
573
|
-
|
|
574
|
-
if (outside.length > 0)
|
|
575
|
-
throw new Error(`Der Agent erhielt Werkzeuge außerhalb seiner Freigabeliste: ${[...new Set(outside)].join(", ")}.`);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
for (const entry of request.tools) {
|
|
579
|
-
if (requestedNames.has(entry.name))
|
|
580
|
-
throw new Error(`RAgents supplied duplicate agent tool ${entry.name}.`);
|
|
581
|
-
|
|
582
|
-
requestedNames.add(entry.name);
|
|
583
|
-
const existing = api.getAllTools().find((tool) => tool.name === entry.name);
|
|
584
|
-
|
|
585
|
-
if (existing && !this.#registeredNames.has(entry.name))
|
|
586
|
-
throw new Error(`RAgents tool ${entry.name} conflicts with agent extension tool ${existing.sourceInfo.path}.`);
|
|
587
|
-
|
|
588
|
-
api.registerTool(
|
|
589
|
-
defineTool({
|
|
590
|
-
name: entry.name,
|
|
591
|
-
label: entry.label,
|
|
592
|
-
description: [entry.description, entry.longDescription].filter(Boolean).join("\n\n"),
|
|
593
|
-
parameters: entry.schema,
|
|
594
|
-
executionMode:
|
|
595
|
-
entry.executionMode ??
|
|
596
|
-
(entry.name.startsWith("agent_") ? "sequential" : "parallel"),
|
|
597
|
-
execute: async (toolCallId: string, params: unknown, toolSignal: AbortSignal | undefined, _onUpdate: unknown, context: ExtensionContext) => {
|
|
598
|
-
const current = this.#turn;
|
|
599
|
-
|
|
600
|
-
if (!current)
|
|
601
|
-
throw new Error(`agent tool ${entry.name} was called outside an RAgents turn.`);
|
|
602
|
-
|
|
603
|
-
if (current.signal.aborted || toolSignal?.aborted)
|
|
604
|
-
throw new Error(`agent tool ${entry.name} was aborted.`);
|
|
605
|
-
|
|
606
|
-
assertJsonValue(params, `agent tool ${entry.name} input`);
|
|
607
|
-
this.#unexecutedManagedCalls.delete(toolCallId);
|
|
608
|
-
const call = await current.request.invoke(toolCallId, entry.name, params, modelContextOf(context.sessionManager.getBranch()));
|
|
609
|
-
const text = typeof call.output === "string" ? call.output : asJson(call.output);
|
|
610
|
-
|
|
611
|
-
return {
|
|
612
|
-
content: [{
|
|
613
|
-
type: "text" as const,
|
|
614
|
-
text: call.ignoredFields.length === 0 ? text : `${ignoredFieldsNotice(entry, call.ignoredFields)}\n\n${text}`,
|
|
615
|
-
}],
|
|
616
|
-
details: {},
|
|
617
|
-
};
|
|
618
|
-
},
|
|
619
|
-
}),
|
|
620
|
-
);
|
|
621
|
-
this.#registeredNames.add(entry.name);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
this.#activeManagedNames = requestedNames;
|
|
625
|
-
api.setActiveTools([...requestedNames]);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
#requiredApi() {
|
|
629
|
-
if (!this.#api)
|
|
630
|
-
throw new Error(`agent extensions are not bound for agent ${this.#context.agentId}.`);
|
|
631
|
-
|
|
632
|
-
return this.#api;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
type SessionIdentityRecord = {
|
|
637
|
-
version: 1;
|
|
638
|
-
runId: string;
|
|
639
|
-
agentId: string;
|
|
640
|
-
sessionId: string;
|
|
641
|
-
file: string;
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
/** The source branch up to its last finished exchange, without unfinished tool calls and without reasoning. */
|
|
645
|
-
export const forkableBranch = (branch: readonly SessionEntry[]): SessionEntry[] => {
|
|
646
|
-
const answered = new Set(branch.flatMap((entry) =>
|
|
647
|
-
entry.type === "message" && entry.message.role === "toolResult" ? [entry.message.toolCallId] : []));
|
|
648
|
-
const unfinished = (entry: SessionEntry) =>
|
|
649
|
-
entry.type === "message" && entry.message.role === "assistant"
|
|
650
|
-
&& entry.message.content.some((block) => block.type === "toolCall" && !answered.has(block.id));
|
|
651
|
-
const end = branch.findIndex(unfinished);
|
|
652
|
-
const kept = end === -1 ? branch : branch.slice(0, end);
|
|
653
|
-
|
|
654
|
-
return kept.map((entry) => entry.type === "message" && entry.message.role === "assistant"
|
|
655
|
-
? { ...entry, message: { ...entry.message, content: entry.message.content.filter((block) => block.type !== "thinking") } }
|
|
656
|
-
: entry);
|
|
197
|
+
type ManagedAgentRuntimeSetup = {
|
|
198
|
+
context: AgentRuntimeContext;
|
|
199
|
+
modelRuntime: ModelRuntime;
|
|
200
|
+
skills: readonly Skill[];
|
|
201
|
+
hooks: readonly AgentHook[];
|
|
202
|
+
options: AgentRuntimeManagerOptions;
|
|
203
|
+
turnAbortTimeoutMs: number;
|
|
657
204
|
};
|
|
658
205
|
|
|
659
|
-
|
|
660
|
-
readonly #context: AgentRuntimeContext;
|
|
661
|
-
readonly #store: AgentSessionStore | undefined;
|
|
662
|
-
readonly #runtimeDirectory: string;
|
|
663
|
-
#directory: string | null = null;
|
|
664
|
-
#signature: string | null = null;
|
|
665
|
-
|
|
666
|
-
constructor(context: AgentRuntimeContext, store: AgentSessionStore | undefined, runtimeDirectory: string) {
|
|
667
|
-
this.#context = context;
|
|
668
|
-
this.#store = store;
|
|
669
|
-
this.#runtimeDirectory = runtimeDirectory;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
async open(): Promise<SessionManager> {
|
|
673
|
-
if (!this.#store)
|
|
674
|
-
return SessionManager.inMemory(this.#runtimeDirectory);
|
|
675
|
-
|
|
676
|
-
const configuredDirectory = this.#store.directory(this.#context.runId, this.#context.agentId);
|
|
677
|
-
|
|
678
|
-
if (!configuredDirectory)
|
|
679
|
-
return SessionManager.inMemory(this.#runtimeDirectory);
|
|
680
|
-
|
|
681
|
-
this.#directory = resolve(configuredDirectory);
|
|
682
|
-
await mkdir(this.#directory, { recursive: true, mode: this.#store.directoryMode });
|
|
683
|
-
await chmod(this.#directory, this.#store.directoryMode);
|
|
684
|
-
const stored = await this.#readMarker();
|
|
685
|
-
|
|
686
|
-
if (stored) {
|
|
687
|
-
const manager = await this.#openStored(stored);
|
|
688
|
-
this.#signature = `${stored.sessionId}\u0000${resolve(this.#directory, stored.file)}`;
|
|
689
|
-
|
|
690
|
-
return manager;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const manager = this.#context.forkOf ? await this.#forkedManager(this.#context.forkOf) : await this.#newManager();
|
|
694
|
-
await this.persist(manager.getSessionFile(), manager.getSessionId());
|
|
695
|
-
|
|
696
|
-
return manager;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
async persist(file: string | undefined, sessionId: string) {
|
|
700
|
-
if (!file || !this.#store)
|
|
701
|
-
return;
|
|
702
|
-
|
|
703
|
-
const resolvedFile = resolve(file);
|
|
704
|
-
|
|
705
|
-
if (this.#directory && dirname(resolvedFile) !== this.#directory)
|
|
706
|
-
throw new Error(`agent session ${resolvedFile} is outside its configured directory ${this.#directory}.`);
|
|
707
|
-
|
|
708
|
-
const signature = `${sessionId}\u0000${resolvedFile}`;
|
|
709
|
-
|
|
710
|
-
if (signature === this.#signature)
|
|
711
|
-
return;
|
|
712
|
-
|
|
713
|
-
if (this.#directory) {
|
|
714
|
-
const marker = this.#markerPath();
|
|
715
|
-
const temporary = `${marker}.${process.pid}.${randomUUID()}.tmp`;
|
|
716
|
-
const record: SessionIdentityRecord = {
|
|
717
|
-
version: 1,
|
|
718
|
-
runId: this.#context.runId,
|
|
719
|
-
agentId: this.#context.agentId,
|
|
720
|
-
sessionId,
|
|
721
|
-
file: basename(resolvedFile),
|
|
722
|
-
};
|
|
723
|
-
await writeDurably(temporary, `${JSON.stringify(record, null, 2)}\n`);
|
|
724
|
-
await rename(temporary, marker);
|
|
725
|
-
await syncDirectory(this.#directory);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
this.#signature = signature;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
async #newManager() {
|
|
732
|
-
if (!this.#directory)
|
|
733
|
-
return SessionManager.inMemory(this.#runtimeDirectory);
|
|
734
|
-
|
|
735
|
-
const file = join(this.#directory, `ragents-${randomUUID()}.jsonl`);
|
|
736
|
-
await writeDurably(file, "");
|
|
737
|
-
await syncDirectory(this.#directory);
|
|
738
|
-
const manager = SessionManager.open(file, this.#directory, this.#runtimeDirectory);
|
|
739
|
-
syncFile(file);
|
|
740
|
-
|
|
741
|
-
return manager;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
async #forkedManager(sourceAgentId: string) {
|
|
745
|
-
if (!this.#directory || !this.#store)
|
|
746
|
-
throw new Error("Forking an agent session requires a session directory.");
|
|
747
|
-
|
|
748
|
-
const sourceDirectory = this.#store.directory(this.#context.runId, sourceAgentId);
|
|
749
|
-
|
|
750
|
-
if (!sourceDirectory)
|
|
751
|
-
throw new Error(`Fork source ${sourceAgentId} has no persisted session directory.`);
|
|
752
|
-
|
|
753
|
-
const record = await this.#readMarker(join(resolve(sourceDirectory), "active-session.json"), sourceAgentId);
|
|
754
|
-
|
|
755
|
-
if (!record)
|
|
756
|
-
throw new Error(`Fork source ${sourceAgentId} has no model context yet.`);
|
|
757
|
-
|
|
758
|
-
const sourceFile = resolve(sourceDirectory, record.file);
|
|
759
|
-
const source = SessionManager.open(sourceFile, resolve(sourceDirectory), this.#runtimeDirectory);
|
|
760
|
-
const header = {
|
|
761
|
-
type: "session",
|
|
762
|
-
version: CURRENT_SESSION_VERSION,
|
|
763
|
-
id: randomUUID(),
|
|
764
|
-
timestamp: new Date().toISOString(),
|
|
765
|
-
cwd: this.#runtimeDirectory,
|
|
766
|
-
parentSession: sourceFile,
|
|
767
|
-
};
|
|
768
|
-
const lines = [header, ...forkableBranch(source.getBranch())].map((entry) => JSON.stringify(entry)).join("\n");
|
|
769
|
-
const file = join(this.#directory, `ragents-${randomUUID()}.jsonl`);
|
|
770
|
-
await writeDurably(file, `${lines}\n`);
|
|
771
|
-
await syncDirectory(this.#directory);
|
|
772
|
-
const manager = SessionManager.open(file, this.#directory, this.#runtimeDirectory);
|
|
773
|
-
syncFile(file);
|
|
774
|
-
|
|
775
|
-
return manager;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
async #readMarker(marker = this.#markerPath(), agentId = this.#context.agentId): Promise<SessionIdentityRecord | null> {
|
|
779
|
-
let content: string;
|
|
780
|
-
|
|
781
|
-
try {
|
|
782
|
-
content = await readFile(marker, "utf8");
|
|
783
|
-
} catch (error) {
|
|
784
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT")
|
|
785
|
-
return null;
|
|
786
|
-
|
|
787
|
-
throw error;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
let candidate: unknown;
|
|
791
|
-
|
|
792
|
-
try {
|
|
793
|
-
candidate = JSON.parse(content);
|
|
794
|
-
} catch (error) {
|
|
795
|
-
throw new Error(`agent session identity marker is invalid: ${errorMessage(error)}`);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const record = candidate as Partial<SessionIdentityRecord>;
|
|
799
|
-
|
|
800
|
-
if (
|
|
801
|
-
record.version !== 1 ||
|
|
802
|
-
record.runId !== this.#context.runId ||
|
|
803
|
-
record.agentId !== agentId ||
|
|
804
|
-
typeof record.sessionId !== "string" ||
|
|
805
|
-
!record.sessionId ||
|
|
806
|
-
typeof record.file !== "string" ||
|
|
807
|
-
!record.file ||
|
|
808
|
-
basename(record.file) !== record.file
|
|
809
|
-
)
|
|
810
|
-
throw new Error(`agent session identity marker for ${agentId} has invalid contents.`);
|
|
811
|
-
|
|
812
|
-
return record as SessionIdentityRecord;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
async #openStored(record: SessionIdentityRecord) {
|
|
816
|
-
const directory = this.#directory;
|
|
817
|
-
|
|
818
|
-
if (!directory)
|
|
819
|
-
throw new Error("A agent session marker requires a session directory.");
|
|
820
|
-
|
|
821
|
-
const file = resolve(directory, record.file);
|
|
822
|
-
await access(file);
|
|
823
|
-
const manager = SessionManager.open(file, directory, this.#runtimeDirectory);
|
|
824
|
-
|
|
825
|
-
if (manager.getSessionId() !== record.sessionId)
|
|
826
|
-
throw new Error(`agent session identity ${record.sessionId} does not match ${manager.getSessionId()} in ${file}.`);
|
|
827
|
-
|
|
828
|
-
return manager;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
#markerPath() {
|
|
832
|
-
if (!this.#directory)
|
|
833
|
-
throw new Error("A agent session marker requires a session directory.");
|
|
834
|
-
|
|
835
|
-
return join(this.#directory, "active-session.json");
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
206
|
+
/** What an agent keeps between its turns: skills, hooks and the skill preload. Its conversation lives only in the journal. */
|
|
839
207
|
class ManagedAgentRuntime {
|
|
840
|
-
readonly #
|
|
841
|
-
readonly #
|
|
842
|
-
|
|
843
|
-
readonly #identity: PersistedAgentSession;
|
|
844
|
-
readonly #configuration: RuntimeConfiguration;
|
|
845
|
-
readonly #modelRuntime: ModelRuntime;
|
|
846
|
-
readonly #defaultThinking: ThinkingLevel | undefined;
|
|
847
|
-
readonly #turnAbortTimeoutMs: number;
|
|
848
|
-
readonly #onDiagnostic: ((diagnostic: AgentRuntimeDiagnostic) => void) | undefined;
|
|
849
|
-
#unsubscribe: (() => void) | null = null;
|
|
208
|
+
readonly #setup: ManagedAgentRuntimeSetup;
|
|
209
|
+
readonly #preload: SkillPreload | null;
|
|
210
|
+
#turn: AgentTurn | null = null;
|
|
850
211
|
#running = false;
|
|
851
212
|
#disposed = false;
|
|
852
213
|
#disposePromise: Promise<void> | null = null;
|
|
@@ -855,30 +216,17 @@ class ManagedAgentRuntime {
|
|
|
855
216
|
#resolveIdle: (() => void) | null = null;
|
|
856
217
|
readonly #inFlight = new Set<Promise<unknown>>();
|
|
857
218
|
|
|
858
|
-
constructor(
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
this.#context = options.context;
|
|
870
|
-
this.#runtime = options.runtime;
|
|
871
|
-
this.#dispatcher = options.dispatcher;
|
|
872
|
-
this.#identity = options.identity;
|
|
873
|
-
this.#configuration = options.configuration;
|
|
874
|
-
this.#modelRuntime = options.modelRuntime;
|
|
875
|
-
this.#defaultThinking = options.defaultThinking;
|
|
876
|
-
this.#turnAbortTimeoutMs = options.turnAbortTimeoutMs;
|
|
877
|
-
this.#onDiagnostic = options.onDiagnostic;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
async start() {
|
|
881
|
-
await this.#bind(this.#runtime.session);
|
|
219
|
+
constructor(setup: ManagedAgentRuntimeSetup) {
|
|
220
|
+
this.#setup = setup;
|
|
221
|
+
const { skills, options, context, modelRuntime } = setup;
|
|
222
|
+
this.#preload = skills.length > 0 && options.skillPreload !== false
|
|
223
|
+
? createSkillPreload({
|
|
224
|
+
modelRuntime,
|
|
225
|
+
...options.skillPreload,
|
|
226
|
+
onDiagnostic: (message) => options.onDiagnostic?.({ type: "warning", message, context }),
|
|
227
|
+
onUsage: (usage: TurnUsage) => this.#turn?.addUsage(usage),
|
|
228
|
+
})
|
|
229
|
+
: null;
|
|
882
230
|
}
|
|
883
231
|
|
|
884
232
|
async runTurn(request: TurnRequest<"agent">, signal: AbortSignal): Promise<TurnResult> {
|
|
@@ -888,99 +236,102 @@ class ManagedAgentRuntime {
|
|
|
888
236
|
if (this.#running)
|
|
889
237
|
throw new Error(`agent runtime for ${request.agentId} cannot run concurrent turns.`);
|
|
890
238
|
|
|
891
|
-
const promptChanged = request.systemPrompt !== this.#configuration.systemPrompt;
|
|
892
239
|
const stopController = new AbortController();
|
|
893
240
|
const turnSignal = AbortSignal.any([signal, stopController.signal]);
|
|
894
241
|
this.#running = true;
|
|
895
242
|
this.#idle = new Promise<void>((resolveIdle) => {
|
|
896
243
|
this.#resolveIdle = resolveIdle;
|
|
897
244
|
});
|
|
898
|
-
|
|
899
|
-
const abortLatch = createTurnAbortLatch(() => this.#
|
|
900
|
-
const abortDeadline = new TurnAbortDeadline(turnSignal, this.#turnAbortTimeoutMs);
|
|
245
|
+
const abortDeadline = new TurnAbortDeadline(turnSignal, this.#setup.turnAbortTimeoutMs);
|
|
246
|
+
const abortLatch = createTurnAbortLatch(() => this.#turn?.abort() ?? Promise.resolve());
|
|
901
247
|
const abort = () => void abortLatch.request();
|
|
902
248
|
const abortTurn = () => {
|
|
903
249
|
stopController.abort(new Error(`agent runtime ${request.runId}/${request.agentId} is stopping.`));
|
|
904
250
|
|
|
905
251
|
return abortLatch.request();
|
|
906
252
|
};
|
|
253
|
+
let turn: AgentTurn | null = null;
|
|
907
254
|
this.#abortActiveTurn = abortTurn;
|
|
908
|
-
|
|
909
255
|
turnSignal.addEventListener("abort", abort, { once: true });
|
|
910
256
|
|
|
911
257
|
try {
|
|
912
258
|
if (turnSignal.aborted) {
|
|
913
259
|
abortLatch.request();
|
|
914
260
|
|
|
915
|
-
return
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (promptChanged) {
|
|
919
|
-
this.#configuration.systemPrompt = request.systemPrompt;
|
|
920
|
-
this.#runtime.session.setSystemPrompt(request.systemPrompt);
|
|
261
|
+
return { failure: "The agent turn was aborted.", usage: emptyUsage() };
|
|
921
262
|
}
|
|
922
263
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
this.#dispatcher.activate(request, turnSignal);
|
|
930
|
-
await abortDeadline.wait(this.#track(this.#refreshTools()));
|
|
931
|
-
|
|
932
|
-
const prepared = await prepareInputAttachments(request, this.#runtime.session.model?.input ?? []);
|
|
933
|
-
|
|
934
|
-
if (turnSignal.aborted) {
|
|
935
|
-
abortLatch.request();
|
|
936
|
-
} else {
|
|
937
|
-
await abortDeadline.wait(this.#track(this.#runtime.session.prompt(prepared.prompt, {
|
|
938
|
-
attachments: prepared.attachments,
|
|
939
|
-
preflightResult: (accepted) => {
|
|
940
|
-
if (!accepted)
|
|
941
|
-
return;
|
|
942
|
-
|
|
943
|
-
abortLatch.accept();
|
|
944
|
-
queueMicrotask(() => void abortLatch.reapply());
|
|
945
|
-
},
|
|
946
|
-
})));
|
|
947
|
-
}
|
|
264
|
+
turn = this.#turnFor(request, turnSignal);
|
|
265
|
+
this.#turn = turn;
|
|
266
|
+
await abortDeadline.wait(this.#track(turn.run(() => {
|
|
267
|
+
abortLatch.accept();
|
|
268
|
+
queueMicrotask(() => void abortLatch.reapply());
|
|
269
|
+
})));
|
|
948
270
|
|
|
949
271
|
const abortPromise = abortLatch.pending();
|
|
950
272
|
|
|
951
273
|
if (abortPromise)
|
|
952
274
|
await abortDeadline.wait(this.#track(abortPromise));
|
|
953
275
|
|
|
954
|
-
return turnSignal.aborted
|
|
955
|
-
? this.#abortedResult()
|
|
956
|
-
: this.#dispatcher.result();
|
|
276
|
+
return turnSignal.aborted ? turn.abortedResult() : turn.result();
|
|
957
277
|
} catch (error) {
|
|
958
|
-
|
|
278
|
+
if (!turn)
|
|
279
|
+
return { failure: turnSignal.aborted ? "The agent turn was aborted." : errorMessage(error), usage: emptyUsage() };
|
|
280
|
+
|
|
281
|
+
const result = turn.result();
|
|
959
282
|
|
|
960
283
|
return turnSignal.aborted
|
|
961
|
-
?
|
|
284
|
+
? turn.abortedResult()
|
|
962
285
|
: { failure: result.failure ?? errorMessage(error), usage: result.usage };
|
|
963
286
|
} finally {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
this.#running = false;
|
|
978
|
-
this.#resolveIdle?.();
|
|
979
|
-
this.#resolveIdle = null;
|
|
980
|
-
}
|
|
287
|
+
turnSignal.removeEventListener("abort", abort);
|
|
288
|
+
turn?.deactivate();
|
|
289
|
+
|
|
290
|
+
if (this.#turn === turn)
|
|
291
|
+
this.#turn = null;
|
|
292
|
+
|
|
293
|
+
if (this.#abortActiveTurn === abortTurn)
|
|
294
|
+
this.#abortActiveTurn = null;
|
|
295
|
+
|
|
296
|
+
abortDeadline.dispose();
|
|
297
|
+
this.#running = false;
|
|
298
|
+
this.#resolveIdle?.();
|
|
299
|
+
this.#resolveIdle = null;
|
|
981
300
|
}
|
|
982
301
|
}
|
|
983
302
|
|
|
303
|
+
#turnFor(request: TurnRequest<"agent">, signal: AbortSignal): AgentTurn {
|
|
304
|
+
const { modelRuntime, options, context } = this.#setup;
|
|
305
|
+
const { provider, model: modelId, thinking } = request.selection;
|
|
306
|
+
const model = modelRuntime.getModel(provider, modelId);
|
|
307
|
+
|
|
308
|
+
if (!model)
|
|
309
|
+
throw new Error(`The model ${provider}/${modelId} is not configured.`);
|
|
310
|
+
|
|
311
|
+
const level = thinking ?? options.thinkingLevel;
|
|
312
|
+
|
|
313
|
+
if (level) {
|
|
314
|
+
const available = getSupportedThinkingLevels(model);
|
|
315
|
+
|
|
316
|
+
if (!available.includes(level))
|
|
317
|
+
throw new Error(`The thinking level ${level} does not exist for ${modelId}; valid: ${available.join(", ")}.`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return new AgentTurn({
|
|
321
|
+
request,
|
|
322
|
+
signal,
|
|
323
|
+
modelRuntime,
|
|
324
|
+
model,
|
|
325
|
+
thinkingLevel: (level ?? clampThinkingLevel(model, "medium")) as LoopThinkingLevel,
|
|
326
|
+
skills: this.#setup.skills,
|
|
327
|
+
hooks: this.#setup.hooks,
|
|
328
|
+
preload: this.#preload,
|
|
329
|
+
settings: agentSettings(options.settings),
|
|
330
|
+
track: (operation) => this.#track(operation),
|
|
331
|
+
onDiagnostic: (message) => options.onDiagnostic?.({ type: "warning", message, context }),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
984
335
|
dispose(): Promise<void> {
|
|
985
336
|
if (this.#disposed)
|
|
986
337
|
return Promise.resolve();
|
|
@@ -1006,22 +357,12 @@ class ManagedAgentRuntime {
|
|
|
1006
357
|
if (this.#running)
|
|
1007
358
|
this.#abortActiveTurn?.();
|
|
1008
359
|
|
|
1009
|
-
this.#
|
|
1010
|
-
|
|
1011
|
-
this.#dispatcher.deactivate();
|
|
1012
|
-
const active = await Promise.allSettled([this.#runtime.dispose(), idle]);
|
|
1013
|
-
const persistence = await Promise.allSettled([
|
|
1014
|
-
this.#identity.persist(this.#runtime.session.sessionFile, this.#runtime.session.sessionId),
|
|
1015
|
-
]);
|
|
1016
|
-
|
|
1017
|
-
for (const result of [...active, ...persistence]) {
|
|
1018
|
-
if (result.status === "rejected")
|
|
1019
|
-
throw result.reason;
|
|
1020
|
-
}
|
|
360
|
+
this.#turn?.deactivate();
|
|
361
|
+
await idle;
|
|
1021
362
|
}
|
|
1022
363
|
|
|
1023
364
|
waitForSettlement(): Promise<void> {
|
|
1024
|
-
return Promise.allSettled([...this.#inFlight
|
|
365
|
+
return Promise.allSettled([...this.#inFlight]).then(() => undefined);
|
|
1025
366
|
}
|
|
1026
367
|
|
|
1027
368
|
#track<T>(operation: PromiseLike<T> | T): Promise<T> {
|
|
@@ -1034,92 +375,6 @@ class ManagedAgentRuntime {
|
|
|
1034
375
|
|
|
1035
376
|
return pending;
|
|
1036
377
|
}
|
|
1037
|
-
|
|
1038
|
-
#abortedResult(): TurnResult {
|
|
1039
|
-
const result = this.#dispatcher.result();
|
|
1040
|
-
|
|
1041
|
-
return {
|
|
1042
|
-
failure: result.failure ?? "The agent turn was aborted.",
|
|
1043
|
-
usage: result.usage,
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
async #bind(session: AgentSession) {
|
|
1048
|
-
const bindingErrors: ExtensionError[] = [];
|
|
1049
|
-
let initializing = true;
|
|
1050
|
-
await session.bindExtensions({
|
|
1051
|
-
onError: (error) => {
|
|
1052
|
-
if (initializing)
|
|
1053
|
-
bindingErrors.push(error);
|
|
1054
|
-
else
|
|
1055
|
-
this.#dispatcher.handleExtensionError(error);
|
|
1056
|
-
},
|
|
1057
|
-
});
|
|
1058
|
-
initializing = false;
|
|
1059
|
-
|
|
1060
|
-
if (bindingErrors.length > 0)
|
|
1061
|
-
throw new Error(bindingErrors.map((entry) => `${entry.extensionPath}: ${entry.error}`).join("; "));
|
|
1062
|
-
|
|
1063
|
-
this.#dispatcher.initialize();
|
|
1064
|
-
const prepareNextTurn = session.agent.prepareNextTurnWithContext;
|
|
1065
|
-
session.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
1066
|
-
await this.#refreshTools();
|
|
1067
|
-
return prepareNextTurn?.(turn, signal);
|
|
1068
|
-
};
|
|
1069
|
-
session.agent.formatUnknownToolError = (name) => this.#dispatcher.unknownToolError(name);
|
|
1070
|
-
session.agent.steeringSource = () => this.#steering();
|
|
1071
|
-
this.#unsubscribe = session.subscribe((event) => this.#dispatcher.handleEvent(event));
|
|
1072
|
-
await this.#identity.persist(session.sessionFile, session.sessionId);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
async #steering(): Promise<UserMessage[]> {
|
|
1076
|
-
const request = this.#dispatcher.steerableTurn();
|
|
1077
|
-
|
|
1078
|
-
if (!request)
|
|
1079
|
-
return [];
|
|
1080
|
-
|
|
1081
|
-
const messages: UserMessage[] = [];
|
|
1082
|
-
|
|
1083
|
-
for (const steered of request.claimSteering()) {
|
|
1084
|
-
const prepared = await prepareInputAttachments(
|
|
1085
|
-
{ ...request, attachments: steered.attachments, prompt: steered.prompt },
|
|
1086
|
-
this.#runtime.session.model?.input ?? [],
|
|
1087
|
-
);
|
|
1088
|
-
messages.push({ role: "user", content: [{ type: "text", text: prepared.prompt }, ...prepared.attachments], timestamp: Date.now() });
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
return messages;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
async #refreshTools(): Promise<void> {
|
|
1095
|
-
const systemPrompt = await this.#dispatcher.refreshTools();
|
|
1096
|
-
if (systemPrompt !== undefined && systemPrompt !== this.#configuration.systemPrompt) {
|
|
1097
|
-
this.#configuration.systemPrompt = systemPrompt;
|
|
1098
|
-
this.#runtime.session.setSystemPrompt(systemPrompt);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
async #selectModel(request: TurnRequest<"agent">) {
|
|
1103
|
-
const { provider, model: modelId, thinking } = request.selection;
|
|
1104
|
-
const model = this.#modelRuntime.getModel(provider, modelId);
|
|
1105
|
-
|
|
1106
|
-
if (!model)
|
|
1107
|
-
throw new Error(`Das Modell ${provider}/${modelId} ist nicht konfiguriert.`);
|
|
1108
|
-
|
|
1109
|
-
if (this.#runtime.session.model?.provider !== provider || this.#runtime.session.model.id !== modelId)
|
|
1110
|
-
await this.#runtime.session.setModel(model);
|
|
1111
|
-
|
|
1112
|
-
const level = thinking ?? this.#defaultThinking;
|
|
1113
|
-
|
|
1114
|
-
if (!level)
|
|
1115
|
-
return;
|
|
1116
|
-
|
|
1117
|
-
const available = this.#runtime.session.getAvailableThinkingLevels();
|
|
1118
|
-
if (!available.includes(level))
|
|
1119
|
-
throw new Error(`Die Denktiefe ${level} gibt es für ${modelId} nicht; gültig: ${available.join(", ")}.`);
|
|
1120
|
-
|
|
1121
|
-
this.#runtime.session.setThinkingLevel(level);
|
|
1122
|
-
}
|
|
1123
378
|
}
|
|
1124
379
|
|
|
1125
380
|
type RuntimeRecord = {
|
|
@@ -1159,7 +414,6 @@ export class AgentRuntimeManager {
|
|
|
1159
414
|
agentId: request.agentId,
|
|
1160
415
|
workspace: request.workspace,
|
|
1161
416
|
allowedToolNames: request.allowedToolNames === null ? null : [...request.allowedToolNames],
|
|
1162
|
-
forkOf: request.forkOf,
|
|
1163
417
|
};
|
|
1164
418
|
let record = this.#runtimes.get(key);
|
|
1165
419
|
|
|
@@ -1172,7 +426,7 @@ export class AgentRuntimeManager {
|
|
|
1172
426
|
if (!record) {
|
|
1173
427
|
const creationAbort = new AbortController();
|
|
1174
428
|
const creationSignal = AbortSignal.any([signal, creationAbort.signal]);
|
|
1175
|
-
const runtime = this.#create(context,
|
|
429
|
+
const runtime = this.#create(context, creationSignal);
|
|
1176
430
|
record = { context, runtime, creationAbort };
|
|
1177
431
|
this.#runtimes.set(key, record);
|
|
1178
432
|
void runtime.catch(() => {
|
|
@@ -1291,103 +545,34 @@ export class AgentRuntimeManager {
|
|
|
1291
545
|
}
|
|
1292
546
|
}
|
|
1293
547
|
|
|
1294
|
-
async #create(context: AgentRuntimeContext,
|
|
548
|
+
async #create(context: AgentRuntimeContext, signal: AbortSignal) {
|
|
1295
549
|
const modelRuntime = await awaitWithSignal(this.#modelRuntime, signal);
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
const loadHostFactories = managedEnvironment || (request.allowedToolNames?.length ?? 0) > 0;
|
|
1302
|
-
const resolvedFactories = loadHostFactories && this.#options.resolveExtensionFactories
|
|
1303
|
-
? await awaitWithSignal(this.#options.resolveExtensionFactories(context, signal), signal)
|
|
1304
|
-
: undefined;
|
|
550
|
+
const managedEnvironment = context.allowedToolNames === null;
|
|
551
|
+
const loadHostHooks = managedEnvironment || (context.allowedToolNames?.length ?? 0) > 0;
|
|
552
|
+
const hooks = loadHostHooks && this.#options.resolveHooks
|
|
553
|
+
? await this.#settled(context.runId, this.#options.resolveHooks(context, signal), signal)
|
|
554
|
+
: [];
|
|
1305
555
|
const skills = managedEnvironment && this.#options.resolveSkills
|
|
1306
|
-
? await
|
|
556
|
+
? await this.#settled(context.runId, this.#options.resolveSkills(context, signal), signal)
|
|
1307
557
|
: [];
|
|
1308
558
|
assertUniqueSkillNames(skills);
|
|
1309
|
-
const skillPreload = skills.length > 0 && this.#options.skillPreload !== false
|
|
1310
|
-
? createSkillPreloadExtension({
|
|
1311
|
-
modelRuntime,
|
|
1312
|
-
...this.#options.skillPreload,
|
|
1313
|
-
onDiagnostic: (message) => this.#options.onDiagnostic?.({ type: "warning", message, context }),
|
|
1314
|
-
onUsage: (usage) => dispatcher.addUsage(usage),
|
|
1315
|
-
})
|
|
1316
|
-
: null;
|
|
1317
|
-
const extensionFactories = [
|
|
1318
|
-
...(loadHostFactories ? this.#options.extensionFactories ?? [] : []),
|
|
1319
|
-
...(resolvedFactories ?? []),
|
|
1320
|
-
...(skillPreload ? [skillPreload] : []),
|
|
1321
|
-
dispatcher.extension(),
|
|
1322
|
-
];
|
|
1323
|
-
const configuration: RuntimeConfiguration = {
|
|
1324
|
-
selection: request.selection,
|
|
1325
|
-
systemPrompt: request.systemPrompt,
|
|
1326
|
-
};
|
|
1327
|
-
const factory: CreateAgentSessionRuntimeFactory = async (runtimeOptions) => {
|
|
1328
|
-
const services = await createAgentSessionServices({
|
|
1329
|
-
cwd: runtimeOptions.cwd,
|
|
1330
|
-
signal: runtimeOptions.signal,
|
|
1331
|
-
...(runtimeOptions.onExtensionFactorySettlement
|
|
1332
|
-
? { onExtensionFactorySettlement: runtimeOptions.onExtensionFactorySettlement }
|
|
1333
|
-
: {}),
|
|
1334
|
-
modelRuntime,
|
|
1335
|
-
resourceLoaderOptions: {
|
|
1336
|
-
skills,
|
|
1337
|
-
extensionFactories,
|
|
1338
|
-
systemPrompt: configuration.systemPrompt,
|
|
1339
|
-
},
|
|
1340
|
-
});
|
|
1341
|
-
const extensionErrors = services.resourceLoader.getExtensions().errors;
|
|
1342
|
-
|
|
1343
|
-
if (extensionErrors.length > 0)
|
|
1344
|
-
throw new Error(extensionErrors.map((entry) => `${entry.path}: ${entry.error}`).join("; "));
|
|
1345
|
-
|
|
1346
|
-
const { provider, model: modelId, thinking } = configuration.selection;
|
|
1347
|
-
const model = modelRuntime.getModel(provider, modelId);
|
|
1348
|
-
|
|
1349
|
-
if (!model)
|
|
1350
|
-
throw new Error(`Das Modell ${provider}/${modelId} ist nicht konfiguriert.`);
|
|
1351
559
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const created = await createAgentSessionFromServices({
|
|
1355
|
-
services,
|
|
1356
|
-
sessionManager: runtimeOptions.sessionManager,
|
|
1357
|
-
model,
|
|
1358
|
-
...(thinking ?? this.#options.thinkingLevel
|
|
1359
|
-
? { thinkingLevel: thinking ?? this.#options.thinkingLevel }
|
|
1360
|
-
: {}),
|
|
1361
|
-
});
|
|
1362
|
-
|
|
1363
|
-
return { ...created, services };
|
|
1364
|
-
};
|
|
1365
|
-
const runtime = await createAgentSessionRuntime(factory, {
|
|
1366
|
-
cwd: runtimeDirectory,
|
|
1367
|
-
sessionManager,
|
|
1368
|
-
signal,
|
|
1369
|
-
onExtensionFactorySettlement: (settlement) =>
|
|
1370
|
-
this.#rememberRunSettlement(context.runId, settlement),
|
|
1371
|
-
});
|
|
1372
|
-
const managed = new ManagedAgentRuntime({
|
|
560
|
+
return new ManagedAgentRuntime({
|
|
1373
561
|
context,
|
|
1374
|
-
runtime,
|
|
1375
|
-
dispatcher,
|
|
1376
|
-
identity,
|
|
1377
|
-
configuration,
|
|
1378
562
|
modelRuntime,
|
|
1379
|
-
|
|
563
|
+
skills,
|
|
564
|
+
hooks,
|
|
565
|
+
options: this.#options,
|
|
1380
566
|
turnAbortTimeoutMs: this.#turnAbortTimeoutMs,
|
|
1381
|
-
onDiagnostic: this.#options.onDiagnostic,
|
|
1382
567
|
});
|
|
568
|
+
}
|
|
1383
569
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
}
|
|
570
|
+
/** Waits for a resolution under the creation signal; a halt of the run still waits for its real end. */
|
|
571
|
+
#settled<T>(runId: string, operation: PromiseLike<T> | T, signal: AbortSignal): Promise<T> {
|
|
572
|
+
const pending = Promise.resolve(operation);
|
|
573
|
+
this.#rememberRunSettlement(runId, pending.then(() => undefined, () => undefined));
|
|
574
|
+
|
|
575
|
+
return awaitWithSignal(pending, signal);
|
|
1391
576
|
}
|
|
1392
577
|
|
|
1393
578
|
async #disposeRecord(key: string, record: RuntimeRecord) {
|