@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.
Files changed (165) hide show
  1. package/dist/agent/agent-factory.d.ts +11 -0
  2. package/dist/agent/agent-factory.d.ts.map +1 -1
  3. package/dist/agent/agent-factory.js +11 -2
  4. package/dist/agent/agent-factory.js.map +1 -1
  5. package/dist/agent/agent-registry.d.ts +3 -0
  6. package/dist/agent/agent-registry.d.ts.map +1 -1
  7. package/dist/agent/agent-registry.js.map +1 -1
  8. package/dist/agent/create-agent-tool.d.ts +13 -13
  9. package/dist/agent/create-agent-tool.d.ts.map +1 -1
  10. package/dist/agent/delegate-batch-tool.d.ts.map +1 -1
  11. package/dist/agent/delegate-batch-tool.js +1 -1
  12. package/dist/agent/delegate-batch-tool.js.map +1 -1
  13. package/dist/agent/delegate-tool.js +1 -1
  14. package/dist/agent/delegate-tool.js.map +1 -1
  15. package/dist/agent/execution-engine.d.ts +1 -1
  16. package/dist/agent/execution-engine.d.ts.map +1 -1
  17. package/dist/agent/spawn-agent-tool.d.ts +10 -10
  18. package/dist/agent/spawn-agent-tool.d.ts.map +1 -1
  19. package/dist/event-bus/bridge.d.ts +2 -3
  20. package/dist/event-bus/bridge.d.ts.map +1 -1
  21. package/dist/event-bus/bridge.js +12 -5
  22. package/dist/event-bus/bridge.js.map +1 -1
  23. package/dist/fakes/fake-approval-store.d.ts +2 -2
  24. package/dist/fakes/fake-approval-store.d.ts.map +1 -1
  25. package/dist/fakes/fake-approval-store.js +28 -6
  26. package/dist/fakes/fake-approval-store.js.map +1 -1
  27. package/dist/fakes/fake-model.d.ts.map +1 -1
  28. package/dist/fakes/fake-model.js +3 -2
  29. package/dist/fakes/fake-model.js.map +1 -1
  30. package/dist/fakes/fake-store.d.ts +4 -1
  31. package/dist/fakes/fake-store.d.ts.map +1 -1
  32. package/dist/fakes/fake-store.js +33 -0
  33. package/dist/fakes/fake-store.js.map +1 -1
  34. package/dist/index.d.ts +20 -14
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +12 -4
  37. package/dist/index.js.map +1 -1
  38. package/dist/kernel/approval-handler.d.ts +1 -1
  39. package/dist/kernel/approval-handler.d.ts.map +1 -1
  40. package/dist/kernel/approval-handler.js +5 -1
  41. package/dist/kernel/approval-handler.js.map +1 -1
  42. package/dist/kernel/circuit-breaker.d.ts.map +1 -1
  43. package/dist/kernel/circuit-breaker.js +4 -1
  44. package/dist/kernel/circuit-breaker.js.map +1 -1
  45. package/dist/kernel/kernel-error.d.ts +1 -2
  46. package/dist/kernel/kernel-error.d.ts.map +1 -1
  47. package/dist/kernel/kernel-error.js +2 -3
  48. package/dist/kernel/kernel-error.js.map +1 -1
  49. package/dist/kernel/kernel-session.d.ts +13 -5
  50. package/dist/kernel/kernel-session.d.ts.map +1 -1
  51. package/dist/kernel/kernel-session.js +71 -13
  52. package/dist/kernel/kernel-session.js.map +1 -1
  53. package/dist/kernel/kernel-types.d.ts +6 -8
  54. package/dist/kernel/kernel-types.d.ts.map +1 -1
  55. package/dist/kernel/kernel-types.js.map +1 -1
  56. package/dist/kernel/kernel.d.ts +60 -16
  57. package/dist/kernel/kernel.d.ts.map +1 -1
  58. package/dist/kernel/kernel.js +428 -76
  59. package/dist/kernel/kernel.js.map +1 -1
  60. package/dist/kernel/model-caller.d.ts +28 -2
  61. package/dist/kernel/model-caller.d.ts.map +1 -1
  62. package/dist/kernel/model-caller.js +47 -12
  63. package/dist/kernel/model-caller.js.map +1 -1
  64. package/dist/kernel/permission-gate.d.ts +6 -4
  65. package/dist/kernel/permission-gate.d.ts.map +1 -1
  66. package/dist/kernel/permission-gate.js +16 -6
  67. package/dist/kernel/permission-gate.js.map +1 -1
  68. package/dist/kernel/run-context.d.ts +26 -0
  69. package/dist/kernel/run-context.d.ts.map +1 -0
  70. package/dist/kernel/run-context.js +13 -0
  71. package/dist/kernel/run-context.js.map +1 -0
  72. package/dist/kernel/run-loop.d.ts +35 -14
  73. package/dist/kernel/run-loop.d.ts.map +1 -1
  74. package/dist/kernel/run-loop.js +295 -87
  75. package/dist/kernel/run-loop.js.map +1 -1
  76. package/dist/kernel/self-correction.d.ts +2 -2
  77. package/dist/kernel/self-correction.d.ts.map +1 -1
  78. package/dist/kernel/self-correction.js +3 -2
  79. package/dist/kernel/self-correction.js.map +1 -1
  80. package/dist/kernel/state-machine.d.ts +2 -0
  81. package/dist/kernel/state-machine.d.ts.map +1 -1
  82. package/dist/kernel/state-machine.js +2 -0
  83. package/dist/kernel/state-machine.js.map +1 -1
  84. package/dist/kernel/step-executor.d.ts +11 -3
  85. package/dist/kernel/step-executor.d.ts.map +1 -1
  86. package/dist/kernel/step-executor.js +54 -24
  87. package/dist/kernel/step-executor.js.map +1 -1
  88. package/dist/kernel/sub-agent-runner.d.ts +10 -7
  89. package/dist/kernel/sub-agent-runner.d.ts.map +1 -1
  90. package/dist/kernel/sub-agent-runner.js +105 -31
  91. package/dist/kernel/sub-agent-runner.js.map +1 -1
  92. package/dist/kernel/tool-context-builder.d.ts +7 -2
  93. package/dist/kernel/tool-context-builder.d.ts.map +1 -1
  94. package/dist/kernel/tool-context-builder.js +23 -6
  95. package/dist/kernel/tool-context-builder.js.map +1 -1
  96. package/dist/kernel/tool-error-router.d.ts +1 -1
  97. package/dist/kernel/tool-error-router.d.ts.map +1 -1
  98. package/dist/kernel/tool-error-router.js +4 -3
  99. package/dist/kernel/tool-error-router.js.map +1 -1
  100. package/dist/logger.d.ts +5 -0
  101. package/dist/logger.d.ts.map +1 -1
  102. package/dist/logger.js +3 -0
  103. package/dist/logger.js.map +1 -1
  104. package/dist/model.d.ts +9 -2
  105. package/dist/model.d.ts.map +1 -1
  106. package/dist/model.js +18 -4
  107. package/dist/model.js.map +1 -1
  108. package/dist/plugin/manager.d.ts +3 -1
  109. package/dist/plugin/manager.d.ts.map +1 -1
  110. package/dist/plugin/manager.js +50 -3
  111. package/dist/plugin/manager.js.map +1 -1
  112. package/dist/plugin/plugin-file-loader.js +2 -2
  113. package/dist/plugin/plugin-file-loader.js.map +1 -1
  114. package/dist/plugin.d.ts +50 -4
  115. package/dist/plugin.d.ts.map +1 -1
  116. package/dist/plugin.js +14 -1
  117. package/dist/plugin.js.map +1 -1
  118. package/dist/session/null-store.d.ts +1 -0
  119. package/dist/session/null-store.d.ts.map +1 -1
  120. package/dist/session/null-store.js +7 -0
  121. package/dist/session/null-store.js.map +1 -1
  122. package/dist/session/run-coordinator.d.ts +9 -3
  123. package/dist/session/run-coordinator.d.ts.map +1 -1
  124. package/dist/session/run-coordinator.js +4 -0
  125. package/dist/session/run-coordinator.js.map +1 -1
  126. package/dist/skill/create-skill-tool.d.ts +5 -4
  127. package/dist/skill/create-skill-tool.d.ts.map +1 -1
  128. package/dist/skill/create-skill-tool.js +8 -5
  129. package/dist/skill/create-skill-tool.js.map +1 -1
  130. package/dist/skill/skill-def-registry.d.ts +6 -0
  131. package/dist/skill/skill-def-registry.d.ts.map +1 -1
  132. package/dist/skill/skill-def-registry.js +4 -0
  133. package/dist/skill/skill-def-registry.js.map +1 -1
  134. package/dist/tool/bridge.d.ts +7 -1
  135. package/dist/tool/bridge.d.ts.map +1 -1
  136. package/dist/tool/bridge.js +5 -0
  137. package/dist/tool/bridge.js.map +1 -1
  138. package/dist/tool/builtin-provider.d.ts +35 -0
  139. package/dist/tool/builtin-provider.d.ts.map +1 -0
  140. package/dist/tool/builtin-provider.js +75 -0
  141. package/dist/tool/builtin-provider.js.map +1 -0
  142. package/dist/tool/index.d.ts +21 -23
  143. package/dist/tool/index.d.ts.map +1 -1
  144. package/dist/tool/index.js +19 -18
  145. package/dist/tool/index.js.map +1 -1
  146. package/dist/tool/provider-helpers.d.ts.map +1 -1
  147. package/dist/tool/provider-helpers.js +3 -2
  148. package/dist/tool/provider-helpers.js.map +1 -1
  149. package/dist/tool/runtime.d.ts +23 -2
  150. package/dist/tool/runtime.d.ts.map +1 -1
  151. package/dist/tool/runtime.js +20 -16
  152. package/dist/tool/runtime.js.map +1 -1
  153. package/dist/tracer.d.ts +5 -0
  154. package/dist/tracer.d.ts.map +1 -1
  155. package/dist/tracer.js +4 -0
  156. package/dist/tracer.js.map +1 -1
  157. package/dist/workflow/agent-graph.js +3 -3
  158. package/dist/workflow/agent-graph.js.map +1 -1
  159. package/dist/workflow/workflow-helpers.js +2 -2
  160. package/dist/workflow/workflow-helpers.js.map +1 -1
  161. package/dist/workspace.d.ts +7 -0
  162. package/dist/workspace.d.ts.map +1 -1
  163. package/dist/workspace.js +4 -0
  164. package/dist/workspace.js.map +1 -1
  165. package/package.json +21 -11
@@ -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 "./kernel-utils.js";
5
- import { RunStateMachine } from "./run-state.js";
6
- import { PermissionGate } from "./permission-gate.js";
7
- import { ModelCaller } from "./model-caller.js";
8
- import { StepExecutor } from "./step-executor.js";
9
- import { ToolSaga } from "./tool-saga.js";
10
- import { CircuitBreaker } from "./circuit-breaker.js";
11
- import { KernelError } from "./kernel-error.js";
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, updateSessionOnComplete as sessionUpdateComplete, emitFail as sessionEmitFail, buildAgentIdentity, } from "./kernel-session.js";
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 "./circuit-breaker.js";
18
- export { KernelError } from "./kernel-error.js";
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
- autoApprovalEnabled: normalized.permissions?.autoApprovalEnabled,
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 ?? "Analyze the user's request and the conversation context. Think step by step about what needs to be done. Output your reasoning.";
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: normalized.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
- findTool: (name) => self.findTool(name),
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: { get value() { return self.currentAgent; }, set value(v) { self.currentAgent = v; } },
185
- currentDepthRef: { get value() { return self.currentDepth; }, set value(v) { self.currentDepth = v; } },
186
- agentChainRef: { get value() { return self.agentChain; }, set value(v) { self.agentChain = v; } },
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 agentId = this.currentAgent?.id;
235
- if (this.cachedTools && this.cachedToolsAgentId === agentId) {
236
- return this.cachedTools;
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 = this.currentAgent?.capabilities?.tools;
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 = this.currentAgent?.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 = this.currentAgent?.behaviourMode ?? "build";
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, this.currentAgent);
368
+ const perm = this.permissionGate.checkTool(t.id, t.risk, undefined, target);
286
369
  return perm.allowed || perm.needsApproval;
287
370
  });
288
- this.cachedTools = result;
289
- this.cachedToolsAgentId = agentId;
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
- // Build effective prompt with agent identity and system prompt
389
- const identity = buildAgentIdentity(agent);
390
- const systemPrompt = agent?.systemPrompt;
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(effectivePrompt, runId, ctx, abort, sessionId, userContentParts, agentOverride, runSaga).finally(() => {
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
- // Build effective prompt with agent identity and system prompt
454
- const identity = buildAgentIdentity(agent);
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?.profile?.model,
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(effectivePrompt, runId, ctx, abort, sessionId, userContentParts, agentOverride, runSaga);
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
- status: cancelled ? "cancelled" : "succeeded",
475
- totalSteps: 0, // TODO: Get from run loop
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
- code: err instanceof KernelError ? err.kernelCode : undefined,
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 eventDef = { type: "run.*", durable: { aggregate: "run" } };
559
- const events = this.eventBus?.stream(eventDef) ?? (async function* () { })();
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: 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
- const prevAgent = this.stepExecutor.getCurrentAgent();
697
- this.stepExecutor.setCurrentAgent(currentAgent);
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: this.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
- updateSessionOnComplete: (sid, rid, totalIn, totalOut, _steps, _status) => this.updateSessionOnComplete(sid, rid, totalIn, totalOut, _steps, _status),
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.stepExecutor.setCurrentAgent(prevAgent);
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
- updateSessionOnComplete(sid, rid, totalIn, totalOut, _steps, _status) {
740
- return sessionUpdateComplete(this.sessionDeps, sid, rid, totalIn, totalOut);
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