@vinhnt-sdk/core 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-factory.d.ts +11 -0
- package/dist/agent/agent-factory.d.ts.map +1 -1
- package/dist/agent/agent-factory.js +11 -2
- package/dist/agent/agent-factory.js.map +1 -1
- package/dist/agent/agent-registry.d.ts +3 -0
- package/dist/agent/agent-registry.d.ts.map +1 -1
- package/dist/agent/agent-registry.js.map +1 -1
- package/dist/agent/create-agent-tool.d.ts +13 -13
- package/dist/agent/create-agent-tool.d.ts.map +1 -1
- package/dist/agent/delegate-batch-tool.d.ts.map +1 -1
- package/dist/agent/delegate-batch-tool.js +1 -1
- package/dist/agent/delegate-batch-tool.js.map +1 -1
- package/dist/agent/delegate-tool.js +1 -1
- package/dist/agent/delegate-tool.js.map +1 -1
- package/dist/agent/execution-engine.d.ts +1 -1
- package/dist/agent/execution-engine.d.ts.map +1 -1
- package/dist/agent/spawn-agent-tool.d.ts +10 -10
- package/dist/agent/spawn-agent-tool.d.ts.map +1 -1
- package/dist/event-bus/bridge.d.ts +2 -3
- package/dist/event-bus/bridge.d.ts.map +1 -1
- package/dist/event-bus/bridge.js +12 -5
- package/dist/event-bus/bridge.js.map +1 -1
- package/dist/fakes/fake-approval-store.d.ts +2 -2
- package/dist/fakes/fake-approval-store.d.ts.map +1 -1
- package/dist/fakes/fake-approval-store.js +28 -6
- package/dist/fakes/fake-approval-store.js.map +1 -1
- package/dist/fakes/fake-model.d.ts.map +1 -1
- package/dist/fakes/fake-model.js +3 -2
- package/dist/fakes/fake-model.js.map +1 -1
- package/dist/fakes/fake-store.d.ts +4 -1
- package/dist/fakes/fake-store.d.ts.map +1 -1
- package/dist/fakes/fake-store.js +33 -0
- package/dist/fakes/fake-store.js.map +1 -1
- package/dist/index.d.ts +20 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/kernel/approval-handler.d.ts +1 -1
- package/dist/kernel/approval-handler.d.ts.map +1 -1
- package/dist/kernel/approval-handler.js +5 -1
- package/dist/kernel/approval-handler.js.map +1 -1
- package/dist/kernel/circuit-breaker.d.ts.map +1 -1
- package/dist/kernel/circuit-breaker.js +4 -1
- package/dist/kernel/circuit-breaker.js.map +1 -1
- package/dist/kernel/kernel-error.d.ts +1 -2
- package/dist/kernel/kernel-error.d.ts.map +1 -1
- package/dist/kernel/kernel-error.js +2 -3
- package/dist/kernel/kernel-error.js.map +1 -1
- package/dist/kernel/kernel-session.d.ts +13 -5
- package/dist/kernel/kernel-session.d.ts.map +1 -1
- package/dist/kernel/kernel-session.js +71 -13
- package/dist/kernel/kernel-session.js.map +1 -1
- package/dist/kernel/kernel-types.d.ts +6 -8
- package/dist/kernel/kernel-types.d.ts.map +1 -1
- package/dist/kernel/kernel-types.js.map +1 -1
- package/dist/kernel/kernel.d.ts +60 -16
- package/dist/kernel/kernel.d.ts.map +1 -1
- package/dist/kernel/kernel.js +428 -76
- package/dist/kernel/kernel.js.map +1 -1
- package/dist/kernel/model-caller.d.ts +28 -2
- package/dist/kernel/model-caller.d.ts.map +1 -1
- package/dist/kernel/model-caller.js +47 -12
- package/dist/kernel/model-caller.js.map +1 -1
- package/dist/kernel/permission-gate.d.ts +6 -4
- package/dist/kernel/permission-gate.d.ts.map +1 -1
- package/dist/kernel/permission-gate.js +16 -6
- package/dist/kernel/permission-gate.js.map +1 -1
- package/dist/kernel/run-context.d.ts +26 -0
- package/dist/kernel/run-context.d.ts.map +1 -0
- package/dist/kernel/run-context.js +13 -0
- package/dist/kernel/run-context.js.map +1 -0
- package/dist/kernel/run-loop.d.ts +35 -14
- package/dist/kernel/run-loop.d.ts.map +1 -1
- package/dist/kernel/run-loop.js +295 -87
- package/dist/kernel/run-loop.js.map +1 -1
- package/dist/kernel/self-correction.d.ts +2 -2
- package/dist/kernel/self-correction.d.ts.map +1 -1
- package/dist/kernel/self-correction.js +3 -2
- package/dist/kernel/self-correction.js.map +1 -1
- package/dist/kernel/state-machine.d.ts +2 -0
- package/dist/kernel/state-machine.d.ts.map +1 -1
- package/dist/kernel/state-machine.js +2 -0
- package/dist/kernel/state-machine.js.map +1 -1
- package/dist/kernel/step-executor.d.ts +11 -3
- package/dist/kernel/step-executor.d.ts.map +1 -1
- package/dist/kernel/step-executor.js +54 -24
- package/dist/kernel/step-executor.js.map +1 -1
- package/dist/kernel/sub-agent-runner.d.ts +10 -7
- package/dist/kernel/sub-agent-runner.d.ts.map +1 -1
- package/dist/kernel/sub-agent-runner.js +105 -31
- package/dist/kernel/sub-agent-runner.js.map +1 -1
- package/dist/kernel/tool-context-builder.d.ts +7 -2
- package/dist/kernel/tool-context-builder.d.ts.map +1 -1
- package/dist/kernel/tool-context-builder.js +23 -6
- package/dist/kernel/tool-context-builder.js.map +1 -1
- package/dist/kernel/tool-error-router.d.ts +1 -1
- package/dist/kernel/tool-error-router.d.ts.map +1 -1
- package/dist/kernel/tool-error-router.js +4 -3
- package/dist/kernel/tool-error-router.js.map +1 -1
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -1
- package/dist/model.d.ts +9 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +18 -4
- package/dist/model.js.map +1 -1
- package/dist/plugin/manager.d.ts +3 -1
- package/dist/plugin/manager.d.ts.map +1 -1
- package/dist/plugin/manager.js +50 -3
- package/dist/plugin/manager.js.map +1 -1
- package/dist/plugin/plugin-file-loader.js +2 -2
- package/dist/plugin/plugin-file-loader.js.map +1 -1
- package/dist/plugin.d.ts +50 -4
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +14 -1
- package/dist/plugin.js.map +1 -1
- package/dist/session/null-store.d.ts +1 -0
- package/dist/session/null-store.d.ts.map +1 -1
- package/dist/session/null-store.js +7 -0
- package/dist/session/null-store.js.map +1 -1
- package/dist/session/run-coordinator.d.ts +9 -3
- package/dist/session/run-coordinator.d.ts.map +1 -1
- package/dist/session/run-coordinator.js +4 -0
- package/dist/session/run-coordinator.js.map +1 -1
- package/dist/skill/create-skill-tool.d.ts +5 -4
- package/dist/skill/create-skill-tool.d.ts.map +1 -1
- package/dist/skill/create-skill-tool.js +8 -5
- package/dist/skill/create-skill-tool.js.map +1 -1
- package/dist/skill/skill-def-registry.d.ts +6 -0
- package/dist/skill/skill-def-registry.d.ts.map +1 -1
- package/dist/skill/skill-def-registry.js +4 -0
- package/dist/skill/skill-def-registry.js.map +1 -1
- package/dist/tool/bridge.d.ts +7 -1
- package/dist/tool/bridge.d.ts.map +1 -1
- package/dist/tool/bridge.js +5 -0
- package/dist/tool/bridge.js.map +1 -1
- package/dist/tool/builtin-provider.d.ts +35 -0
- package/dist/tool/builtin-provider.d.ts.map +1 -0
- package/dist/tool/builtin-provider.js +75 -0
- package/dist/tool/builtin-provider.js.map +1 -0
- package/dist/tool/index.d.ts +21 -23
- package/dist/tool/index.d.ts.map +1 -1
- package/dist/tool/index.js +19 -18
- package/dist/tool/index.js.map +1 -1
- package/dist/tool/provider-helpers.d.ts.map +1 -1
- package/dist/tool/provider-helpers.js +3 -2
- package/dist/tool/provider-helpers.js.map +1 -1
- package/dist/tool/runtime.d.ts +23 -2
- package/dist/tool/runtime.d.ts.map +1 -1
- package/dist/tool/runtime.js +20 -16
- package/dist/tool/runtime.js.map +1 -1
- package/dist/tracer.d.ts +5 -0
- package/dist/tracer.d.ts.map +1 -1
- package/dist/tracer.js +4 -0
- package/dist/tracer.js.map +1 -1
- package/dist/workflow/agent-graph.js +3 -3
- package/dist/workflow/agent-graph.js.map +1 -1
- package/dist/workflow/workflow-helpers.js +2 -2
- package/dist/workflow/workflow-helpers.js.map +1 -1
- package/dist/workspace.d.ts +7 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +4 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +21 -11
package/dist/kernel/kernel.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { ValidationError, AgentNotFoundError } from "@vinhnt-sdk/schema";
|
|
2
|
+
import { restoreRunFromStore, findActiveSessionIds } from "@vinhnt-sdk/session";
|
|
3
|
+
import { createRedactingLogger } from "@vinhnt-sdk/security";
|
|
2
4
|
import { wildcardMatch } from "@vinhnt-sdk/schema";
|
|
3
5
|
import { getBehaviourProfile } from "../agent/behaviour-profiles.js";
|
|
4
|
-
import { DEFAULT_MAX_STEPS, DEFAULT_MAX_TOOL_CALLS_PER_STEP, DOOM_LOOP_THRESHOLD } from "
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { DEFAULT_MAX_STEPS, DEFAULT_MAX_TOOL_CALLS_PER_STEP, DOOM_LOOP_THRESHOLD } from "@vinhnt-sdk/step-executor";
|
|
7
|
+
/** Default thinking prompt for reasoning steps. Exported for user override. */
|
|
8
|
+
export const DEFAULT_THINKING_PROMPT = "Analyze the user's request and the conversation context. Think step by step about what needs to be done. Output your reasoning.";
|
|
9
|
+
import { RunStateMachine } from "@vinhnt-sdk/step-executor";
|
|
10
|
+
import { PermissionGate } from "@vinhnt-sdk/step-executor";
|
|
11
|
+
import { ModelCaller } from "@vinhnt-sdk/llm";
|
|
12
|
+
import { StepExecutor } from "@vinhnt-sdk/step-executor";
|
|
13
|
+
import { ToolSaga } from "@vinhnt-sdk/tools";
|
|
14
|
+
import { createRunContext } from "@vinhnt-sdk/step-executor";
|
|
15
|
+
import { CircuitBreaker } from "@vinhnt-sdk/step-executor";
|
|
16
|
+
import { KernelError } from "@vinhnt-sdk/step-executor";
|
|
12
17
|
import { runLoop as executeRunLoop } from "./run-loop.js";
|
|
13
18
|
import { LifecycleManager } from "./lifecycle-manager.js";
|
|
14
|
-
import { emitEvent as sessionEmit, addSessionMessage as sessionAddMessage,
|
|
19
|
+
import { emitEvent as sessionEmit, addSessionMessage as sessionAddMessage, emitCompletedWithSession as sessionEmitCompleted, emitFail as sessionEmitFail, buildAgentIdentity, } from "./kernel-session.js";
|
|
15
20
|
import { runAgent as runSubAgent, runAgentsParallel as runSubAgentsParallel, createSubAgentAndRegister, } from "./sub-agent-runner.js";
|
|
16
21
|
import { normalizeConfig } from "./kernel-types.js";
|
|
17
|
-
export { CircuitBreaker, CircuitBreakerOpenError } from "
|
|
18
|
-
export { KernelError } from "
|
|
22
|
+
export { CircuitBreaker, CircuitBreakerOpenError } from "@vinhnt-sdk/step-executor";
|
|
23
|
+
export { KernelError } from "@vinhnt-sdk/step-executor";
|
|
19
24
|
/**
|
|
20
25
|
* Core agent orchestration — run loop, permission gate, tool execution, sub-agent spawning.
|
|
21
26
|
* Composition root for all run-time services. Each call to `run()` creates a tracked
|
|
@@ -46,6 +51,7 @@ export class AgentKernel {
|
|
|
46
51
|
toolProviderRegistry;
|
|
47
52
|
sessionTitleGenerator;
|
|
48
53
|
saga;
|
|
54
|
+
runSagas = new Map();
|
|
49
55
|
stepExecutor;
|
|
50
56
|
currentAgent;
|
|
51
57
|
currentDepth = 0;
|
|
@@ -60,6 +66,16 @@ export class AgentKernel {
|
|
|
60
66
|
sessionDeps;
|
|
61
67
|
subAgentDeps;
|
|
62
68
|
runSessionStates = new Map();
|
|
69
|
+
/** runId → sessionId for pending-input persistence (RV-21). */
|
|
70
|
+
runSessionIds = new Map();
|
|
71
|
+
/** FIFO mirror of the state-machine input queue, used to mark promotion (RV-21). */
|
|
72
|
+
runPendingEntries = new Map();
|
|
73
|
+
/** Per-session monotonic admitted-seq allocator, seeded from the store lazily (RV-21). */
|
|
74
|
+
nextAdmittedSeq = new Map();
|
|
75
|
+
/** Per-session serialized write chain so admitted seqs stay FIFO-consistent (RV-21). */
|
|
76
|
+
inputWriteChains = new Map();
|
|
77
|
+
/** Per-run execution context — keeps parallel runs' agent/depth/chain/tools isolated. */
|
|
78
|
+
runContexts = new Map();
|
|
63
79
|
lifecycleManager;
|
|
64
80
|
constructor(config) {
|
|
65
81
|
const normalized = normalizeConfig(config);
|
|
@@ -104,19 +120,35 @@ export class AgentKernel {
|
|
|
104
120
|
this.compactionThreshold = normalized.compactionThreshold;
|
|
105
121
|
this.termination = normalized.termination;
|
|
106
122
|
this.circuitBreaker = normalized.circuitBreaker ?? new CircuitBreaker({
|
|
123
|
+
...(normalized.maxRetries !== undefined
|
|
124
|
+
? { maxRetries: normalized.maxRetries }
|
|
125
|
+
: normalized.circuitBreakerOptions?.maxRetries !== undefined
|
|
126
|
+
? { maxRetries: normalized.circuitBreakerOptions.maxRetries }
|
|
127
|
+
: {}),
|
|
128
|
+
...(normalized.retryBackoffMs !== undefined
|
|
129
|
+
? { backoffMs: normalized.retryBackoffMs }
|
|
130
|
+
: normalized.circuitBreakerOptions?.backoffMs !== undefined
|
|
131
|
+
? { backoffMs: normalized.circuitBreakerOptions.backoffMs }
|
|
132
|
+
: {}),
|
|
133
|
+
...(normalized.maxRetryBackoffMs !== undefined
|
|
134
|
+
? { maxBackoffMs: normalized.maxRetryBackoffMs }
|
|
135
|
+
: normalized.circuitBreakerOptions?.maxBackoffMs !== undefined
|
|
136
|
+
? { maxBackoffMs: normalized.circuitBreakerOptions.maxBackoffMs }
|
|
137
|
+
: {}),
|
|
107
138
|
...normalized.circuitBreakerOptions,
|
|
108
|
-
maxRetries: normalized.maxRetries ?? normalized.circuitBreakerOptions?.maxRetries,
|
|
109
|
-
backoffMs: normalized.retryBackoffMs ?? normalized.circuitBreakerOptions?.backoffMs,
|
|
110
|
-
maxBackoffMs: normalized.maxRetryBackoffMs ?? normalized.circuitBreakerOptions?.maxBackoffMs,
|
|
111
139
|
});
|
|
112
140
|
this.saga = new ToolSaga();
|
|
113
141
|
this.stateMachine = new RunStateMachine();
|
|
114
142
|
this.permissionGate = new PermissionGate({
|
|
115
143
|
store: normalized.store,
|
|
116
|
-
eventBus: normalized.eventBus,
|
|
144
|
+
...(normalized.eventBus !== undefined ? { eventBus: normalized.eventBus } : {}),
|
|
145
|
+
// Core's PluginManager structurally satisfies the step-executor hook
|
|
146
|
+
// contract (named generic fireHook — castable, matches by design).
|
|
117
147
|
pluginManager: normalized.pluginManager,
|
|
118
148
|
approvalStore: normalized.permissions?.approvalStore,
|
|
119
|
-
|
|
149
|
+
...(normalized.permissions?.autoApprovalEnabled !== undefined
|
|
150
|
+
? { autoApprovalEnabled: normalized.permissions.autoApprovalEnabled }
|
|
151
|
+
: {}),
|
|
120
152
|
});
|
|
121
153
|
if (normalized.permissions?.globalPermissionRules) {
|
|
122
154
|
this.permissionGate.setGlobalRules(normalized.permissions.globalPermissionRules);
|
|
@@ -129,19 +161,32 @@ export class AgentKernel {
|
|
|
129
161
|
}
|
|
130
162
|
this.maxTokens = normalized.maxTokens ?? 4096;
|
|
131
163
|
const maxTokens = this.maxTokens;
|
|
132
|
-
const thinkingPrompt = normalized.thinkingPrompt ??
|
|
164
|
+
const thinkingPrompt = normalized.thinkingPrompt ?? DEFAULT_THINKING_PROMPT;
|
|
165
|
+
// P1-N: wire the redacting logger into the kernel so every log line that
|
|
166
|
+
// passes through it (e.g. model-caller diagnostics) is scrubbed of secrets.
|
|
167
|
+
const rawLogger = normalized.logger;
|
|
168
|
+
const redactingLogger = rawLogger
|
|
169
|
+
? {
|
|
170
|
+
debug: createRedactingLogger(rawLogger.debug.bind(rawLogger)),
|
|
171
|
+
info: createRedactingLogger(rawLogger.info.bind(rawLogger)),
|
|
172
|
+
warn: createRedactingLogger(rawLogger.warn.bind(rawLogger)),
|
|
173
|
+
error: createRedactingLogger(rawLogger.error.bind(rawLogger)),
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
133
176
|
this.modelCaller = new ModelCaller({
|
|
134
177
|
defaultModel: normalized.model,
|
|
135
178
|
modelRegistry: normalized.modelRegistry,
|
|
136
179
|
maxTokens,
|
|
137
180
|
thinkingBudget: this.thinkingBudget,
|
|
138
181
|
thinkingPrompt,
|
|
182
|
+
// Core's PluginManager structurally satisfies the model-caller hook
|
|
183
|
+
// contract (named generic fireHook — castable, matches by design).
|
|
139
184
|
pluginManager: normalized.pluginManager,
|
|
140
|
-
logger:
|
|
185
|
+
logger: redactingLogger,
|
|
141
186
|
emitEvent: (event, persist) => this.emitEvent(event, persist),
|
|
142
187
|
modelForRun: (runId) => this.stateMachine.getModelForRun(runId),
|
|
143
188
|
setModelForRun: (runId, model) => this.stateMachine.setModelForRun(runId, model),
|
|
144
|
-
getAvailableTools: () => this.getAvailableTools(),
|
|
189
|
+
getAvailableTools: (runId) => this.getAvailableTools(runId),
|
|
145
190
|
});
|
|
146
191
|
const self = this;
|
|
147
192
|
this.stepExecutor = new StepExecutor({
|
|
@@ -159,7 +204,9 @@ export class AgentKernel {
|
|
|
159
204
|
...(normalized.workspaceRoot !== undefined ? { workspaceRoot: normalized.workspaceRoot } : {}),
|
|
160
205
|
currentAgent: this.currentAgent,
|
|
161
206
|
saga: this.saga,
|
|
162
|
-
|
|
207
|
+
agentForRun: (runId) => this.runContexts.get(runId)?.agent,
|
|
208
|
+
sagaForRun: (runId) => this.runContexts.get(runId)?.saga ?? this.saga,
|
|
209
|
+
findTool: (name, runId) => self.findTool(name, runId),
|
|
163
210
|
hasTool: (name) => self.hasTool(name),
|
|
164
211
|
});
|
|
165
212
|
if (normalized.tools) {
|
|
@@ -167,9 +214,9 @@ export class AgentKernel {
|
|
|
167
214
|
}
|
|
168
215
|
this.sessionDeps = {
|
|
169
216
|
store: this.store,
|
|
170
|
-
eventBus: this.eventBus,
|
|
171
|
-
pluginManager: this.pluginManager,
|
|
172
|
-
sessionStore: this.sessionStore,
|
|
217
|
+
...(this.eventBus !== undefined ? { eventBus: this.eventBus } : {}),
|
|
218
|
+
...(this.pluginManager !== undefined ? { pluginManager: this.pluginManager } : {}),
|
|
219
|
+
...(this.sessionStore !== undefined ? { sessionStore: this.sessionStore } : {}),
|
|
173
220
|
modelCaller: this.modelCaller,
|
|
174
221
|
saga: this.saga,
|
|
175
222
|
...(normalized.noStore ? { noStore: true } : {}),
|
|
@@ -181,14 +228,46 @@ export class AgentKernel {
|
|
|
181
228
|
store: this.store,
|
|
182
229
|
modelCaller: this.modelCaller,
|
|
183
230
|
maxSubAgentDepth: this.maxSubAgentDepth,
|
|
184
|
-
currentAgentRef: {
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
currentAgentRef: {
|
|
232
|
+
get value() { return self.activeRunContext()?.agent ?? self.currentAgent; },
|
|
233
|
+
set value(v) {
|
|
234
|
+
const rc = self.activeRunContext();
|
|
235
|
+
if (rc) {
|
|
236
|
+
rc.agent = v;
|
|
237
|
+
rc.cachedTools = null;
|
|
238
|
+
rc.cachedToolsAgentId = undefined;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
self.currentAgent = v;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
currentDepthRef: {
|
|
246
|
+
get value() { return self.activeRunContext()?.depth ?? self.currentDepth; },
|
|
247
|
+
set value(v) {
|
|
248
|
+
const rc = self.activeRunContext();
|
|
249
|
+
if (rc)
|
|
250
|
+
rc.depth = v;
|
|
251
|
+
else
|
|
252
|
+
self.currentDepth = v;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
agentChainRef: {
|
|
256
|
+
get value() { return self.activeRunContext()?.agentChain ?? self.agentChain; },
|
|
257
|
+
set value(v) {
|
|
258
|
+
const rc = self.activeRunContext();
|
|
259
|
+
if (rc)
|
|
260
|
+
rc.agentChain = v;
|
|
261
|
+
else
|
|
262
|
+
self.agentChain = v;
|
|
263
|
+
},
|
|
264
|
+
},
|
|
187
265
|
sessionStateRef: {
|
|
188
266
|
get value() { return self["sessionState"]; },
|
|
189
267
|
set value(v) { self["sessionState"] = v; },
|
|
190
268
|
},
|
|
191
269
|
runFn: (prompt, ctx, sessionId, _agentOverride) => this.run(prompt, ctx, sessionId, undefined, _agentOverride),
|
|
270
|
+
runContextFor: (runId) => this.runContexts.get(runId),
|
|
192
271
|
};
|
|
193
272
|
this.lifecycleManager = new LifecycleManager();
|
|
194
273
|
}
|
|
@@ -230,12 +309,16 @@ export class AgentKernel {
|
|
|
230
309
|
addSessionMessage(sid, role, content, extra) {
|
|
231
310
|
return sessionAddMessage(this.sessionDeps, sid, role, content, extra);
|
|
232
311
|
}
|
|
233
|
-
getAvailableTools() {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
312
|
+
getAvailableTools(runId) {
|
|
313
|
+
const rc = runId ? this.runContexts.get(runId) : undefined;
|
|
314
|
+
const target = rc?.agent ?? this.currentAgent;
|
|
315
|
+
const agentId = target?.id;
|
|
316
|
+
const cachedTools = rc ? rc.cachedTools : this.cachedTools;
|
|
317
|
+
const cachedToolsAgentId = rc ? rc.cachedToolsAgentId : this.cachedToolsAgentId;
|
|
318
|
+
if (cachedTools && cachedToolsAgentId === agentId) {
|
|
319
|
+
return cachedTools;
|
|
237
320
|
}
|
|
238
|
-
const caps =
|
|
321
|
+
const caps = target?.capabilities?.tools;
|
|
239
322
|
// Tool source priority: toolProviderRegistry > toolRegistry > tools[]
|
|
240
323
|
let pool;
|
|
241
324
|
if (this.toolProviderRegistry) {
|
|
@@ -248,7 +331,7 @@ export class AgentKernel {
|
|
|
248
331
|
// belongs to plus core tools (tools in no registered domain). Undefined
|
|
249
332
|
// (legacy default) means no filtering — all tools visible. Per-domain
|
|
250
333
|
// permission defaults (e.g. an MCP server set to "deny") remove the tool.
|
|
251
|
-
const domains =
|
|
334
|
+
const domains = target?.domains;
|
|
252
335
|
if (domains !== undefined) {
|
|
253
336
|
pool = pool.filter((t) => {
|
|
254
337
|
const domain = this.domainFor(t.id);
|
|
@@ -269,7 +352,7 @@ export class AgentKernel {
|
|
|
269
352
|
pool = pool.filter((t) => caps.some((c) => wildcardMatch(c, t.id)));
|
|
270
353
|
}
|
|
271
354
|
// Apply behaviour mode profile (if not "build" mode, profile rules restrict tools)
|
|
272
|
-
const behaviourMode =
|
|
355
|
+
const behaviourMode = target?.behaviourMode ?? "build";
|
|
273
356
|
if (behaviourMode !== "build") {
|
|
274
357
|
const profile = getBehaviourProfile(behaviourMode);
|
|
275
358
|
pool = pool.filter((t) => {
|
|
@@ -282,15 +365,21 @@ export class AgentKernel {
|
|
|
282
365
|
});
|
|
283
366
|
}
|
|
284
367
|
const result = pool.filter((t) => {
|
|
285
|
-
const perm = this.permissionGate.checkTool(t.id, t.risk, undefined,
|
|
368
|
+
const perm = this.permissionGate.checkTool(t.id, t.risk, undefined, target);
|
|
286
369
|
return perm.allowed || perm.needsApproval;
|
|
287
370
|
});
|
|
288
|
-
|
|
289
|
-
|
|
371
|
+
if (rc) {
|
|
372
|
+
rc.cachedTools = result;
|
|
373
|
+
rc.cachedToolsAgentId = agentId;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
this.cachedTools = result;
|
|
377
|
+
this.cachedToolsAgentId = agentId;
|
|
378
|
+
}
|
|
290
379
|
return result;
|
|
291
380
|
}
|
|
292
|
-
findTool(name) {
|
|
293
|
-
return this.getAvailableTools().find((t) => t.id === name);
|
|
381
|
+
findTool(name, runId) {
|
|
382
|
+
return this.getAvailableTools(runId).find((t) => t.id === name);
|
|
294
383
|
}
|
|
295
384
|
/** Whether a tool is registered in the underlying pool (regardless of per-agent filtering). */
|
|
296
385
|
hasTool(name) {
|
|
@@ -357,10 +446,17 @@ export class AgentKernel {
|
|
|
357
446
|
return this.store;
|
|
358
447
|
}
|
|
359
448
|
/** Delegate a prompt to a named sub-agent. Returns the agent's response text. */
|
|
360
|
-
async runAgent(agentId, prompt, ctx, sessionId) {
|
|
449
|
+
async runAgent(agentId, prompt, ctx, sessionId, parentRunId, signal) {
|
|
361
450
|
if (!this.agentRegistry)
|
|
362
451
|
throw new KernelError("internal_error", "No agent registry configured");
|
|
363
|
-
return runSubAgent(this.subAgentDeps, agentId, prompt, ctx, sessionId);
|
|
452
|
+
return runSubAgent(this.subAgentDeps, agentId, prompt, ctx, sessionId, parentRunId, signal);
|
|
453
|
+
}
|
|
454
|
+
/** Build the system head (agent identity + configured system prompt) for a run. */
|
|
455
|
+
buildSystemPrompt(agent) {
|
|
456
|
+
const identity = buildAgentIdentity(agent);
|
|
457
|
+
const systemPrompt = agent?.systemPrompt;
|
|
458
|
+
const parts = [identity, systemPrompt].filter(Boolean);
|
|
459
|
+
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
364
460
|
}
|
|
365
461
|
/**
|
|
366
462
|
* Start a new agent run. Creates a run ID, wires abort + session, and returns
|
|
@@ -381,18 +477,80 @@ export class AgentKernel {
|
|
|
381
477
|
};
|
|
382
478
|
}
|
|
383
479
|
this.runSessionStates.set(runId, this.sessionState);
|
|
480
|
+
if (sessionId)
|
|
481
|
+
this.runSessionIds.set(runId, sessionId);
|
|
384
482
|
const agent = agentOverride ?? this.currentAgent;
|
|
385
483
|
if (agent) {
|
|
386
484
|
this.modelCaller.resolveAgentModel(agent, runId);
|
|
387
485
|
}
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
const systemPrompt = agent
|
|
391
|
-
const parts = [identity, systemPrompt, prompt].filter(Boolean);
|
|
392
|
-
const effectivePrompt = parts.join("\n\n");
|
|
486
|
+
// RV-40: keep the system head separate from the user prompt so the model
|
|
487
|
+
// receives a real `system` message instead of a flattened user turn.
|
|
488
|
+
const systemPrompt = this.buildSystemPrompt(agent);
|
|
393
489
|
// Per-run saga (no instance-level mutation)
|
|
394
490
|
const runSaga = new ToolSaga();
|
|
395
|
-
const promise = this.runLoop(
|
|
491
|
+
const promise = this.runLoop(prompt, runId, ctx, abort, sessionId, userContentParts, agentOverride, runSaga, undefined, systemPrompt)
|
|
492
|
+
.then(() => undefined)
|
|
493
|
+
.finally(() => {
|
|
494
|
+
this.stateMachine.runIdStack.pop();
|
|
495
|
+
this.runSessionStates.delete(runId);
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
runId,
|
|
499
|
+
completed: promise,
|
|
500
|
+
abort: () => { abort.abort(); },
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
this.stateMachine.runIdStack.pop();
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Resume a previously persisted run from durable storage (snapshot + messages).
|
|
510
|
+
* Restores the run's session state (history, step counter, token counts) and
|
|
511
|
+
* continues the run loop from where it stopped. Emits no `run.started` event
|
|
512
|
+
* (the run has already started) and does not re-inject the original prompt.
|
|
513
|
+
*
|
|
514
|
+
* @param runId The run to resume — must have persisted events in the store.
|
|
515
|
+
* @param prompt The steering prompt for the resumed run (appended as the
|
|
516
|
+
* latest user turn).
|
|
517
|
+
* @param ctx Request context.
|
|
518
|
+
* @param sessionId Optional session id — falls back to the persisted one.
|
|
519
|
+
*/
|
|
520
|
+
async resumeRun(runId, prompt, ctx, sessionId, agentOverride) {
|
|
521
|
+
const restored = await restoreRunFromStore(this.store, this.sessionStore, runId);
|
|
522
|
+
if (!restored) {
|
|
523
|
+
return {
|
|
524
|
+
runId,
|
|
525
|
+
completed: Promise.reject(new Error(`No durable state found for run "${runId}" — nothing to resume`)),
|
|
526
|
+
abort: () => { },
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const resolvedSessionId = restored.sessionId ?? sessionId;
|
|
530
|
+
this.stateMachine.runIdStack.push(runId);
|
|
531
|
+
try {
|
|
532
|
+
const parentRunId = this.stateMachine.runIdStack.at(-2);
|
|
533
|
+
const abort = this.stateMachine.createRun(runId, resolvedSessionId, parentRunId);
|
|
534
|
+
if (!abort) {
|
|
535
|
+
return {
|
|
536
|
+
runId,
|
|
537
|
+
completed: Promise.reject(new Error(`Session "${resolvedSessionId}" is busy — concurrent run rejected`)),
|
|
538
|
+
abort: () => { },
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
this.runSessionStates.set(runId, restored.sessionState);
|
|
542
|
+
if (resolvedSessionId)
|
|
543
|
+
this.runSessionIds.set(runId, resolvedSessionId);
|
|
544
|
+
const agent = agentOverride ?? this.currentAgent;
|
|
545
|
+
if (agent) {
|
|
546
|
+
this.modelCaller.resolveAgentModel(agent, runId);
|
|
547
|
+
}
|
|
548
|
+
// RV-40: keep the system head separate from the steering prompt.
|
|
549
|
+
const systemPrompt = this.buildSystemPrompt(agent);
|
|
550
|
+
const runSaga = new ToolSaga();
|
|
551
|
+
const promise = this.runLoop(prompt, runId, ctx, abort, resolvedSessionId, undefined, agentOverride, runSaga, true, systemPrompt)
|
|
552
|
+
.then(() => undefined)
|
|
553
|
+
.finally(() => {
|
|
396
554
|
this.stateMachine.runIdStack.pop();
|
|
397
555
|
this.runSessionStates.delete(runId);
|
|
398
556
|
});
|
|
@@ -407,6 +565,10 @@ export class AgentKernel {
|
|
|
407
565
|
throw err;
|
|
408
566
|
}
|
|
409
567
|
}
|
|
568
|
+
/** Reconstruct session IDs with non-terminal runs from the event store (for restart recovery). */
|
|
569
|
+
async getActiveSessionIds() {
|
|
570
|
+
return findActiveSessionIds(this.store);
|
|
571
|
+
}
|
|
410
572
|
/**
|
|
411
573
|
* Start a new agent run with full lifecycle management.
|
|
412
574
|
* Returns an AgentRunHandle with cancel, status, and event streaming.
|
|
@@ -445,16 +607,22 @@ export class AgentKernel {
|
|
|
445
607
|
if (!abort) {
|
|
446
608
|
throw new KernelError("session_busy", `Session "${sessionId}" is busy`);
|
|
447
609
|
}
|
|
610
|
+
// Wire the handle's cancel() into the controller the run loop listens on.
|
|
611
|
+
abortController.signal.addEventListener("abort", () => abort.abort(), { once: true });
|
|
612
|
+
// RV-7: if cancel() was called before this listener attached (sync call
|
|
613
|
+
// right after createRunHandle), the abort signal is already fired — replay
|
|
614
|
+
// it so the run converges on the single `cancelled` terminal outcome.
|
|
615
|
+
if (abortController.signal.aborted)
|
|
616
|
+
abort.abort();
|
|
448
617
|
this.runSessionStates.set(runId, this.sessionState);
|
|
618
|
+
if (sessionId)
|
|
619
|
+
this.runSessionIds.set(runId, sessionId);
|
|
449
620
|
const agent = agentOverride ?? this.currentAgent;
|
|
450
621
|
if (agent) {
|
|
451
622
|
this.modelCaller.resolveAgentModel(agent, runId);
|
|
452
623
|
}
|
|
453
|
-
//
|
|
454
|
-
const
|
|
455
|
-
const systemPrompt = agent?.systemPrompt;
|
|
456
|
-
const parts = [identity, systemPrompt, prompt].filter(Boolean);
|
|
457
|
-
const effectivePrompt = parts.join("\n\n");
|
|
624
|
+
// RV-40: keep the system head separate from the user prompt.
|
|
625
|
+
const systemPrompt = this.buildSystemPrompt(agent);
|
|
458
626
|
// Per-run saga (no instance-level mutation)
|
|
459
627
|
const runSaga = new ToolSaga();
|
|
460
628
|
// Emit agent.started event
|
|
@@ -463,16 +631,21 @@ export class AgentKernel {
|
|
|
463
631
|
timestamp: new Date().toISOString(),
|
|
464
632
|
runId,
|
|
465
633
|
prompt,
|
|
466
|
-
model: agent
|
|
634
|
+
...(agent?.profile?.model !== undefined ? { model: agent.profile.model } : {}),
|
|
467
635
|
};
|
|
468
636
|
eventHandlers.forEach(h => h(startEvent));
|
|
469
|
-
// Run the loop
|
|
470
|
-
await this.runLoop(
|
|
637
|
+
// Run the loop — it reports its terminal status through the return value.
|
|
638
|
+
const { totalSteps, status: runStatus } = await this.runLoop(prompt, runId, ctx, abort, sessionId, userContentParts, agentOverride, runSaga, undefined, systemPrompt);
|
|
471
639
|
completed = true;
|
|
472
640
|
result = {
|
|
473
641
|
runId,
|
|
474
|
-
|
|
475
|
-
|
|
642
|
+
// RV-7: runLoop reports its own terminal status; the local `cancelled`
|
|
643
|
+
// flag (handle.cancel()) agrees whenever the run was cancelled. Never
|
|
644
|
+
// let a cancelled run be coerced into "succeeded".
|
|
645
|
+
status: cancelled || runStatus === "cancelled"
|
|
646
|
+
? "cancelled"
|
|
647
|
+
: runStatus === "failed" ? "failed" : "succeeded",
|
|
648
|
+
totalSteps, // totalSteps from the run loop
|
|
476
649
|
};
|
|
477
650
|
// Emit agent.completed event
|
|
478
651
|
const completeEvent = {
|
|
@@ -498,7 +671,7 @@ export class AgentKernel {
|
|
|
498
671
|
timestamp: new Date().toISOString(),
|
|
499
672
|
runId,
|
|
500
673
|
error: result.error ?? "Unknown error",
|
|
501
|
-
|
|
674
|
+
...(err instanceof KernelError ? { code: err.code } : {}),
|
|
502
675
|
};
|
|
503
676
|
eventHandlers.forEach(h => h(errorEvent));
|
|
504
677
|
throw err;
|
|
@@ -555,21 +728,26 @@ export class AgentKernel {
|
|
|
555
728
|
*/
|
|
556
729
|
streamRun(prompt, ctx, sessionId, userContentParts, agentOverride) {
|
|
557
730
|
const handle = this.run(prompt, ctx, sessionId, userContentParts, agentOverride);
|
|
558
|
-
const
|
|
559
|
-
const events =
|
|
731
|
+
const eventBus = this.eventBus;
|
|
732
|
+
const events = eventBus ? streamRunEvents(eventBus, handle.runId, handle.completed) : (async function* () { })();
|
|
560
733
|
return {
|
|
561
734
|
runId: handle.runId,
|
|
562
|
-
events
|
|
735
|
+
events,
|
|
563
736
|
};
|
|
564
737
|
}
|
|
565
738
|
getRunSession(runId) {
|
|
566
739
|
return this.runSessionStates.get(runId) ?? this.sessionState;
|
|
567
740
|
}
|
|
741
|
+
/** Resolve the context of the most recently started run, if one is active. */
|
|
742
|
+
activeRunContext() {
|
|
743
|
+
const runId = this.stateMachine.runIdStack.at(-1);
|
|
744
|
+
return runId ? this.runContexts.get(runId) : undefined;
|
|
745
|
+
}
|
|
568
746
|
/** Run multiple sub-agents concurrently. Returns combined output. */
|
|
569
|
-
async runAgentsParallel(tasks, ctx, sessionId) {
|
|
747
|
+
async runAgentsParallel(tasks, ctx, sessionId, parentRunId, signal) {
|
|
570
748
|
if (!this.agentRegistry)
|
|
571
749
|
throw new KernelError("internal_error", "No agent registry configured");
|
|
572
|
-
return runSubAgentsParallel(this.subAgentDeps, tasks, ctx, sessionId);
|
|
750
|
+
return runSubAgentsParallel(this.subAgentDeps, tasks, ctx, sessionId, parentRunId, signal);
|
|
573
751
|
}
|
|
574
752
|
/** Start a run with error-safe wrapper — returns `{ ok, value }` or `{ ok: false, error }`. */
|
|
575
753
|
runSafe(prompt, ctx, sessionId, userContentParts) {
|
|
@@ -586,7 +764,12 @@ export class AgentKernel {
|
|
|
586
764
|
}
|
|
587
765
|
}
|
|
588
766
|
/** Get the tool saga for inspecting completed tool calls and triggering rollbacks. */
|
|
589
|
-
getSaga() {
|
|
767
|
+
getSaga(runId) {
|
|
768
|
+
if (runId) {
|
|
769
|
+
const runSaga = this.runSagas.get(runId);
|
|
770
|
+
if (runSaga)
|
|
771
|
+
return runSaga;
|
|
772
|
+
}
|
|
590
773
|
return this.saga;
|
|
591
774
|
}
|
|
592
775
|
/** Get the circuit breaker for inspecting trip state and threshold. */
|
|
@@ -663,6 +846,96 @@ export class AgentKernel {
|
|
|
663
846
|
/** Send a user input string to a waiting run (e.g., approval response). */
|
|
664
847
|
sendInput(runId, text) {
|
|
665
848
|
this.stateMachine.sendInput(runId, text);
|
|
849
|
+
this.queuePendingEntry(runId, text);
|
|
850
|
+
}
|
|
851
|
+
/** Queue an input into the runtime queue and (when a session is attached) start persisting it. */
|
|
852
|
+
queuePendingEntry(runId, text) {
|
|
853
|
+
const sessionId = this.runSessionIds.get(runId);
|
|
854
|
+
const list = this.runPendingEntries.get(runId) ?? [];
|
|
855
|
+
let persisted;
|
|
856
|
+
if (this.sessionStore && sessionId) {
|
|
857
|
+
persisted = this.persistPendingInput(sessionId, text);
|
|
858
|
+
}
|
|
859
|
+
list.push({ text, ...(persisted !== undefined ? { persisted } : {}) });
|
|
860
|
+
this.runPendingEntries.set(runId, list);
|
|
861
|
+
}
|
|
862
|
+
/** Serialize per-session writes so sequential admissions get sequential seqs (RV-21). */
|
|
863
|
+
persistPendingInput(sessionId, text) {
|
|
864
|
+
const prev = this.inputWriteChains.get(sessionId) ?? Promise.resolve();
|
|
865
|
+
const write = prev.catch(() => { }).then(async () => {
|
|
866
|
+
let next = this.nextAdmittedSeq.get(sessionId);
|
|
867
|
+
if (next === undefined) {
|
|
868
|
+
next = (await this.maxAdmittedSeq(sessionId)) + 1;
|
|
869
|
+
}
|
|
870
|
+
const admittedSeq = next;
|
|
871
|
+
this.nextAdmittedSeq.set(sessionId, admittedSeq + 1);
|
|
872
|
+
const msg = await this.sessionStore.addMessage(sessionId, "user", text, undefined, undefined, undefined, undefined, admittedSeq);
|
|
873
|
+
return { sessionId, messageId: msg.id, admittedSeq };
|
|
874
|
+
});
|
|
875
|
+
this.inputWriteChains.set(sessionId, write);
|
|
876
|
+
return write;
|
|
877
|
+
}
|
|
878
|
+
async maxAdmittedSeq(sessionId) {
|
|
879
|
+
if (typeof this.sessionStore?.listMessages !== "function")
|
|
880
|
+
return 0;
|
|
881
|
+
const messages = await this.sessionStore.listMessages(sessionId);
|
|
882
|
+
let max = 0;
|
|
883
|
+
for (const m of messages) {
|
|
884
|
+
if (typeof m.admittedSeq === "number" && m.admittedSeq > max)
|
|
885
|
+
max = m.admittedSeq;
|
|
886
|
+
}
|
|
887
|
+
return max;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Re-queue inputs admitted but not yet promoted (e.g. from a previous process
|
|
891
|
+
* that crashed mid-run) in admission order, exactly once (RV-21).
|
|
892
|
+
*/
|
|
893
|
+
async hydratePendingInputs(runId, sessionId) {
|
|
894
|
+
if (!this.sessionStore || !sessionId)
|
|
895
|
+
return;
|
|
896
|
+
if (typeof this.sessionStore.listMessages !== "function")
|
|
897
|
+
return;
|
|
898
|
+
const messages = await this.sessionStore.listMessages(sessionId);
|
|
899
|
+
let max = 0;
|
|
900
|
+
// Seed the allocator from the max admittedSeq across ALL history so live
|
|
901
|
+
// inputs continue past persisted seqs even when nothing is pending.
|
|
902
|
+
for (const m of messages) {
|
|
903
|
+
if (typeof m.admittedSeq === "number" && m.admittedSeq > max)
|
|
904
|
+
max = m.admittedSeq;
|
|
905
|
+
}
|
|
906
|
+
const pending = messages
|
|
907
|
+
.filter((m) => typeof m.admittedSeq === "number" && (m.promotedSeq === undefined || m.promotedSeq < m.admittedSeq))
|
|
908
|
+
.sort((a, b) => (a.admittedSeq ?? 0) - (b.admittedSeq ?? 0));
|
|
909
|
+
const list = this.runPendingEntries.get(runId) ?? [];
|
|
910
|
+
for (const m of pending) {
|
|
911
|
+
this.stateMachine.sendInput(runId, m.content);
|
|
912
|
+
list.push({ text: m.content, persisted: Promise.resolve({ sessionId, messageId: m.id, admittedSeq: m.admittedSeq }) });
|
|
913
|
+
}
|
|
914
|
+
if (pending.length > 0 || list.length > 0) {
|
|
915
|
+
this.runPendingEntries.set(runId, list);
|
|
916
|
+
}
|
|
917
|
+
// Never regress the allocator under parallel restarts / in-flight writes.
|
|
918
|
+
this.nextAdmittedSeq.set(sessionId, Math.max(max + 1, this.nextAdmittedSeq.get(sessionId) ?? 0));
|
|
919
|
+
}
|
|
920
|
+
/** Mark the given drained inputs as promoted (best-effort, at-least-once on crash). */
|
|
921
|
+
async markInputsPromoted(runId, texts) {
|
|
922
|
+
const entries = this.runPendingEntries.get(runId);
|
|
923
|
+
if (!entries || entries.length === 0)
|
|
924
|
+
return;
|
|
925
|
+
const taken = entries.splice(0, texts.length);
|
|
926
|
+
if (this.sessionStore?.updateMessage) {
|
|
927
|
+
for (const entry of taken) {
|
|
928
|
+
if (!entry.persisted)
|
|
929
|
+
continue;
|
|
930
|
+
try {
|
|
931
|
+
const p = await entry.persisted;
|
|
932
|
+
await this.sessionStore.updateMessage(p.sessionId, p.messageId, { promotedSeq: p.admittedSeq });
|
|
933
|
+
}
|
|
934
|
+
catch {
|
|
935
|
+
// Best-effort: leave un-promoted so a later restart re-delivers at-least-once.
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
666
939
|
}
|
|
667
940
|
/** Abort all currently running sessions. */
|
|
668
941
|
cancelCurrentRun() {
|
|
@@ -681,7 +954,7 @@ export class AgentKernel {
|
|
|
681
954
|
this.cancelCurrentRun();
|
|
682
955
|
return this.lifecycleManager.shutdown();
|
|
683
956
|
}
|
|
684
|
-
async runLoop(prompt, runId, ctx, runAbort, sessionId, userContentParts, agentOverride, runSaga) {
|
|
957
|
+
async runLoop(prompt, runId, ctx, runAbort, sessionId, userContentParts, agentOverride, runSaga, resume, systemPrompt) {
|
|
685
958
|
const runModel = this.modelCaller.getActiveModel(runId);
|
|
686
959
|
const runSessionState = this.getRunSession(runId);
|
|
687
960
|
const currentAgent = agentOverride ?? this.currentAgent;
|
|
@@ -693,15 +966,24 @@ export class AgentKernel {
|
|
|
693
966
|
judgeModel = this.modelCaller.resolveAgentModel(evaluator);
|
|
694
967
|
}
|
|
695
968
|
const runSagaInstance = runSaga ?? new ToolSaga();
|
|
696
|
-
|
|
697
|
-
|
|
969
|
+
// Per-run context — the active agent, sub-agent depth/chain, saga and tool
|
|
970
|
+
// cache all live here so parallel runs never clobber each other's state.
|
|
971
|
+
const runContext = createRunContext(runId, currentAgent, runSagaInstance);
|
|
972
|
+
this.runContexts.set(runId, runContext);
|
|
973
|
+
// Point the run's recorder at this run's saga so that
|
|
974
|
+
// record()/registerCompensation()/rollbackStep() operate on the same instance.
|
|
975
|
+
this.runSagas.set(runId, runSagaInstance);
|
|
976
|
+
// Per-run circuit breaker — parallel runs must not share breaker state, so
|
|
977
|
+
// each run retries/trips independently. The instance breaker stays as the
|
|
978
|
+
// default config (exposed via getCircuitBreaker()) for new runs.
|
|
979
|
+
const runCircuitBreaker = new CircuitBreaker(this.circuitBreaker.getOptions());
|
|
698
980
|
const orchestratorDeps = {
|
|
699
981
|
modelCaller: this.modelCaller,
|
|
700
982
|
permissionGate: this.permissionGate,
|
|
701
983
|
stepExecutor: this.stepExecutor,
|
|
702
984
|
saga: runSagaInstance,
|
|
703
985
|
store: this.store,
|
|
704
|
-
circuitBreaker:
|
|
986
|
+
circuitBreaker: runCircuitBreaker,
|
|
705
987
|
stateMachine: this.stateMachine,
|
|
706
988
|
...(this.pluginManager ? { pluginManager: this.pluginManager } : {}),
|
|
707
989
|
...(this.systemContext ? { systemContext: this.systemContext } : {}),
|
|
@@ -712,35 +994,105 @@ export class AgentKernel {
|
|
|
712
994
|
maxTokens: this.maxTokens,
|
|
713
995
|
thinkingBudget: this.thinkingBudget,
|
|
714
996
|
stepTimeout: this.stepTimeout,
|
|
715
|
-
compactionThreshold: this.compactionThreshold,
|
|
997
|
+
...(this.compactionThreshold !== undefined ? { compactionThreshold: this.compactionThreshold } : {}),
|
|
716
998
|
...(this.termination ? { termination: this.termination } : {}),
|
|
717
999
|
...(judgeModel ? { judgeModel } : {}),
|
|
718
1000
|
...(currentAgent ? { currentAgent } : {}),
|
|
719
1001
|
addSessionMessage: (sid, role, content, extra) => this.addSessionMessage(sid, role, content, extra),
|
|
1002
|
+
beforeRun: async () => {
|
|
1003
|
+
const parentRunId = this.stateMachine.runIdStack.at(-2);
|
|
1004
|
+
if (parentRunId)
|
|
1005
|
+
return;
|
|
1006
|
+
await this.hydratePendingInputs(runId, sessionId);
|
|
1007
|
+
},
|
|
1008
|
+
onInputsDrained: (rid, texts) => this.markInputsPromoted(rid, texts),
|
|
720
1009
|
};
|
|
721
1010
|
try {
|
|
722
|
-
await executeRunLoop(orchestratorDeps, {
|
|
1011
|
+
const result = await executeRunLoop(orchestratorDeps, {
|
|
723
1012
|
prompt, runId, ctx, runAbort,
|
|
724
1013
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
725
1014
|
...(userContentParts !== undefined ? { userContentParts } : {}),
|
|
1015
|
+
...(resume !== undefined ? { resume } : {}),
|
|
1016
|
+
...(systemPrompt !== undefined ? { systemPrompt } : {}),
|
|
726
1017
|
runModel,
|
|
727
1018
|
...(runSessionState !== undefined ? { runSessionState } : {}),
|
|
728
1019
|
addSessionMessage: (sid, role, content, extra) => this.addSessionMessage(sid, role, content, extra),
|
|
729
1020
|
emitEvent: (event, persist) => this.emitEvent(event, persist),
|
|
730
1021
|
setState: (rid, state) => this.stateMachine.setState(rid, state),
|
|
731
|
-
|
|
732
|
-
emitFail: (rid, c, reason, steps, sid, totalIn, totalOut, dur) => this.emitFail(rid, c, reason, steps, sid, totalIn, totalOut, dur),
|
|
1022
|
+
emitCompleted: (event, sid, rid, totalIn, totalOut, status) => this.emitCompleted(event, sid, rid, totalIn, totalOut, status),
|
|
1023
|
+
emitFail: (rid, c, reason, steps, sid, totalIn, totalOut, dur, cancelled) => this.emitFail(rid, c, reason, steps, sid, totalIn, totalOut, dur, cancelled),
|
|
733
1024
|
});
|
|
1025
|
+
return result;
|
|
734
1026
|
}
|
|
735
1027
|
finally {
|
|
736
|
-
this.
|
|
1028
|
+
// Drop this run's context and saga — parallel runs each own their own.
|
|
1029
|
+
this.runContexts.delete(runId);
|
|
1030
|
+
this.runSagas.delete(runId);
|
|
1031
|
+
this.runSessionIds.delete(runId);
|
|
1032
|
+
this.runPendingEntries.delete(runId);
|
|
737
1033
|
}
|
|
738
1034
|
}
|
|
739
|
-
|
|
740
|
-
return
|
|
1035
|
+
emitCompleted(event, sid, rid, totalIn, totalOut, status) {
|
|
1036
|
+
return sessionEmitCompleted(this.sessionDeps, event, sid, rid, totalIn, totalOut, status);
|
|
741
1037
|
}
|
|
742
|
-
emitFail(rid, c, reason, steps, sid, totalIn = 0, totalOut = 0, dur) {
|
|
743
|
-
return sessionEmitFail(this.sessionDeps, rid, c, reason, steps, sid, totalIn, totalOut, dur);
|
|
1038
|
+
emitFail(rid, c, reason, steps, sid, totalIn = 0, totalOut = 0, dur, cancelled = false) {
|
|
1039
|
+
return sessionEmitFail(this.sessionDeps, rid, c, reason, steps, sid, totalIn, totalOut, dur, cancelled);
|
|
744
1040
|
}
|
|
745
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Stream the events of a single run from the event bus, filtered by run ID.
|
|
1044
|
+
* Subscribes eagerly so no event published before the first `next()` is missed.
|
|
1045
|
+
* Terminates when the run completes (or fails).
|
|
1046
|
+
*/
|
|
1047
|
+
function streamRunEvents(eventBus, runId, completed) {
|
|
1048
|
+
const queue = [];
|
|
1049
|
+
let notify = null;
|
|
1050
|
+
let done = false;
|
|
1051
|
+
const wake = () => {
|
|
1052
|
+
const r = notify;
|
|
1053
|
+
notify = null;
|
|
1054
|
+
if (r)
|
|
1055
|
+
r();
|
|
1056
|
+
};
|
|
1057
|
+
const unsubscribe = eventBus.subscribeAll((event) => {
|
|
1058
|
+
if (event.aggregateId !== runId)
|
|
1059
|
+
return;
|
|
1060
|
+
queue.push(event);
|
|
1061
|
+
wake();
|
|
1062
|
+
});
|
|
1063
|
+
completed.then(wake, wake);
|
|
1064
|
+
return {
|
|
1065
|
+
[Symbol.asyncIterator]() {
|
|
1066
|
+
const self = this;
|
|
1067
|
+
return {
|
|
1068
|
+
async next() {
|
|
1069
|
+
while (!done) {
|
|
1070
|
+
if (queue.length > 0) {
|
|
1071
|
+
return { done: false, value: queue.shift() };
|
|
1072
|
+
}
|
|
1073
|
+
const stopped = await Promise.race([
|
|
1074
|
+
new Promise((resolve) => { notify = () => resolve(false); }),
|
|
1075
|
+
completed.then(() => true, () => true),
|
|
1076
|
+
]);
|
|
1077
|
+
if (stopped)
|
|
1078
|
+
done = true;
|
|
1079
|
+
}
|
|
1080
|
+
unsubscribe();
|
|
1081
|
+
// Drain any events that arrived after the run completed
|
|
1082
|
+
if (queue.length > 0) {
|
|
1083
|
+
return { done: false, value: queue.shift() };
|
|
1084
|
+
}
|
|
1085
|
+
return { done: true, value: undefined };
|
|
1086
|
+
},
|
|
1087
|
+
return() {
|
|
1088
|
+
unsubscribe();
|
|
1089
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
1090
|
+
},
|
|
1091
|
+
[Symbol.asyncIterator]() {
|
|
1092
|
+
return self;
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
746
1098
|
//# sourceMappingURL=kernel.js.map
|