@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 +31 -6
- package/dist/coordinator/create.js +1 -1
- package/dist/coordinator/parse.d.ts +2 -1
- package/dist/execution/cleanup.js +3 -0
- package/dist/execution/control.js +60 -48
- package/dist/execution/scheduler.js +2 -0
- package/dist/execution/state.d.ts +12 -3
- package/dist/execution/state.js +0 -2
- package/dist/execution/stream.js +54 -20
- package/dist/mcp/http.js +0 -1
- package/dist/mcp/protocol.js +1 -1
- package/dist/shared/operational-errors.js +0 -1
- package/dist/store/memory.js +1 -1
- package/dist/store/types.d.ts +1 -1
- package/dist/store-adapter/execution.js +6 -3
- package/package.json +4 -4
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 !==
|
|
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) =>
|
|
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
|
-
|
|
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 ||
|
|
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 =
|
|
160
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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.
|
|
231
|
+
pending.resolution ??= work;
|
|
221
232
|
void work.finally(() => {
|
|
222
233
|
pending.live.pendingOperations.delete(operationId);
|
|
223
|
-
|
|
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;
|
package/dist/execution/state.js
CHANGED
package/dist/execution/stream.js
CHANGED
|
@@ -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
|
|
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 (
|
|
49
|
-
await state.options.host.onRequest({ handle, task, run, request, 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 === "
|
|
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
|
|
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.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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);
|
package/dist/mcp/protocol.js
CHANGED
|
@@ -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.
|
|
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
|
});
|
package/dist/store/memory.js
CHANGED
|
@@ -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:
|
|
55
|
+
contractVersion: 4,
|
|
56
56
|
persistence: "volatile",
|
|
57
57
|
storeId,
|
|
58
58
|
limits,
|
package/dist/store/types.d.ts
CHANGED
|
@@ -205,7 +205,7 @@ export type DelegationStoreScanPage = Readonly<{
|
|
|
205
205
|
page: DelegationPage<DelegationNotificationRecord>;
|
|
206
206
|
}>;
|
|
207
207
|
export interface DelegationStore {
|
|
208
|
-
readonly contractVersion:
|
|
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, {
|
|
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.
|
|
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.
|
|
37
|
-
"@agen-ai/
|
|
38
|
-
"@agen-ai/
|
|
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"
|