@agen-ai/agent-orchestration 0.0.0-stage → 0.4.0
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/LICENSE +21 -0
- package/README.md +413 -2
- package/dist/catalog/parse.d.ts +3 -0
- package/dist/catalog/parse.js +13 -0
- package/dist/catalog/schemas.d.ts +8 -0
- package/dist/catalog/schemas.js +78 -0
- package/dist/catalog/types.d.ts +53 -0
- package/dist/catalog/types.js +0 -0
- package/dist/coordinator/admission.d.ts +7 -0
- package/dist/coordinator/admission.js +189 -0
- package/dist/coordinator/cancel.d.ts +7 -0
- package/dist/coordinator/cancel.js +111 -0
- package/dist/coordinator/create.d.ts +3 -0
- package/dist/coordinator/create.js +87 -0
- package/dist/coordinator/parse.d.ts +10 -0
- package/dist/coordinator/parse.js +60 -0
- package/dist/coordinator/read.d.ts +4 -0
- package/dist/coordinator/read.js +267 -0
- package/dist/coordinator/recovery.d.ts +4 -0
- package/dist/coordinator/recovery.js +24 -0
- package/dist/coordinator/shutdown.d.ts +7 -0
- package/dist/coordinator/shutdown.js +66 -0
- package/dist/coordinator/types.d.ts +154 -0
- package/dist/coordinator/types.js +0 -0
- package/dist/delivery/calls.d.ts +4 -0
- package/dist/delivery/calls.js +34 -0
- package/dist/delivery/journal.d.ts +7 -0
- package/dist/delivery/journal.js +57 -0
- package/dist/delivery/parse.d.ts +4 -0
- package/dist/delivery/parse.js +31 -0
- package/dist/delivery/recovery.d.ts +3 -0
- package/dist/delivery/recovery.js +18 -0
- package/dist/delivery/scheduler.d.ts +3 -0
- package/dist/delivery/scheduler.js +123 -0
- package/dist/delivery/setup.d.ts +7 -0
- package/dist/delivery/setup.js +37 -0
- package/dist/delivery/types.d.ts +36 -0
- package/dist/delivery/types.js +0 -0
- package/dist/execution/authority.d.ts +16 -0
- package/dist/execution/authority.js +86 -0
- package/dist/execution/cleanup.d.ts +3 -0
- package/dist/execution/cleanup.js +75 -0
- package/dist/execution/control.d.ts +6 -0
- package/dist/execution/control.js +231 -0
- package/dist/execution/finish.d.ts +6 -0
- package/dist/execution/finish.js +49 -0
- package/dist/execution/journal.d.ts +19 -0
- package/dist/execution/journal.js +28 -0
- package/dist/execution/prepare.d.ts +3 -0
- package/dist/execution/prepare.js +153 -0
- package/dist/execution/scheduler.d.ts +4 -0
- package/dist/execution/scheduler.js +136 -0
- package/dist/execution/state.d.ts +107 -0
- package/dist/execution/state.js +120 -0
- package/dist/execution/stream.d.ts +9 -0
- package/dist/execution/stream.js +108 -0
- package/dist/host/types.d.ts +95 -0
- package/dist/host/types.js +0 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +58 -0
- package/dist/mcp/bridge.d.ts +4 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/http.d.ts +11 -0
- package/dist/mcp/http.js +74 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/limits.d.ts +10 -0
- package/dist/mcp/limits.js +30 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +81 -0
- package/dist/mcp/types.d.ts +22 -0
- package/dist/mcp/types.js +0 -0
- package/dist/policy/matching.d.ts +4 -0
- package/dist/policy/matching.js +18 -0
- package/dist/policy/parse.d.ts +3 -0
- package/dist/policy/parse.js +13 -0
- package/dist/policy/resolve.d.ts +7 -0
- package/dist/policy/resolve.js +67 -0
- package/dist/policy/schemas.d.ts +5 -0
- package/dist/policy/schemas.js +59 -0
- package/dist/policy/types.d.ts +60 -0
- package/dist/policy/types.js +0 -0
- package/dist/shared/bounds.d.ts +6 -0
- package/dist/shared/bounds.js +88 -0
- package/dist/shared/errors.d.ts +9 -0
- package/dist/shared/errors.js +27 -0
- package/dist/shared/identity.d.ts +6 -0
- package/dist/shared/identity.js +32 -0
- package/dist/shared/immutable.d.ts +2 -0
- package/dist/shared/immutable.js +10 -0
- package/dist/shared/limits.d.ts +32 -0
- package/dist/shared/limits.js +69 -0
- package/dist/shared/operational-errors.d.ts +7 -0
- package/dist/shared/operational-errors.js +15 -0
- package/dist/shared/schemas.d.ts +11 -0
- package/dist/shared/schemas.js +23 -0
- package/dist/shared/text.d.ts +2 -0
- package/dist/shared/text.js +15 -0
- package/dist/store/memory.d.ts +7 -0
- package/dist/store/memory.js +215 -0
- package/dist/store/pages.d.ts +16 -0
- package/dist/store/pages.js +46 -0
- package/dist/store/parse.d.ts +5 -0
- package/dist/store/parse.js +137 -0
- package/dist/store/types.d.ts +219 -0
- package/dist/store/types.js +0 -0
- package/dist/store-adapter/admission.d.ts +6 -0
- package/dist/store-adapter/admission.js +90 -0
- package/dist/store-adapter/context.d.ts +20 -0
- package/dist/store-adapter/context.js +107 -0
- package/dist/store-adapter/delivery.d.ts +15 -0
- package/dist/store-adapter/delivery.js +81 -0
- package/dist/store-adapter/execution.d.ts +12 -0
- package/dist/store-adapter/execution.js +151 -0
- package/dist/store-adapter/index.d.ts +7 -0
- package/dist/store-adapter/index.js +27 -0
- package/dist/store-adapter/lifecycle.d.ts +15 -0
- package/dist/store-adapter/lifecycle.js +80 -0
- package/dist/store-adapter/plan.d.ts +7 -0
- package/dist/store-adapter/plan.js +168 -0
- package/dist/store-adapter/types.d.ts +45 -0
- package/dist/store-adapter/types.js +0 -0
- package/dist/tasks/failure.d.ts +4 -0
- package/dist/tasks/failure.js +24 -0
- package/dist/tasks/parse.d.ts +18 -0
- package/dist/tasks/parse.js +66 -0
- package/dist/tasks/schemas.d.ts +47 -0
- package/dist/tasks/schemas.js +215 -0
- package/dist/tasks/types.d.ts +192 -0
- package/dist/tasks/types.js +0 -0
- package/dist/tools/create.d.ts +4 -0
- package/dist/tools/create.js +69 -0
- package/dist/tools/errors.d.ts +4 -0
- package/dist/tools/errors.js +35 -0
- package/dist/tools/guidance.d.ts +4 -0
- package/dist/tools/guidance.js +28 -0
- package/dist/tools/handlers.d.ts +4 -0
- package/dist/tools/handlers.js +84 -0
- package/dist/tools/projection.d.ts +13 -0
- package/dist/tools/projection.js +72 -0
- package/dist/tools/schemas.d.ts +6 -0
- package/dist/tools/schemas.js +93 -0
- package/dist/tools/types.d.ts +28 -0
- package/dist/tools/types.js +0 -0
- package/package.json +64 -3
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { effectiveChoices } from "../execution/authority.js";
|
|
3
|
+
import { sameScopeIdentity } from "../execution/state.js";
|
|
4
|
+
import { captureDelegationRecord } from "../shared/bounds.js";
|
|
5
|
+
import { invalidDelegationData } from "../shared/errors.js";
|
|
6
|
+
import { freezeDelegationData } from "../shared/immutable.js";
|
|
7
|
+
import { failDelegation } from "../shared/operational-errors.js";
|
|
8
|
+
import { delegationCursorPrefix, pageDelegationRows } from "../store/pages.js";
|
|
9
|
+
import { parseCoordinatorInput } from "./parse.js";
|
|
10
|
+
const cursor = z.string().min(1).max(512);
|
|
11
|
+
const limit = z.number().int().positive().max(100);
|
|
12
|
+
const taskId = z.uuid();
|
|
13
|
+
const targetQuery = z.strictObject({ cursor: cursor.optional(), limit: limit.optional() });
|
|
14
|
+
const inspectQuery = z.strictObject({ taskId, runsCursor: cursor.optional(), limit: limit.optional() });
|
|
15
|
+
const observeQuery = z.strictObject({ taskId, after: cursor.optional() });
|
|
16
|
+
const waitQuery = z.strictObject({ taskId, timeoutMs: z.number().int().positive().max(2147483647) });
|
|
17
|
+
function withSignal(schema, input) {
|
|
18
|
+
const { signal, ...data } = captureDelegationRecord(input);
|
|
19
|
+
if (signal !== void 0 && !(signal instanceof AbortSignal)) {
|
|
20
|
+
return invalidDelegationData("invalid_data", ["signal"], "A subscription signal must be an AbortSignal.");
|
|
21
|
+
}
|
|
22
|
+
return { ...parseCoordinatorInput(schema, data), ...signal === void 0 ? {} : { signal } };
|
|
23
|
+
}
|
|
24
|
+
function assertAccess(state, binding, task) {
|
|
25
|
+
if (task && !sameScopeIdentity(task.parent, binding.scope)) {
|
|
26
|
+
failDelegation("policy_denied", "The task does not belong to this parent scope.");
|
|
27
|
+
}
|
|
28
|
+
state.options.host.assertResultAccess({ scope: binding.scope, ...task === void 0 ? {} : { task } });
|
|
29
|
+
}
|
|
30
|
+
async function inspect(state, binding, input) {
|
|
31
|
+
assertAccess(state, binding);
|
|
32
|
+
const result = await state.options.store.read({
|
|
33
|
+
kind: "task",
|
|
34
|
+
taskId: input.taskId,
|
|
35
|
+
...input.runsCursor === void 0 ? {} : { cursor: input.runsCursor },
|
|
36
|
+
...input.limit === void 0 ? {} : { limit: input.limit }
|
|
37
|
+
});
|
|
38
|
+
if (result.kind !== "task" || !result.view) return failDelegation("request_unavailable", "The managed task is unavailable.");
|
|
39
|
+
assertAccess(state, binding, result.view.task);
|
|
40
|
+
return freezeDelegationData(result.view);
|
|
41
|
+
}
|
|
42
|
+
function watch(state, signal, timeoutMs) {
|
|
43
|
+
let resolve;
|
|
44
|
+
const promise = new Promise((done) => {
|
|
45
|
+
resolve = done;
|
|
46
|
+
});
|
|
47
|
+
const wake = () => resolve();
|
|
48
|
+
state.waiters.add(wake);
|
|
49
|
+
signal?.addEventListener("abort", wake, { once: true });
|
|
50
|
+
const timer = setTimeout(wake, timeoutMs);
|
|
51
|
+
if (signal?.aborted) wake();
|
|
52
|
+
return { promise, dispose() {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
state.waiters.delete(wake);
|
|
55
|
+
signal?.removeEventListener("abort", wake);
|
|
56
|
+
} };
|
|
57
|
+
}
|
|
58
|
+
function observationCursor(state, task, run, index) {
|
|
59
|
+
return `observation:${state.options.store.storeId}:${task}:${run}:${index}`;
|
|
60
|
+
}
|
|
61
|
+
async function observeFromCursor(state, task, after) {
|
|
62
|
+
if (after === void 0) return void 0;
|
|
63
|
+
const parts = after.split(":");
|
|
64
|
+
if (parts.length !== 5 || parts[0] !== "observation" || parts[1] !== state.options.store.storeId || parts[2] !== task || !z.uuid().safeParse(parts[3]).success || !/^(?:0|[1-9][0-9]*)$/u.test(parts[4])) {
|
|
65
|
+
return failDelegation("stale_revision", "The observation cursor belongs to a different task or store.");
|
|
66
|
+
}
|
|
67
|
+
const run = await state.run(task, parts[3]);
|
|
68
|
+
const index = Number(parts[4]);
|
|
69
|
+
if (!Number.isSafeInteger(index) || index > run.observationIndex) {
|
|
70
|
+
return failDelegation("stale_revision", "The observation cursor is beyond the retained run.");
|
|
71
|
+
}
|
|
72
|
+
return { runId: run.runId, index, ordinal: run.ordinal };
|
|
73
|
+
}
|
|
74
|
+
async function* observations(state, binding, input) {
|
|
75
|
+
assertAccess(state, binding);
|
|
76
|
+
let position = await observeFromCursor(state, input.taskId, input.after);
|
|
77
|
+
for (; ; ) {
|
|
78
|
+
input.signal.throwIfAborted();
|
|
79
|
+
const update = watch(state, input.signal, state.options.limits.maxWaitMs);
|
|
80
|
+
try {
|
|
81
|
+
let runsCursor;
|
|
82
|
+
let view;
|
|
83
|
+
let hasUnsettledRuns = false;
|
|
84
|
+
let currentRunActive = false;
|
|
85
|
+
do {
|
|
86
|
+
view = await inspect(state, binding, {
|
|
87
|
+
taskId: input.taskId,
|
|
88
|
+
...runsCursor === void 0 ? {} : { runsCursor }
|
|
89
|
+
});
|
|
90
|
+
for (const run of view.runs.items) {
|
|
91
|
+
if (run.phase !== "settled") hasUnsettledRuns = true;
|
|
92
|
+
if (position && run.ordinal < position.ordinal) continue;
|
|
93
|
+
if (!position || position.runId !== run.runId) position = { runId: run.runId, index: 0, ordinal: run.ordinal };
|
|
94
|
+
let more;
|
|
95
|
+
do {
|
|
96
|
+
input.signal.throwIfAborted();
|
|
97
|
+
assertAccess(state, binding, view.task);
|
|
98
|
+
const prefix = delegationCursorPrefix(
|
|
99
|
+
state.options.store.storeId,
|
|
100
|
+
{ kind: "observations", taskId: input.taskId, runId: run.runId }
|
|
101
|
+
);
|
|
102
|
+
const page = await state.options.store.read({
|
|
103
|
+
kind: "observations",
|
|
104
|
+
taskId: input.taskId,
|
|
105
|
+
runId: run.runId,
|
|
106
|
+
...position.index === 0 ? {} : { cursor: `${prefix}${String(position.index).padStart(16, "0")}` }
|
|
107
|
+
});
|
|
108
|
+
if (page.kind !== "observations") return failDelegation("store_failed", "The store returned a mismatched observation page.");
|
|
109
|
+
assertAccess(state, binding, view.task);
|
|
110
|
+
for (const observation of page.page.items) {
|
|
111
|
+
input.signal.throwIfAborted();
|
|
112
|
+
assertAccess(state, binding, view.task);
|
|
113
|
+
if (observation.index > position.index + 1) {
|
|
114
|
+
position.index = observation.index - 1;
|
|
115
|
+
yield freezeDelegationData({
|
|
116
|
+
kind: "gap",
|
|
117
|
+
storeId: state.options.store.storeId,
|
|
118
|
+
taskId: input.taskId,
|
|
119
|
+
runId: run.runId,
|
|
120
|
+
cursor: observationCursor(state, input.taskId, run.runId, position.index)
|
|
121
|
+
});
|
|
122
|
+
input.signal.throwIfAborted();
|
|
123
|
+
assertAccess(state, binding, view.task);
|
|
124
|
+
}
|
|
125
|
+
position.index = observation.index;
|
|
126
|
+
yield freezeDelegationData({ ...observation, cursor: observationCursor(state, input.taskId, run.runId, position.index) });
|
|
127
|
+
}
|
|
128
|
+
more = page.page.cursor;
|
|
129
|
+
if (more === void 0 && page.latestIndex > position.index) {
|
|
130
|
+
position.index = page.latestIndex;
|
|
131
|
+
input.signal.throwIfAborted();
|
|
132
|
+
assertAccess(state, binding, view.task);
|
|
133
|
+
yield freezeDelegationData({
|
|
134
|
+
kind: "gap",
|
|
135
|
+
storeId: state.options.store.storeId,
|
|
136
|
+
taskId: input.taskId,
|
|
137
|
+
runId: run.runId,
|
|
138
|
+
cursor: observationCursor(state, input.taskId, run.runId, position.index)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
} while (more !== void 0);
|
|
142
|
+
if (run.phase !== "settled") {
|
|
143
|
+
currentRunActive = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
runsCursor = currentRunActive ? void 0 : view.runs.cursor;
|
|
148
|
+
} while (runsCursor !== void 0);
|
|
149
|
+
if (view.task.state === "settled" || view.task.state === "retired") return;
|
|
150
|
+
if (view.task.state === "recovery_required" && !hasUnsettledRuns) return;
|
|
151
|
+
if (state.failed || state.released) failDelegation("owner_lost", "The managed executor is unavailable; inspect its retained history.");
|
|
152
|
+
await update.promise;
|
|
153
|
+
} finally {
|
|
154
|
+
update.dispose();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function subscription(state, binding, input) {
|
|
159
|
+
return { [Symbol.asyncIterator]() {
|
|
160
|
+
const lifetime = new AbortController();
|
|
161
|
+
const signal = input.signal ? AbortSignal.any([input.signal, lifetime.signal]) : lifetime.signal;
|
|
162
|
+
const iterator = observations(state, binding, { ...input, signal });
|
|
163
|
+
let pulling = false;
|
|
164
|
+
return {
|
|
165
|
+
async next() {
|
|
166
|
+
if (pulling) return failDelegation("unsupported_operation", "An observation subscription permits one pending read.");
|
|
167
|
+
pulling = true;
|
|
168
|
+
try {
|
|
169
|
+
return await iterator.next();
|
|
170
|
+
} finally {
|
|
171
|
+
pulling = false;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async return() {
|
|
175
|
+
lifetime.abort(new DOMException("Observation subscription closed.", "AbortError"));
|
|
176
|
+
await iterator.return(void 0);
|
|
177
|
+
return { done: true, value: void 0 };
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
} };
|
|
181
|
+
}
|
|
182
|
+
function createReadClient(state, binding) {
|
|
183
|
+
return {
|
|
184
|
+
async bindingSnapshot() {
|
|
185
|
+
assertAccess(state, binding);
|
|
186
|
+
const choices = await effectiveChoices(state, binding);
|
|
187
|
+
assertAccess(state, binding);
|
|
188
|
+
const { maxInputBytes, maxInputTextCharacters, maxWaitMs, runTimeoutMs, maxPageRecords } = state.options.limits;
|
|
189
|
+
return freezeDelegationData({
|
|
190
|
+
enabled: choices.enabled,
|
|
191
|
+
policyRevision: choices.resolved.policyRevision,
|
|
192
|
+
completionPolicies: state.options.parentDelivery ? ["collect", "next_turn", "automatic"] : ["collect"],
|
|
193
|
+
limits: { maxInputBytes, maxInputTextCharacters, maxWaitMs, runTimeoutMs, maxPageRecords }
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
async targets(input = {}) {
|
|
197
|
+
const query = parseCoordinatorInput(targetQuery, input);
|
|
198
|
+
assertAccess(state, binding);
|
|
199
|
+
const choices = await effectiveChoices(state, binding);
|
|
200
|
+
assertAccess(state, binding);
|
|
201
|
+
const page = pageDelegationRows({
|
|
202
|
+
storeId: state.options.store.storeId,
|
|
203
|
+
query: {
|
|
204
|
+
kind: "targets",
|
|
205
|
+
scope: binding.scope,
|
|
206
|
+
policyRevision: choices.resolved.policyRevision,
|
|
207
|
+
originPolicyRevision: choices.originPolicyRevision,
|
|
208
|
+
ceilingId: choices.ceiling.ceilingId
|
|
209
|
+
},
|
|
210
|
+
rows: choices.targets.map((target, index) => ({ target, index })),
|
|
211
|
+
key: ({ index }) => String(index).padStart(4, "0"),
|
|
212
|
+
limits: state.options.limits,
|
|
213
|
+
...query,
|
|
214
|
+
envelopeBytes: 64
|
|
215
|
+
});
|
|
216
|
+
return freezeDelegationData({
|
|
217
|
+
policyRevision: choices.resolved.policyRevision,
|
|
218
|
+
targets: page.items.map(({ target }) => target),
|
|
219
|
+
...page.cursor === void 0 ? {} : { cursor: page.cursor },
|
|
220
|
+
...choices.targets.length === 0 ? { emptyReason: choices.enabled ? "no_permitted_targets" : "delegation_disabled" } : {}
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
inspect(input) {
|
|
224
|
+
return inspect(state, binding, parseCoordinatorInput(inspectQuery, input));
|
|
225
|
+
},
|
|
226
|
+
observe(input) {
|
|
227
|
+
return subscription(state, binding, withSignal(observeQuery, input));
|
|
228
|
+
},
|
|
229
|
+
async wait(input) {
|
|
230
|
+
const query = withSignal(waitQuery, input);
|
|
231
|
+
if (query.timeoutMs > state.options.limits.maxWaitMs) failDelegation("capacity_exceeded", "The wait exceeds the configured subscription deadline.");
|
|
232
|
+
const deadline = Date.now() + query.timeoutMs;
|
|
233
|
+
for (; ; ) {
|
|
234
|
+
query.signal?.throwIfAborted();
|
|
235
|
+
const update = watch(state, query.signal, Math.max(1, deadline - Date.now()));
|
|
236
|
+
try {
|
|
237
|
+
const view = await inspect(state, binding, { taskId: query.taskId });
|
|
238
|
+
query.signal?.throwIfAborted();
|
|
239
|
+
if (view.task.state === "settled" || view.task.state === "retired") {
|
|
240
|
+
return freezeDelegationData({ status: "settled", task: view });
|
|
241
|
+
}
|
|
242
|
+
if (view.task.state === "recovery_required") {
|
|
243
|
+
let page = view;
|
|
244
|
+
let active = false;
|
|
245
|
+
let changed = false;
|
|
246
|
+
for (; ; ) {
|
|
247
|
+
active ||= page.runs.items.some((run) => run.phase !== "settled");
|
|
248
|
+
changed ||= page.task.revision !== view.task.revision;
|
|
249
|
+
if (!page.runs.cursor || active || changed) break;
|
|
250
|
+
page = await inspect(state, binding, { taskId: query.taskId, runsCursor: page.runs.cursor });
|
|
251
|
+
}
|
|
252
|
+
query.signal?.throwIfAborted();
|
|
253
|
+
if (!active && !changed) return freezeDelegationData({ status: "settled", task: view });
|
|
254
|
+
}
|
|
255
|
+
if (Date.now() >= deadline) return freezeDelegationData({ status: "timeout", task: view });
|
|
256
|
+
if (state.failed || state.released) failDelegation("owner_lost", "The managed executor is unavailable; inspect its retained history.");
|
|
257
|
+
await update.promise;
|
|
258
|
+
} finally {
|
|
259
|
+
update.dispose();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
export {
|
|
266
|
+
createReadClient
|
|
267
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CoordinatorState } from "../execution/state.js";
|
|
2
|
+
import type { DelegationRecoveryPage } from "./types.js";
|
|
3
|
+
export declare function recoverRuns(state: CoordinatorState, limit: number, cursor?: string, start?: boolean): Promise<DelegationRecoveryPage>;
|
|
4
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { settleRun } from "../execution/finish.js";
|
|
2
|
+
import { schedule } from "../execution/scheduler.js";
|
|
3
|
+
import { now } from "../execution/state.js";
|
|
4
|
+
import { failDelegation } from "../shared/operational-errors.js";
|
|
5
|
+
async function recoverRuns(state, limit, cursor, start = true) {
|
|
6
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > state.options.limits.maxPageRecords) failDelegation("capacity_exceeded", "Recovery page size exceeds its bound.");
|
|
7
|
+
const page = await state.options.store.scan({ kind: "recoverable_runs", at: now(), limit, ...cursor ? { cursor } : {} });
|
|
8
|
+
if (page.kind !== "recoverable_runs") return { recovered: [] };
|
|
9
|
+
const recovered = [];
|
|
10
|
+
for (const run of page.page.items) {
|
|
11
|
+
if (run.ownerEpoch === state.owner.epoch || run.phase === "settled") continue;
|
|
12
|
+
if (run.phase === "queued" && run.preparation === "none" && run.dispatch === "none") {
|
|
13
|
+
recovered.push({ taskId: run.taskId, runId: run.runId, disposition: "queued" });
|
|
14
|
+
} else {
|
|
15
|
+
await settleRun(state, run, { kind: "recovery_required", reason: run.pendingRequest ? "request_executor_lost" : "executor_lost" });
|
|
16
|
+
recovered.push({ taskId: run.taskId, runId: run.runId, disposition: "recovery_required" });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (start) schedule(state);
|
|
20
|
+
return { recovered, ...page.page.cursor ? { cursor: page.page.cursor } : {} };
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
recoverRuns
|
|
24
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CoordinatorState } from "../execution/state.js";
|
|
2
|
+
import type { DelegationShutdownReceipt } from "./types.js";
|
|
3
|
+
export declare function shutdown(state: CoordinatorState, input: Readonly<{
|
|
4
|
+
mode: "drain" | "cancel";
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
}>): Promise<DelegationShutdownReceipt>;
|
|
7
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { cancelLive } from "../execution/control.js";
|
|
2
|
+
import { now } from "../execution/state.js";
|
|
3
|
+
import { schedule } from "../execution/scheduler.js";
|
|
4
|
+
import { delegationInputDigest, delegationSubordinateId } from "../shared/identity.js";
|
|
5
|
+
import { failDelegation } from "../shared/operational-errors.js";
|
|
6
|
+
async function cancelQueued(state) {
|
|
7
|
+
let cursor;
|
|
8
|
+
do {
|
|
9
|
+
const page = await state.options.store.scan({ kind: "queued", at: now(), limit: state.options.limits.maxPageRecords, ...cursor ? { cursor } : {} });
|
|
10
|
+
if (page.kind !== "queued") return;
|
|
11
|
+
for (const run of page.page.items) await state.mutate(
|
|
12
|
+
run.taskId,
|
|
13
|
+
run.runId,
|
|
14
|
+
delegationSubordinateId(run.runId, "shutdown_cancel"),
|
|
15
|
+
delegationInputDigest({ runId: run.runId, kind: "shutdown_cancel" }),
|
|
16
|
+
(fence) => ({ ...fence, kind: "cancel", at: now(), reason: "shutdown" })
|
|
17
|
+
);
|
|
18
|
+
cursor = page.page.cursor;
|
|
19
|
+
} while (cursor);
|
|
20
|
+
}
|
|
21
|
+
async function shutdown(state, input) {
|
|
22
|
+
if (!["drain", "cancel"].includes(input.mode) || !Number.isSafeInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > state.options.limits.maxWaitMs) failDelegation("capacity_exceeded", "Shutdown exceeds its configured wait bound.");
|
|
23
|
+
if (state.released) return { status: "closed", retainedTaskIds: [] };
|
|
24
|
+
state.closing = true;
|
|
25
|
+
state.stopDelivery?.(input.mode === "cancel");
|
|
26
|
+
state.draining = input.mode === "drain";
|
|
27
|
+
if (!state.draining && state.queueTimer) {
|
|
28
|
+
clearTimeout(state.queueTimer);
|
|
29
|
+
state.queueTimer = void 0;
|
|
30
|
+
}
|
|
31
|
+
const actions = input.mode === "cancel" ? Promise.allSettled([cancelQueued(state), ...[...state.live.values()].map((live) => cancelLive(state, live, "shutdown"))]) : Promise.resolve();
|
|
32
|
+
state.track(Promise.resolve(actions).then(() => void 0));
|
|
33
|
+
if (state.draining) schedule(state);
|
|
34
|
+
const end = Date.now() + input.timeoutMs;
|
|
35
|
+
while ((state.producers.size > 0 || state.live.size > 0) && Date.now() < end) {
|
|
36
|
+
let timer;
|
|
37
|
+
let wake;
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
wake = resolve;
|
|
40
|
+
state.waiters.add(resolve);
|
|
41
|
+
timer = setTimeout(resolve, Math.max(1, end - Date.now()));
|
|
42
|
+
});
|
|
43
|
+
if (timer) clearTimeout(timer);
|
|
44
|
+
if (wake) state.waiters.delete(wake);
|
|
45
|
+
}
|
|
46
|
+
const queuedTaskIds = [];
|
|
47
|
+
let cursor;
|
|
48
|
+
do {
|
|
49
|
+
const queued = await state.options.store.scan({ kind: "queued", at: now(), limit: state.options.limits.maxPageRecords, ...cursor ? { cursor } : {} });
|
|
50
|
+
if (queued.kind !== "queued") failDelegation("store_failed", "The queued scan does not match its query.");
|
|
51
|
+
queuedTaskIds.push(...queued.page.items.map((run) => run.taskId));
|
|
52
|
+
cursor = queued.page.cursor;
|
|
53
|
+
} while (cursor);
|
|
54
|
+
if (state.live.size > 0 || state.producers.size > 0 || state.failed || queuedTaskIds.length > 0) return { status: "recovery_required", retainedTaskIds: [...new Set([...state.live.values()].map((live) => live.taskId).concat(queuedTaskIds, [...state.deliveryCalls.values()].map((call) => call.taskId)))].sort() };
|
|
55
|
+
await state.options.store.releaseOwner(state.owner);
|
|
56
|
+
if (state.queueTimer) {
|
|
57
|
+
clearTimeout(state.queueTimer);
|
|
58
|
+
state.queueTimer = void 0;
|
|
59
|
+
}
|
|
60
|
+
state.released = true;
|
|
61
|
+
state.changed();
|
|
62
|
+
return { status: "closed", retainedTaskIds: [] };
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
shutdown
|
|
66
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { AgentRequestResolution, AgentTurnInputContent } from "@agen-ai/agent-protocol";
|
|
2
|
+
import type { DelegationTarget, DelegationTargetReference } from "../catalog/types.js";
|
|
3
|
+
import type { DelegationParentDeliveryPort } from "../delivery/types.js";
|
|
4
|
+
import type { DelegationHost, DelegationHostRequestHandle } from "../host/types.js";
|
|
5
|
+
import type { DelegationLimits } from "../shared/limits.js";
|
|
6
|
+
import type { DelegationStore } from "../store/types.js";
|
|
7
|
+
import type { DelegationCompletionPolicy, DelegationObservationGap, DelegationObservedTaskObservation, DelegationParentScope, DelegationTaskView } from "../tasks/types.js";
|
|
8
|
+
export interface DelegationTargetPage {
|
|
9
|
+
readonly policyRevision: number;
|
|
10
|
+
readonly targets: readonly DelegationTarget[];
|
|
11
|
+
readonly cursor?: string;
|
|
12
|
+
readonly emptyReason?: "delegation_disabled" | "no_permitted_targets";
|
|
13
|
+
}
|
|
14
|
+
export interface DelegationBindingSnapshot {
|
|
15
|
+
readonly enabled: boolean;
|
|
16
|
+
readonly policyRevision: number;
|
|
17
|
+
readonly completionPolicies: readonly DelegationCompletionPolicy[];
|
|
18
|
+
readonly limits: Readonly<Pick<DelegationLimits, "maxInputBytes" | "maxInputTextCharacters" | "maxWaitMs" | "runTimeoutMs" | "maxPageRecords">>;
|
|
19
|
+
}
|
|
20
|
+
export interface DelegationSpawnTaskInput {
|
|
21
|
+
readonly operationId: string;
|
|
22
|
+
readonly target: DelegationTargetReference;
|
|
23
|
+
readonly expectedPolicyRevision: number;
|
|
24
|
+
readonly input: AgentTurnInputContent;
|
|
25
|
+
readonly completionPolicy?: DelegationCompletionPolicy;
|
|
26
|
+
readonly timeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface DelegationSendTaskInput {
|
|
29
|
+
readonly operationId: string;
|
|
30
|
+
readonly taskId: string;
|
|
31
|
+
readonly mode: "steer" | "next_turn" | "follow_up";
|
|
32
|
+
readonly input: AgentTurnInputContent;
|
|
33
|
+
readonly target: DelegationTargetReference;
|
|
34
|
+
readonly expectedPolicyRevision: number;
|
|
35
|
+
}
|
|
36
|
+
export interface DelegationAdmissionReceipt {
|
|
37
|
+
readonly storeId: string;
|
|
38
|
+
readonly operationId: string;
|
|
39
|
+
readonly taskId: string;
|
|
40
|
+
readonly runId: string;
|
|
41
|
+
readonly target: DelegationTargetReference;
|
|
42
|
+
readonly revision: number;
|
|
43
|
+
readonly status: "queued" | "preparing";
|
|
44
|
+
readonly duplicate: boolean;
|
|
45
|
+
}
|
|
46
|
+
export type DelegationSendReceipt = Readonly<{
|
|
47
|
+
status: "queued";
|
|
48
|
+
admission: DelegationAdmissionReceipt;
|
|
49
|
+
duplicate: boolean;
|
|
50
|
+
}> | Readonly<{
|
|
51
|
+
status: "delivered" | "rejected" | "delivery_uncertain";
|
|
52
|
+
operationId: string;
|
|
53
|
+
taskId: string;
|
|
54
|
+
runId: string;
|
|
55
|
+
duplicate: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
export interface DelegationCancelReceipt {
|
|
58
|
+
readonly status: "requested" | "confirmed" | "recovery_required";
|
|
59
|
+
readonly operationId: string;
|
|
60
|
+
readonly taskId: string;
|
|
61
|
+
readonly duplicate: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface DelegationResolveHostRequestInput {
|
|
64
|
+
readonly handle: DelegationHostRequestHandle;
|
|
65
|
+
readonly operationId: string;
|
|
66
|
+
readonly resolution: AgentRequestResolution;
|
|
67
|
+
}
|
|
68
|
+
interface DelegationRequestReceiptIdentity {
|
|
69
|
+
readonly taskId: string;
|
|
70
|
+
readonly runId: string;
|
|
71
|
+
readonly operationId: string;
|
|
72
|
+
readonly duplicate: boolean;
|
|
73
|
+
}
|
|
74
|
+
export type DelegationRequestReceipt = DelegationRequestReceiptIdentity & (Readonly<{
|
|
75
|
+
status: "delivered" | "delivery_uncertain";
|
|
76
|
+
}> | Readonly<{
|
|
77
|
+
status: "rejected";
|
|
78
|
+
requestState: "pending" | "unavailable";
|
|
79
|
+
}>);
|
|
80
|
+
export type DelegationWaitReceipt = Readonly<{
|
|
81
|
+
status: "settled" | "timeout";
|
|
82
|
+
task: DelegationTaskView;
|
|
83
|
+
}>;
|
|
84
|
+
export interface DelegationRecoveryPage {
|
|
85
|
+
readonly recovered: readonly Readonly<{
|
|
86
|
+
taskId: string;
|
|
87
|
+
runId: string;
|
|
88
|
+
disposition: "queued" | "recovery_required";
|
|
89
|
+
}>[];
|
|
90
|
+
readonly cursor?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface DelegationShutdownReceipt {
|
|
93
|
+
readonly status: "closed" | "recovery_required";
|
|
94
|
+
readonly retainedTaskIds: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
export interface DelegationClient {
|
|
97
|
+
bindingSnapshot(): Promise<DelegationBindingSnapshot>;
|
|
98
|
+
targets(input?: Readonly<{
|
|
99
|
+
cursor?: string;
|
|
100
|
+
limit?: number;
|
|
101
|
+
}>): Promise<DelegationTargetPage>;
|
|
102
|
+
spawn(input: DelegationSpawnTaskInput): Promise<DelegationAdmissionReceipt>;
|
|
103
|
+
inspect(input: Readonly<{
|
|
104
|
+
taskId: string;
|
|
105
|
+
runsCursor?: string;
|
|
106
|
+
limit?: number;
|
|
107
|
+
}>): Promise<DelegationTaskView>;
|
|
108
|
+
observe(input: Readonly<{
|
|
109
|
+
taskId: string;
|
|
110
|
+
after?: string;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
}>): AsyncIterable<DelegationObservedTaskObservation | DelegationObservationGap>;
|
|
113
|
+
wait(input: Readonly<{
|
|
114
|
+
taskId: string;
|
|
115
|
+
timeoutMs: number;
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}>): Promise<DelegationWaitReceipt>;
|
|
118
|
+
send(input: DelegationSendTaskInput): Promise<DelegationSendReceipt>;
|
|
119
|
+
cancel(input: Readonly<{
|
|
120
|
+
operationId: string;
|
|
121
|
+
taskId: string;
|
|
122
|
+
}>): Promise<DelegationCancelReceipt>;
|
|
123
|
+
}
|
|
124
|
+
export interface DelegationCoordinator {
|
|
125
|
+
bindParent(input: Readonly<{
|
|
126
|
+
scope: DelegationParentScope;
|
|
127
|
+
ancestor?: Readonly<{
|
|
128
|
+
taskId: string;
|
|
129
|
+
runId: string;
|
|
130
|
+
}>;
|
|
131
|
+
}>): Promise<DelegationClient>;
|
|
132
|
+
resolveRequest(input: DelegationResolveHostRequestInput): Promise<DelegationRequestReceipt>;
|
|
133
|
+
recover(input: Readonly<{
|
|
134
|
+
limit: number;
|
|
135
|
+
cursor?: string;
|
|
136
|
+
}>): Promise<DelegationRecoveryPage>;
|
|
137
|
+
retireRecoveryTask(input: Readonly<{
|
|
138
|
+
operationId: string;
|
|
139
|
+
taskId: string;
|
|
140
|
+
reason: string;
|
|
141
|
+
}>): Promise<DelegationTaskView>;
|
|
142
|
+
dispose(input: Readonly<{
|
|
143
|
+
mode: "drain" | "cancel";
|
|
144
|
+
timeoutMs: number;
|
|
145
|
+
}>): Promise<DelegationShutdownReceipt>;
|
|
146
|
+
}
|
|
147
|
+
export interface CreateDelegationCoordinatorOptions {
|
|
148
|
+
readonly host: DelegationHost;
|
|
149
|
+
readonly store: DelegationStore;
|
|
150
|
+
readonly limits: DelegationLimits;
|
|
151
|
+
readonly parentDelivery?: DelegationParentDeliveryPort;
|
|
152
|
+
}
|
|
153
|
+
export {};
|
|
154
|
+
//# sourceMappingURL=types.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CoordinatorState } from "../execution/state.js";
|
|
2
|
+
import type { DelegationNotificationRecord } from "../tasks/types.js";
|
|
3
|
+
export declare function callInbox<Value>(state: CoordinatorState, notification: DelegationNotificationRecord, kind: "accept" | "inspect", stopped: () => boolean, invoke: (signal: AbortSignal, deadlineAt: string) => Promise<Value>): Promise<Value | undefined>;
|
|
4
|
+
//# sourceMappingURL=calls.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
async function callInbox(state, notification, kind, stopped, invoke) {
|
|
2
|
+
const unavailable = () => stopped() || state.failed || state.released || kind === "accept" && Date.parse(notification.envelope.expiresAt) <= Date.now();
|
|
3
|
+
if (unavailable()) return void 0;
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const deadline = Math.min(
|
|
6
|
+
Date.now() + state.options.limits.controlTimeoutMs,
|
|
7
|
+
kind === "accept" ? Date.parse(notification.envelope.expiresAt) : Infinity
|
|
8
|
+
);
|
|
9
|
+
const id = notification.envelope.notificationId;
|
|
10
|
+
const promise = Promise.resolve().then(() => {
|
|
11
|
+
if (unavailable()) return void 0;
|
|
12
|
+
return invoke(controller.signal, new Date(deadline).toISOString());
|
|
13
|
+
}).finally(() => {
|
|
14
|
+
state.deliveryCalls.delete(id);
|
|
15
|
+
state.changed();
|
|
16
|
+
if (!stopped()) state.wakeDelivery?.();
|
|
17
|
+
});
|
|
18
|
+
state.deliveryCalls.set(id, { controller, taskId: notification.envelope.taskId, promise });
|
|
19
|
+
state.track(promise);
|
|
20
|
+
let timer;
|
|
21
|
+
try {
|
|
22
|
+
return await Promise.race([promise, new Promise((resolve) => {
|
|
23
|
+
timer = setTimeout(() => {
|
|
24
|
+
controller.abort(new DOMException("Parent inbox deadline expired.", "AbortError"));
|
|
25
|
+
resolve(void 0);
|
|
26
|
+
}, Math.max(1, deadline - Date.now()));
|
|
27
|
+
})]);
|
|
28
|
+
} finally {
|
|
29
|
+
if (timer) clearTimeout(timer);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
callInbox
|
|
34
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CoordinatorState } from "../execution/state.js";
|
|
2
|
+
import type { DelegationDeliveryResult } from "../store/types.js";
|
|
3
|
+
import type { DelegationNotificationRecord } from "../tasks/types.js";
|
|
4
|
+
export declare function readNotification(state: CoordinatorState, notificationId: string): Promise<DelegationNotificationRecord>;
|
|
5
|
+
export declare function claimNotification(state: CoordinatorState, notification: DelegationNotificationRecord): Promise<DelegationNotificationRecord>;
|
|
6
|
+
export declare function receiptNotification(state: CoordinatorState, notification: DelegationNotificationRecord, result: DelegationDeliveryResult): Promise<DelegationNotificationRecord>;
|
|
7
|
+
//# sourceMappingURL=journal.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { now } from "../execution/state.js";
|
|
2
|
+
import { delegationInputDigest } from "../shared/identity.js";
|
|
3
|
+
import { failDelegation } from "../shared/operational-errors.js";
|
|
4
|
+
import { deliveryAttemptIdentity } from "../store-adapter/delivery.js";
|
|
5
|
+
import { parseDelegationNotificationRecord } from "../tasks/parse.js";
|
|
6
|
+
async function readNotification(state, notificationId) {
|
|
7
|
+
const page = await state.options.store.read({ kind: "notification", notificationId });
|
|
8
|
+
if (page.kind !== "notification" || !page.notification) return failDelegation("store_failed", "A retained completion notification is unavailable.");
|
|
9
|
+
return parseDelegationNotificationRecord(page.notification);
|
|
10
|
+
}
|
|
11
|
+
async function claimNotification(state, notification) {
|
|
12
|
+
const identity = deliveryAttemptIdentity(notification.envelope.notificationId, notification.attempts + 1);
|
|
13
|
+
const command = {
|
|
14
|
+
kind: "delivery_claim",
|
|
15
|
+
notificationId: notification.envelope.notificationId,
|
|
16
|
+
expectedNotificationRevision: notification.revision,
|
|
17
|
+
attemptId: identity.attemptId,
|
|
18
|
+
at: now()
|
|
19
|
+
};
|
|
20
|
+
await state.mutate(
|
|
21
|
+
notification.envelope.taskId,
|
|
22
|
+
notification.envelope.runId,
|
|
23
|
+
identity.claimOperationId,
|
|
24
|
+
delegationInputDigest(command),
|
|
25
|
+
(fence) => ({ ...fence, ...command })
|
|
26
|
+
);
|
|
27
|
+
return readNotification(state, notification.envelope.notificationId);
|
|
28
|
+
}
|
|
29
|
+
async function receiptNotification(state, notification, result) {
|
|
30
|
+
const identity = deliveryAttemptIdentity(notification.envelope.notificationId, notification.attempts);
|
|
31
|
+
const at = now();
|
|
32
|
+
const boundedResult = result.status === "blocked" || result.status === "uncertain" ? {
|
|
33
|
+
...result,
|
|
34
|
+
retryAt: new Date(Math.max(Date.parse(at) + 1e3, Math.min(Date.parse(at) + 3e4, Date.parse(result.retryAt)))).toISOString()
|
|
35
|
+
} : result;
|
|
36
|
+
const command = {
|
|
37
|
+
kind: "delivery_receipt",
|
|
38
|
+
notificationId: notification.envelope.notificationId,
|
|
39
|
+
expectedNotificationRevision: notification.revision,
|
|
40
|
+
attemptId: identity.attemptId,
|
|
41
|
+
result: boundedResult,
|
|
42
|
+
at
|
|
43
|
+
};
|
|
44
|
+
await state.mutate(
|
|
45
|
+
notification.envelope.taskId,
|
|
46
|
+
notification.envelope.runId,
|
|
47
|
+
identity.receiptOperationId,
|
|
48
|
+
delegationInputDigest(command),
|
|
49
|
+
(fence) => ({ ...fence, ...command })
|
|
50
|
+
);
|
|
51
|
+
return readNotification(state, notification.envelope.notificationId);
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
claimNotification,
|
|
55
|
+
readNotification,
|
|
56
|
+
receiptNotification
|
|
57
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DelegationInboxAcceptance, DelegationInboxInspection } from "./types.js";
|
|
2
|
+
export declare const parseDelegationInboxAcceptance: (input: unknown) => DelegationInboxAcceptance;
|
|
3
|
+
export declare const parseDelegationInboxInspection: (input: unknown) => DelegationInboxInspection;
|
|
4
|
+
//# sourceMappingURL=parse.d.ts.map
|