@agen-ai/agent-orchestration 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -1,8 +1,15 @@
1
1
  # @agen-ai/agent-orchestration
2
2
 
3
3
  Optional managed delegation for applications using the AgenAI coding-agent SDK, at coordinated
4
- version `0.4.0`. This package sits above
5
- `@agen-ai/agent-runtime`. Core runtime consumers do not depend on it.
4
+ unpublished source version `0.4.2` (published `0.4.1`). This package sits above
5
+ `@agen-ai/agent-runtime`. Core runtime consumers do not depend on it. Current source uses Agent
6
+ Protocol V12; published `0.4.1` uses V11 and the earlier `0.4.0` release uses V10. Managed store
7
+ contract is 4; the reference SQLite format remains 3; retained historical requests cannot reconstruct live reply authority.
8
+
9
+ External stores must implement the contract-4 transitions, including exact request retirement
10
+ while an entered answer retains its own receipt. Declaring version 4 alone is insufficient.
11
+ The unchanged SQLite format needs no format-3-to-4 migration. Keep every installed SDK package
12
+ at one coordinated version and follow the [0.4.2 migration guide](../../MIGRATING-TO-0.4.2.md).
6
13
 
7
14
  The current implementation provides bounded target catalogs, a pure developer/user policy resolver,
8
15
  managed task coordination, a volatile store and host-injected completion delivery. A separately
@@ -177,17 +184,30 @@ runs independently from the sole child output reader, so output continues while
177
184
  `resolveRequest` delivers through the retained turn execution and returns `delivered`, `rejected` with
178
185
  `requestState: "pending" | "unavailable"`, or `delivery_uncertain`. A delivered receipt confirms provider-owned
179
186
  acceptance of that exact canonical answer or form step; it does not claim native transport acknowledgment,
180
- turn completion or approved effect completion. Only a definitive pending rejection restores answerability;
187
+ turn completion or approved effect completion. Only a definitive pending rejection for a request that remains live restores answerability;
181
188
  uncertainty forbids replay. The receipt settles without waiting for a later output, request or terminal.
182
189
  An unavailable rejection removes only that exact request's eligibility; it does not complete the
183
190
  child or its parent. Request expiry, accepted interruption and terminality fence fresh answers.
184
191
  Local malformed, stale or pre-aborted answers reject before provider entry; entered provider
185
192
  exceptions and uncertain receipts retain the child output/cleanup owner and prohibit resending.
186
- Raw request answers and native diagnostics are not stored in observations.
193
+ Canonical `request.unavailable` retires the exact request and aborts its host presentation signal,
194
+ leaving the child run alive. A new request can be presented and answered while an already entered
195
+ answer to the retired request remains unsettled. That original attempt retains its actual receipt,
196
+ including a late `delivered` or `rejected`/`pending`; retirement never restores its eligibility or
197
+ transfers its receipt to the new request. Retirement creates no response operation. Exact duplicate
198
+ operation lookup still returns the original receipt under current host access checks.
199
+
200
+ Returning from `onRequest` does not retire its signal. The coordinator retains only each request's
201
+ historical identity and presentation controller until canonical retirement or run cleanup, bounded
202
+ by the validated runtime's session-generation request census. It retains actual presentation
203
+ callbacks independently even when they ignore their abort signal.
204
+ Terminal output can initiate native quiescence before joining outstanding answers; the original
205
+ reader, callbacks and controls all finish before host resources are released. Raw request answers
206
+ and native diagnostics are not stored in observations.
187
207
 
188
208
  ## Store and capacity contract
189
209
 
190
- The in-memory store is explicitly volatile. Store port contract version 3 provides owner fencing, immutable outcomes,
210
+ The in-memory store is explicitly volatile. Store port contract version 4 provides owner fencing, immutable outcomes,
191
211
  operation idempotency, bounded records and independent settlement/control capacity. The explicit
192
212
  `@agen-ai/agent-orchestration/store-adapter` entrypoint supplies a pure transition planner and closed
193
213
  row mutations for storage adapters. It does not provide a database, arbitrary transaction callback,
@@ -226,7 +246,12 @@ failure. V10 rejects a malformed `failed` terminal without its required error be
226
246
  An exception after observed dispatch remains `recovery_required` with `dispatch_uncertain`, and
227
247
  unresolved cleanup or control uncertainty still overrides an apparent definitive failure.
228
248
  An old store-port contract is rejected before ownership acquisition; persistence adapters must
229
- explicitly migrate their independent record format before presenting contract version 3.
249
+ explicitly implement contract 4 before presenting that version. Contract 4 requires request retirement
250
+ to change a waiting run to running and to preserve independently entered receipts without reviving
251
+ retired eligibility or replacing a newer request. The reference SQLite format stays 3: SQL, headers,
252
+ slots, record shapes and settled historical meanings are unchanged, and committed historical receipt
253
+ bytes remain intact. Formats 1 and 2 retain their explicit offline migration path; startup does not
254
+ rewrite them or recreate callbacks.
230
255
 
231
256
  ## Completion and restart
232
257
 
@@ -17,7 +17,7 @@ import { recoverRuns } from "./recovery.js";
17
17
  import { shutdown } from "./shutdown.js";
18
18
  async function createDelegationCoordinator(options) {
19
19
  const limits = parseDelegationLimits(options.limits);
20
- if (options.store.contractVersion !== 3 || delegationInputDigest(limits) !== delegationInputDigest(options.store.limits)) {
20
+ if (options.store.contractVersion !== 4 || delegationInputDigest(limits) !== delegationInputDigest(options.store.limits)) {
21
21
  failDelegation("store_failed", "The coordinator requires the exact store contract and limits.");
22
22
  }
23
23
  validateDeliverySetup(options);
@@ -1,3 +1,4 @@
1
+ import type { AgentRequestResolution } from "@agen-ai/agent-protocol";
1
2
  import { z } from "zod/v4";
2
3
  import type { DelegationLimits } from "../shared/limits.js";
3
4
  import type { DelegationSendTaskInput, DelegationSpawnTaskInput } from "./types.js";
@@ -6,5 +7,5 @@ export declare function parseSpawnTaskInput(input: unknown, limits: DelegationLi
6
7
  export declare function parseSendTaskInput(input: unknown, limits: DelegationLimits): DelegationSendTaskInput;
7
8
  export declare const parseOperationId: (input: unknown) => string;
8
9
  export declare const parseTaskId: (input: unknown) => string;
9
- export declare const parseRequestResolution: (input: unknown) => import("packages/agent-protocol/dist/index.js").AgentRequestResolution;
10
+ export declare const parseRequestResolution: (input: unknown) => AgentRequestResolution;
10
11
  //# sourceMappingURL=parse.d.ts.map
@@ -42,11 +42,14 @@ function cleanupRun(state, live) {
42
42
  live.request = void 0;
43
43
  }
44
44
  const reason = new DOMException("The managed run is closing.", "AbortError");
45
+ for (const presentation of live.requestPresentations.values()) presentation.abort.abort(reason);
46
+ live.requestPresentations.clear();
45
47
  live.controlAbort.abort(reason);
46
48
  const runtimeClosing = runtimeCleanup(state, live);
47
49
  live.lifetime.abort(reason);
48
50
  while (live.controls.size > 0) await Promise.allSettled(live.controls);
49
51
  if (!await runtimeClosing) return false;
52
+ await live.outputDrained;
50
53
  while (!live.resourcesReleased && live.resourceReleaseAttempts < 2) {
51
54
  live.resourceReleaseAttempts += 1;
52
55
  try {
@@ -141,7 +141,7 @@ async function continueRequest(state, input) {
141
141
  const { live, request } = pending;
142
142
  parseAgentRequestResolutionFor(request, resolution);
143
143
  const identity = { operationId, inputDigest: delegationInputDigest({ kind: "request_resolution", taskId: live.taskId, runId: live.runId, requestId: request.requestId, resolution }) };
144
- return serializeControl(live, async () => {
144
+ const admission = await serializeControl(live, async () => {
145
145
  const [{ task }, run] = await Promise.all([state.task(live.taskId), state.run(live.taskId, live.runId)]);
146
146
  state.options.host.assertResultAccess({ scope: live.scope, task });
147
147
  state.options.host.assertRequestAccess({ handle: input.handle, task, run, request });
@@ -151,59 +151,70 @@ async function continueRequest(state, input) {
151
151
  if (existing.operation.kind !== "request_resolution" || existing.operation.inputDigest !== identity.inputDigest || existing.operation.taskId !== live.taskId || existing.operation.runId !== live.runId) {
152
152
  failDelegation("operation_conflict", "The operation identity already has different input or ownership.");
153
153
  }
154
- return requestReceipt(live, operationId, existing.operation.disposition, existing.operation.requestState, true);
154
+ return { kind: "receipt", receipt: requestReceipt(live, operationId, existing.operation.disposition, existing.operation.requestState, true) };
155
155
  }
156
- if (live.request !== pending || !live.stream || live.finishing || !live.turn || !live.requestHistory || live.uncertain || live.cancellation || run.phase !== "waiting" || live.execution?.signal.aborted || live.lifetime.signal.aborted) {
156
+ if (live.request !== pending || pending.eligibility !== "waiting" || !live.stream || live.finishing || !live.turn || live.uncertain || live.cancellation || run.phase !== "waiting" || live.execution?.signal.aborted || live.lifetime.signal.aborted) {
157
157
  failDelegation("request_unavailable", "The exact waiting request no longer has a live executor.");
158
158
  }
159
- const history = live.requestHistory;
160
- await markRequest(state, live, "resolution_intended", { ...history, state: "resolution_intended" }, identity);
161
- live.request = void 0;
162
- let disposition;
163
- let requestState;
164
- let localFailure;
165
- let entered = false;
159
+ const history = pending.history;
160
+ pending.eligibility = "claimed";
166
161
  try {
167
- const turn = live.turn;
168
- const result = await boundedControl(state, live, (signal) => turn.resolveRequest({
169
- resolution,
170
- signal: AbortSignal.any([signal, live.lifetime.signal]),
171
- onProviderExecutionStarted() {
172
- state.options.host.assertRequestAccess({ handle: input.handle, task, run, request });
173
- live.execution?.assertCurrent();
174
- entered = true;
175
- }
176
- }));
177
- disposition = result.status === "delivery_uncertain" ? "uncertain" : result.status;
178
- if (result.status === "rejected") requestState = result.requestState;
162
+ await markRequest(state, live, "resolution_intended", { ...history, state: "resolution_intended" }, identity);
179
163
  } catch (error) {
180
- if (error instanceof AgentProviderDelegatedOperationError || entered && error instanceof DOMException && error.name === "AbortError") {
181
- disposition = "uncertain";
182
- } else {
183
- disposition = "rejected";
184
- requestState = live.lifetime.signal.aborted || live.cancellation ? "unavailable" : "pending";
185
- localFailure = { error };
186
- }
187
- }
188
- if (disposition === "uncertain") live.uncertain = "request_executor_lost";
189
- try {
190
- await state.mutate(
191
- live.taskId,
192
- live.runId,
193
- operationId,
194
- identity.inputDigest,
195
- (fence) => ({ ...fence, kind: "operation_receipt", disposition, at: now(), ...requestState ? { requestState } : {} })
196
- );
197
- } catch (error) {
198
- live.uncertain = "storage_failed";
199
- void finishRun(state, live, { kind: "recovery_required", reason: "storage_failed" }).catch(() => void 0);
164
+ if (!pending.signal.aborted && live.request === pending) pending.eligibility = "waiting";
200
165
  throw error;
201
166
  }
202
- if (requestState === "pending" && !live.lifetime.signal.aborted && !live.cancellation) live.request = pending;
203
- if (live.uncertain) void finishRun(state, live, { kind: "recovery_required", reason: live.uncertain }).catch(() => void 0);
204
- if (localFailure) throw localFailure.error;
205
- return requestReceipt(live, operationId, disposition, requestState);
167
+ live.requestAttempts.set(operationId, pending);
168
+ const turn = live.turn;
169
+ return { kind: "dispatch", invoke: async () => {
170
+ let disposition;
171
+ let requestState;
172
+ let localFailure;
173
+ let entered = false;
174
+ try {
175
+ const result = await boundedControl(state, live, (signal) => turn.resolveRequest({
176
+ resolution,
177
+ signal: AbortSignal.any([signal, live.lifetime.signal]),
178
+ onProviderExecutionStarted() {
179
+ state.options.host.assertRequestAccess({ handle: input.handle, task, run, request });
180
+ live.execution?.assertCurrent();
181
+ entered = true;
182
+ }
183
+ }));
184
+ disposition = result.status === "delivery_uncertain" ? "uncertain" : result.status;
185
+ if (result.status === "rejected") requestState = result.requestState;
186
+ } catch (error) {
187
+ if (error instanceof AgentProviderDelegatedOperationError || entered && error instanceof DOMException && error.name === "AbortError") {
188
+ disposition = "uncertain";
189
+ } else {
190
+ disposition = "rejected";
191
+ requestState = live.lifetime.signal.aborted || live.cancellation ? "unavailable" : "pending";
192
+ localFailure = { error };
193
+ }
194
+ }
195
+ if (disposition === "uncertain") live.uncertain = "request_executor_lost";
196
+ try {
197
+ await state.mutate(
198
+ live.taskId,
199
+ live.runId,
200
+ operationId,
201
+ identity.inputDigest,
202
+ (fence) => ({ ...fence, kind: "operation_receipt", disposition, at: now(), ...requestState ? { requestState } : {} })
203
+ );
204
+ } catch (error) {
205
+ live.uncertain = "storage_failed";
206
+ void finishRun(state, live, { kind: "recovery_required", reason: "storage_failed" }).catch(() => void 0);
207
+ throw error;
208
+ }
209
+ if (pending.eligibility !== "retired") {
210
+ pending.eligibility = requestState === "pending" && live.request === pending && !live.lifetime.signal.aborted && !live.cancellation ? "waiting" : "retired";
211
+ }
212
+ if (live.uncertain) void finishRun(state, live, { kind: "recovery_required", reason: live.uncertain }).catch(() => void 0);
213
+ if (localFailure) throw localFailure.error;
214
+ return requestReceipt(live, operationId, disposition, requestState);
215
+ } };
206
216
  });
217
+ return admission.kind === "receipt" ? admission.receipt : admission.invoke();
207
218
  }
208
219
  function resolveLiveRequest(state, input) {
209
220
  const pending = state.requests.get(input.handle);
@@ -217,10 +228,11 @@ function resolveLiveRequest(state, input) {
217
228
  if (prior) return state.track(prior.promise.then((receipt) => ({ ...receipt, duplicate: true })), pending.live);
218
229
  const work = state.track(continueRequest(state, input), pending.live);
219
230
  pending.live.pendingOperations.set(operationId, { digest, promise: work });
220
- pending.live.requestResolution = work;
231
+ pending.resolution ??= work;
221
232
  void work.finally(() => {
222
233
  pending.live.pendingOperations.delete(operationId);
223
- if (pending.live.requestResolution === work) pending.live.requestResolution = void 0;
234
+ pending.live.requestAttempts.delete(operationId);
235
+ if (pending.resolution === work) pending.resolution = void 0;
224
236
  }).catch(() => void 0);
225
237
  return work;
226
238
  }
@@ -68,6 +68,8 @@ async function pump(state) {
68
68
  sessionId: parseAgentSessionId(createDelegationId()),
69
69
  turnId: parseAgentTurnId(createDelegationId()),
70
70
  controls: /* @__PURE__ */ new Set(),
71
+ requestAttempts: /* @__PURE__ */ new Map(),
72
+ requestPresentations: /* @__PURE__ */ new Map(),
71
73
  pendingOperations: /* @__PURE__ */ new Map(),
72
74
  observedStart: false,
73
75
  recordedStart: false,
@@ -4,7 +4,7 @@ import type { DelegationTarget } from "../catalog/types.js";
4
4
  import type { CreateDelegationCoordinatorOptions } from "../coordinator/types.js";
5
5
  import type { DelegationChildResources, DelegationContinuation, DelegationExecutionLease, DelegationHostRequestHandle } from "../host/types.js";
6
6
  import type { DelegationCommitReceipt, DelegationMutationFence, DelegationStoreCommand } from "../store/types.js";
7
- import type { DelegationOwnerToken, DelegationParentScope, DelegationPolicyCeiling, DelegationResultReference, DelegationRunOutcome, DelegationRunRecord, DelegationTaskRecord, DelegationTaskView } from "../tasks/types.js";
7
+ import type { DelegationHistoricalRequest, DelegationOwnerToken, DelegationParentScope, DelegationPolicyCeiling, DelegationResultReference, DelegationRunOutcome, DelegationRunRecord, DelegationTaskRecord, DelegationTaskView } from "../tasks/types.js";
8
8
  export interface ParentBinding {
9
9
  readonly scope: DelegationParentScope;
10
10
  readonly ancestor?: Readonly<{
@@ -18,6 +18,14 @@ export interface LiveRequest {
18
18
  readonly handle: DelegationHostRequestHandle;
19
19
  readonly live: LiveRun;
20
20
  readonly request: AgentRequest;
21
+ readonly history: DelegationHistoricalRequest;
22
+ readonly signal: AbortSignal;
23
+ eligibility: "waiting" | "claimed" | "retired";
24
+ resolution?: Promise<unknown>;
25
+ }
26
+ export interface RequestPresentation {
27
+ readonly history: DelegationHistoricalRequest;
28
+ readonly abort: AbortController;
21
29
  }
22
30
  export interface LiveRun {
23
31
  readonly taskId: string;
@@ -30,6 +38,8 @@ export interface LiveRun {
30
38
  readonly sessionId: AgentSessionId;
31
39
  readonly turnId: AgentTurnId;
32
40
  readonly controls: Set<Promise<unknown>>;
41
+ readonly requestAttempts: Map<string, LiveRequest>;
42
+ readonly requestPresentations: Map<AgentRequest["requestId"], RequestPresentation>;
33
43
  readonly pendingOperations: Map<string, Readonly<{
34
44
  digest: string;
35
45
  promise: Promise<unknown>;
@@ -40,8 +50,8 @@ export interface LiveRun {
40
50
  turn?: AgentProviderTurnExecution;
41
51
  rejectedOpening?: AgentProviderSessionCleanupError;
42
52
  stream?: Promise<void>;
53
+ outputDrained?: Promise<void>;
43
54
  request?: LiveRequest;
44
- requestResolution?: Promise<unknown>;
45
55
  observedStart: boolean;
46
56
  recordedStart: boolean;
47
57
  controlTail: Promise<void>;
@@ -62,7 +72,6 @@ export interface LiveRun {
62
72
  cancellation?: Promise<unknown>;
63
73
  finishing?: Promise<void>;
64
74
  ready?: Promise<void>;
65
- requestHistory?: DelegationRunRecord["pendingRequest"];
66
75
  schedule?: () => void;
67
76
  deadline?: ReturnType<typeof setTimeout>;
68
77
  removeRevocation?: () => void;
@@ -4,8 +4,6 @@ class CoordinatorState {
4
4
  this.options = options;
5
5
  this.owner = owner;
6
6
  }
7
- options;
8
- owner;
9
7
  live = /* @__PURE__ */ new Map();
10
8
  continuations = /* @__PURE__ */ new Map();
11
9
  requests = /* @__PURE__ */ new WeakMap();
@@ -25,7 +25,7 @@ async function markRequest(state, live, stateChange, request, identity = operati
25
25
  live.runId,
26
26
  identity.operationId,
27
27
  identity.inputDigest,
28
- (fence) => ({ ...fence, kind: "request_state", state: stateChange, request, at: now() })
28
+ (fence, _task, run) => stateChange === "unavailable" && (run.pendingRequest?.requestId !== request.requestId || run.pendingRequest.requestKind !== request.requestKind) ? void 0 : { ...fence, kind: "request_state", state: stateChange, request, at: now() }
29
29
  );
30
30
  }
31
31
  async function routeRequest(state, live, request) {
@@ -37,19 +37,28 @@ async function routeRequest(state, live, request) {
37
37
  state: "waiting"
38
38
  };
39
39
  await markRequest(state, live, "opened", historical);
40
- live.requestHistory = historical;
41
40
  const handle = Object.freeze({});
42
- const pending = { handle, live, request };
41
+ const presentationAbort = new AbortController();
42
+ const pending = {
43
+ handle,
44
+ live,
45
+ request,
46
+ history: historical,
47
+ signal: AbortSignal.any([presentationAbort.signal, live.lifetime.signal]),
48
+ eligibility: "waiting"
49
+ };
43
50
  live.request = pending;
44
51
  state.requests.set(handle, pending);
52
+ live.requestPresentations.set(request.requestId, { history: historical, abort: presentationAbort });
45
53
  await observe(state, live, { kind: "request", request: historical });
46
54
  const callback = Promise.resolve().then(async () => {
47
55
  const [{ task }, run] = await Promise.all([state.task(live.taskId), state.run(live.taskId, live.runId)]);
48
- if (live.lifetime.signal.aborted || live.request !== pending) return;
49
- await state.options.host.onRequest({ handle, task, run, request, signal: live.lifetime.signal });
56
+ if (pending.signal.aborted || live.request !== pending) return;
57
+ await state.options.host.onRequest({ handle, task, run, request, signal: pending.signal });
50
58
  });
51
59
  state.track(callback, live);
52
- void callback.catch(() => {
60
+ void callback.catch((error) => {
61
+ if (pending.signal.aborted && (error === pending.signal.reason || error instanceof DOMException && error.name === "AbortError")) return;
53
62
  live.uncertain = "request_executor_lost";
54
63
  void finishRun(state, live, { kind: "recovery_required", reason: "request_executor_lost" }).catch(() => void 0);
55
64
  });
@@ -72,35 +81,60 @@ async function reduceOutput(state, live, output) {
72
81
  await observe(state, live, { kind: "text", text: event.payload.delta });
73
82
  } else if (event.type === "turn.started") await observe(state, live, { kind: "state", phase: "running" });
74
83
  else if (event.type === "request.opened") return { request: event.payload.request };
75
- else if (event.type === "turn.completed") {
84
+ else if (event.type === "request.unavailable") {
85
+ const presentation = live.requestPresentations.get(event.payload.requestId);
86
+ if (presentation) {
87
+ const pending = live.request?.request.requestId === event.payload.requestId ? live.request : [...live.requestAttempts.values()].find((request) => request.request.requestId === event.payload.requestId);
88
+ if (pending) pending.eligibility = "retired";
89
+ presentation.abort.abort(new DOMException("The managed request is no longer available.", "AbortError"));
90
+ live.requestPresentations.delete(event.payload.requestId);
91
+ const historical = { ...presentation.history, state: "unavailable" };
92
+ await markRequest(state, live, "unavailable", historical);
93
+ await observe(state, live, { kind: "request", request: historical });
94
+ }
95
+ } else if (event.type === "turn.completed") {
76
96
  return { terminal: event.payload.outcome === "completed" ? { kind: "succeeded", result: { text: live.summary, truncated: live.truncated, references: [...live.references] } } : event.payload.outcome === "canceled" ? { kind: "canceled", confirmation: "provider_terminal" } : { kind: "failed", error: projectDelegationFailure(event.payload.error) } };
77
97
  }
78
98
  return {};
79
99
  }
80
100
  async function consumeTurn(state, live, outputs) {
81
101
  let terminal;
102
+ let drained;
103
+ live.outputDrained = new Promise((resolve) => {
104
+ drained = resolve;
105
+ });
82
106
  try {
83
107
  for await (const output of outputs) {
84
108
  const projected = await reduceOutput(state, live, output);
85
- if (projected.request || projected.terminal) await live.requestResolution?.catch(() => void 0);
109
+ if (projected.request && live.request?.eligibility !== "retired") await live.request?.resolution?.catch(() => void 0);
86
110
  if (projected.request && !live.uncertain && !live.lifetime.signal.aborted) await routeRequest(state, live, projected.request);
87
111
  terminal = projected.terminal ?? terminal;
112
+ if (projected.terminal && live.requestAttempts.size > 0) {
113
+ live.terminal = projected.terminal;
114
+ void finishRun(state, live, projected.terminal).catch(() => void 0);
115
+ }
88
116
  }
89
- live.stream = void 0;
90
- if (!terminal) terminal = { kind: "recovery_required", reason: live.cancellation ? "cancellation_uncertain" : live.requestHistory ? "request_executor_lost" : "dispatch_uncertain" };
91
- } catch {
92
- live.stream = void 0;
93
- terminal = live.observedStart ? { kind: "recovery_required", reason: live.uncertain ?? (live.cancellation ? "cancellation_uncertain" : "dispatch_uncertain") } : { kind: "failed", error: { category: "unknown", code: "turn_rejected", message: "The managed child turn was rejected before provider execution.", recovery: { kind: "unknown" } } };
94
- if (!live.observedStart) {
95
- try {
96
- await operationReceipt(state, live, "rejected", operationIdentity(live, "turn"));
97
- } catch {
98
- live.uncertain = "storage_failed";
117
+ if (!terminal) terminal = { kind: "recovery_required", reason: live.cancellation ? "cancellation_uncertain" : live.request ? "request_executor_lost" : "dispatch_uncertain" };
118
+ } catch (error) {
119
+ const closedAfterTerminal = terminal !== void 0 && live.finishing !== void 0 && live.lifetime.signal.aborted && error instanceof DOMException && error.name === "AbortError";
120
+ if (!closedAfterTerminal) {
121
+ terminal = live.observedStart ? { kind: "recovery_required", reason: live.uncertain ?? (live.cancellation ? "cancellation_uncertain" : "dispatch_uncertain") } : { kind: "failed", error: { category: "unknown", code: "turn_rejected", message: "The managed child turn was rejected before provider execution.", recovery: { kind: "unknown" } } };
122
+ if (live.observedStart) live.uncertain = terminal.kind === "recovery_required" ? terminal.reason : "dispatch_uncertain";
123
+ else {
124
+ try {
125
+ await operationReceipt(state, live, "rejected", operationIdentity(live, "turn"));
126
+ } catch {
127
+ live.uncertain = "storage_failed";
128
+ }
99
129
  }
100
130
  }
131
+ } finally {
132
+ live.stream = void 0;
133
+ drained();
101
134
  }
102
- live.terminal = terminal;
103
- await finishRun(state, live, terminal);
135
+ const outcome = terminal ?? { kind: "recovery_required", reason: "dispatch_uncertain" };
136
+ live.terminal = outcome;
137
+ await finishRun(state, live, outcome);
104
138
  }
105
139
  export {
106
140
  consumeTurn,
package/dist/mcp/http.js CHANGED
@@ -5,7 +5,6 @@ class McpHttpError extends Error {
5
5
  super("The MCP HTTP request is unavailable.");
6
6
  this.status = status;
7
7
  }
8
- status;
9
8
  }
10
9
  function authorizeRequest(request, expectedHost, authorization) {
11
10
  if (request.headers.host !== expectedHost || request.headers.origin !== void 0) throw new McpHttpError(403);
@@ -30,7 +30,7 @@ function mcpTools(binding) {
30
30
  return tools;
31
31
  }
32
32
  function createMcpProtocol(context, tools) {
33
- const protocol = new Server({ name: "agenai_delegation", version: "0.4.0" }, {
33
+ const protocol = new Server({ name: "agenai_delegation", version: "0.4.2" }, {
34
34
  capabilities: { tools: {} },
35
35
  ...context.binding.instructions ? { instructions: context.binding.instructions.text } : {}
36
36
  });
@@ -4,7 +4,6 @@ class DelegationOperationError extends Error {
4
4
  this.code = code;
5
5
  this.name = "DelegationOperationError";
6
6
  }
7
- code;
8
7
  }
9
8
  function failDelegation(code, message) {
10
9
  throw new DelegationOperationError(code, message);
@@ -52,7 +52,7 @@ function createInMemoryDelegationStore(options = {}) {
52
52
  const runObservations = (runId) => [...observations.values()].filter((observation) => observation.runId === runId);
53
53
  const page = (query, rows, key, input, envelopeBytes = 0) => pageDelegationRows({ storeId, query, rows, key, limits, ...input, envelopeBytes });
54
54
  return Object.freeze({
55
- contractVersion: 3,
55
+ contractVersion: 4,
56
56
  persistence: "volatile",
57
57
  storeId,
58
58
  limits,
@@ -205,7 +205,7 @@ export type DelegationStoreScanPage = Readonly<{
205
205
  page: DelegationPage<DelegationNotificationRecord>;
206
206
  }>;
207
207
  export interface DelegationStore {
208
- readonly contractVersion: 3;
208
+ readonly contractVersion: 4;
209
209
  readonly persistence: DelegationPersistence;
210
210
  readonly storeId: string;
211
211
  readonly limits: DelegationLimits;
@@ -96,8 +96,8 @@ function operationReceipt(context, command) {
96
96
  const run = requireRun(context);
97
97
  if (!prior.request) failDelegation("operation_conflict", "Request delivery requires its exact retained request identity.");
98
98
  if (run.pendingRequest?.requestId === prior.request.requestId && run.pendingRequest.requestKind === prior.request.requestKind) {
99
- if (run.pendingRequest.state !== "resolution_intended") failDelegation("request_unavailable", "The exact request has no entered resolution intent.");
100
- const state = command.disposition === "delivered" ? "resolved" : command.requestState === "pending" ? "waiting" : "unavailable";
99
+ if (run.pendingRequest.state !== "resolution_intended" && run.pendingRequest.state !== "unavailable") failDelegation("request_unavailable", "The exact request has no entered resolution intent.");
100
+ const state = run.pendingRequest.state === "unavailable" ? "unavailable" : command.disposition === "delivered" ? "resolved" : command.requestState === "pending" ? "waiting" : "unavailable";
101
101
  setRun(context, {
102
102
  phase: run.phase === "settled" ? "settled" : state === "waiting" ? "waiting" : "running",
103
103
  pendingRequest: { ...run.pendingRequest, state }
@@ -139,7 +139,10 @@ function requestState(context, command) {
139
139
  setRun(context, { phase: "running", pendingRequest: { ...run.pendingRequest, state: "resolution_intended" } });
140
140
  } else {
141
141
  if (command.state === "resolved" && run.pendingRequest.state !== "resolution_intended") failDelegation("request_unavailable", "Resolution requires its prior intent.");
142
- setRun(context, { pendingRequest: { ...run.pendingRequest, state: command.state } });
142
+ setRun(context, {
143
+ ...command.state === "unavailable" && run.phase === "waiting" ? { phase: "running" } : {},
144
+ pendingRequest: { ...run.pendingRequest, state: command.state }
145
+ });
143
146
  }
144
147
  }
145
148
  context.receipt = { ...context.receipt, status: requireRun(context).phase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/agent-orchestration",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "Optional policy-governed managed delegation for the AgenAI coding-agent runtime.",
6
6
  "type": "module",
@@ -33,9 +33,9 @@
33
33
  "dependencies": {
34
34
  "zod": "4.4.3",
35
35
  "@modelcontextprotocol/sdk": "1.29.0",
36
- "@agen-ai/agent-protocol": "^0.4.0",
37
- "@agen-ai/agent-runtime": "^0.4.0",
38
- "@agen-ai/validation": "^0.4.0"
36
+ "@agen-ai/agent-protocol": "^0.4.2",
37
+ "@agen-ai/validation": "^0.4.2",
38
+ "@agen-ai/agent-runtime": "^0.4.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "tsup": "^8.5.0"