@zq-silk/yui 0.6.8 → 0.6.10
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 +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +101 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/task/nextAction.js +1 -1
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
const RECENT_DEDUPE_KEY_LIMIT = 256;
|
|
2
3
|
function requireText(value, label) {
|
|
3
4
|
const normalized = value.trim();
|
|
4
5
|
if (normalized.length === 0) {
|
|
@@ -128,7 +129,15 @@ function mergeBatches(earlier, later) {
|
|
|
128
129
|
refs: appendUnique(earlier.refs, later.refs, mailboxEntityRefKey),
|
|
129
130
|
requestCount: earlier.requestCount + later.requestCount,
|
|
130
131
|
firstQueuedAt: earlier.firstQueuedAt,
|
|
131
|
-
lastQueuedAt: later.lastQueuedAt
|
|
132
|
+
lastQueuedAt: later.lastQueuedAt,
|
|
133
|
+
sources: appendUnique(earlier.sources, later.sources, (source) => source),
|
|
134
|
+
dedupeKeys: appendUnique(earlier.dedupeKeys, later.dedupeKeys, (key) => key),
|
|
135
|
+
deliveryModes: appendUnique(earlier.deliveryModes, later.deliveryModes, (mode) => mode),
|
|
136
|
+
...((earlier.highestFactRevision ?? later.highestFactRevision) === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: {
|
|
139
|
+
highestFactRevision: Math.max(earlier.highestFactRevision ?? 0, later.highestFactRevision ?? 0)
|
|
140
|
+
})
|
|
132
141
|
};
|
|
133
142
|
return merged;
|
|
134
143
|
}
|
|
@@ -144,32 +153,87 @@ function requireProcessing(mailbox, batchId) {
|
|
|
144
153
|
}
|
|
145
154
|
export function createWorkMailbox(target) {
|
|
146
155
|
return {
|
|
147
|
-
schemaVersion:
|
|
156
|
+
schemaVersion: 2,
|
|
148
157
|
target: copyTarget(target),
|
|
149
158
|
nextSequence: 1,
|
|
150
159
|
processing: null,
|
|
151
|
-
pending:
|
|
160
|
+
pending: {
|
|
161
|
+
normal: null,
|
|
162
|
+
userCorrection: null,
|
|
163
|
+
cursors: { normal: 0, userCorrection: 0 },
|
|
164
|
+
recentDedupeKeys: []
|
|
165
|
+
},
|
|
166
|
+
inputDelivery: null
|
|
152
167
|
};
|
|
153
168
|
}
|
|
154
169
|
export function validateWorkMailbox(value) {
|
|
155
170
|
const mailbox = record(value, "WorkMailbox");
|
|
156
|
-
exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending"], "WorkMailbox");
|
|
157
|
-
if (mailbox.schemaVersion !==
|
|
158
|
-
throw new Error("WorkMailbox must use schemaVersion
|
|
171
|
+
exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending", "inputDelivery"], "WorkMailbox");
|
|
172
|
+
if (mailbox.schemaVersion !== 2)
|
|
173
|
+
throw new Error("WorkMailbox must use schemaVersion 2");
|
|
159
174
|
const target = parseTarget(mailbox.target);
|
|
160
175
|
const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
|
|
161
176
|
const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
|
|
162
|
-
const pending =
|
|
163
|
-
const
|
|
177
|
+
const pending = parsePendingLanes(mailbox.pending);
|
|
178
|
+
for (const cursor of [pending.cursors.normal, pending.cursors.userCorrection]) {
|
|
179
|
+
if (cursor >= nextSequence) {
|
|
180
|
+
throw new Error("WorkMailbox lane cursor must be lower than nextSequence");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (pending.normal !== null && pending.normal.fromSequence <= pending.cursors.normal) {
|
|
184
|
+
throw new Error("WorkMailbox normal pending batch is behind its cursor");
|
|
185
|
+
}
|
|
186
|
+
if (pending.userCorrection !== null
|
|
187
|
+
&& pending.userCorrection.fromSequence <= pending.cursors.userCorrection) {
|
|
188
|
+
throw new Error("WorkMailbox user-correction pending batch is behind its cursor");
|
|
189
|
+
}
|
|
190
|
+
const inputDelivery = mailbox.inputDelivery === null
|
|
191
|
+
? null
|
|
192
|
+
: parseInputDelivery(mailbox.inputDelivery);
|
|
193
|
+
const batches = [
|
|
194
|
+
processing?.batch,
|
|
195
|
+
pending.normal,
|
|
196
|
+
pending.userCorrection,
|
|
197
|
+
inputDelivery?.batch
|
|
198
|
+
].filter((batch) => batch !== null && batch !== undefined);
|
|
164
199
|
for (const batch of batches) {
|
|
165
200
|
if (batch.toSequence >= nextSequence) {
|
|
166
201
|
throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
|
|
167
202
|
}
|
|
168
203
|
}
|
|
169
|
-
|
|
170
|
-
|
|
204
|
+
const ranges = batches
|
|
205
|
+
.map((batch) => [batch.fromSequence, batch.toSequence])
|
|
206
|
+
.sort((left, right) => left[0] - right[0]);
|
|
207
|
+
for (let index = 1; index < ranges.length; index += 1) {
|
|
208
|
+
if (ranges[index - 1][1] >= ranges[index][0]) {
|
|
209
|
+
throw new Error("WorkMailbox batch sequences overlap");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (inputDelivery !== null && target.kind !== "role" && target.kind !== "operator") {
|
|
213
|
+
throw new Error("WorkMailbox inputDelivery requires a Role or Operator target");
|
|
214
|
+
}
|
|
215
|
+
return { schemaVersion: 2, target, nextSequence, processing, pending, inputDelivery };
|
|
216
|
+
}
|
|
217
|
+
function parsePendingLanes(value) {
|
|
218
|
+
const lanes = record(value, "WorkMailbox pending");
|
|
219
|
+
exact(lanes, ["normal", "userCorrection", "cursors", "recentDedupeKeys"], "WorkMailbox pending");
|
|
220
|
+
const cursors = record(lanes.cursors, "WorkMailbox pending cursors");
|
|
221
|
+
exact(cursors, ["normal", "userCorrection"], "WorkMailbox pending cursors");
|
|
222
|
+
const recentDedupeKeys = requireStringArray(lanes.recentDedupeKeys, "WorkMailbox recent dedupe keys");
|
|
223
|
+
if (recentDedupeKeys.length > RECENT_DEDUPE_KEY_LIMIT) {
|
|
224
|
+
throw new Error("WorkMailbox recent dedupe keys exceed the bounded limit");
|
|
171
225
|
}
|
|
172
|
-
return {
|
|
226
|
+
return {
|
|
227
|
+
normal: lanes.normal === null ? null : parseBatch(lanes.normal, "WorkMailbox pending normal"),
|
|
228
|
+
userCorrection: lanes.userCorrection === null
|
|
229
|
+
? null
|
|
230
|
+
: parseBatch(lanes.userCorrection, "WorkMailbox pending userCorrection"),
|
|
231
|
+
cursors: {
|
|
232
|
+
normal: requireInteger(cursors.normal, 0, "WorkMailbox normal cursor"),
|
|
233
|
+
userCorrection: requireInteger(cursors.userCorrection, 0, "WorkMailbox user-correction cursor")
|
|
234
|
+
},
|
|
235
|
+
recentDedupeKeys
|
|
236
|
+
};
|
|
173
237
|
}
|
|
174
238
|
function parseTarget(value) {
|
|
175
239
|
const target = record(value, "WorkMailbox target");
|
|
@@ -206,7 +270,9 @@ function parseTarget(value) {
|
|
|
206
270
|
}
|
|
207
271
|
function parseRef(value, label) {
|
|
208
272
|
const ref = record(value, label);
|
|
209
|
-
const types = [
|
|
273
|
+
const types = [
|
|
274
|
+
"task", "run", "work-item", "input", "session", "message", "event"
|
|
275
|
+
];
|
|
210
276
|
if (!types.includes(ref.type))
|
|
211
277
|
throw new Error(`${label} type is invalid`);
|
|
212
278
|
if (ref.type === "task" || ref.type === "session") {
|
|
@@ -222,8 +288,19 @@ function parseRef(value, label) {
|
|
|
222
288
|
}
|
|
223
289
|
function parseBatch(value, label) {
|
|
224
290
|
const batch = record(value, label);
|
|
225
|
-
const required = [
|
|
226
|
-
|
|
291
|
+
const required = [
|
|
292
|
+
"fromSequence",
|
|
293
|
+
"toSequence",
|
|
294
|
+
"reasons",
|
|
295
|
+
"refs",
|
|
296
|
+
"requestCount",
|
|
297
|
+
"firstQueuedAt",
|
|
298
|
+
"lastQueuedAt",
|
|
299
|
+
"sources",
|
|
300
|
+
"dedupeKeys",
|
|
301
|
+
"deliveryModes"
|
|
302
|
+
];
|
|
303
|
+
exact(batch, batch.highestFactRevision === undefined ? required : [...required, "highestFactRevision"], label);
|
|
227
304
|
const fromSequence = requireInteger(batch.fromSequence, 1, `${label} fromSequence`);
|
|
228
305
|
const toSequence = requireInteger(batch.toSequence, fromSequence, `${label} toSequence`);
|
|
229
306
|
const requestCount = requireInteger(batch.requestCount, 1, `${label} requestCount`);
|
|
@@ -246,16 +323,34 @@ function parseBatch(value, label) {
|
|
|
246
323
|
refs,
|
|
247
324
|
requestCount,
|
|
248
325
|
firstQueuedAt: requireTimestamp(batch.firstQueuedAt, `${label} firstQueuedAt`),
|
|
249
|
-
lastQueuedAt: requireTimestamp(batch.lastQueuedAt, `${label} lastQueuedAt`)
|
|
326
|
+
lastQueuedAt: requireTimestamp(batch.lastQueuedAt, `${label} lastQueuedAt`),
|
|
327
|
+
sources: requireStringArray(batch.sources, `${label} sources`),
|
|
328
|
+
dedupeKeys: requireStringArray(batch.dedupeKeys, `${label} dedupeKeys`),
|
|
329
|
+
deliveryModes: parseDeliveryModes(batch.deliveryModes, `${label} deliveryModes`),
|
|
330
|
+
...(batch.highestFactRevision === undefined
|
|
331
|
+
? {}
|
|
332
|
+
: {
|
|
333
|
+
highestFactRevision: requireInteger(batch.highestFactRevision, 0, `${label} highestFactRevision`)
|
|
334
|
+
})
|
|
250
335
|
};
|
|
251
336
|
return result;
|
|
252
337
|
}
|
|
338
|
+
function parseDeliveryModes(value, label) {
|
|
339
|
+
const modes = requireStringArray(value, label);
|
|
340
|
+
for (const mode of modes) {
|
|
341
|
+
if (mode !== "followup" && mode !== "steer-if-safe" && mode !== "inject") {
|
|
342
|
+
throw new Error(`${label} contains an invalid delivery mode`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return modes;
|
|
346
|
+
}
|
|
253
347
|
function parseProcessing(value) {
|
|
254
348
|
const processing = record(value, "WorkMailbox processing");
|
|
255
|
-
const required = ["batchId", "batch", "owner", "startedAt"];
|
|
349
|
+
const required = ["batchId", "lane", "batch", "owner", "startedAt"];
|
|
256
350
|
exact(processing, processing.executionRef === undefined ? required : [...required, "executionRef"], "WorkMailbox processing");
|
|
257
351
|
const result = {
|
|
258
352
|
batchId: requireString(processing.batchId, "WorkMailbox processing batchId"),
|
|
353
|
+
lane: parseLane(processing.lane, "WorkMailbox processing lane"),
|
|
259
354
|
batch: parseBatch(processing.batch, "WorkMailbox processing batch"),
|
|
260
355
|
owner: requireString(processing.owner, "WorkMailbox processing owner"),
|
|
261
356
|
startedAt: requireTimestamp(processing.startedAt, "WorkMailbox processing startedAt")
|
|
@@ -264,11 +359,97 @@ function parseProcessing(value) {
|
|
|
264
359
|
? result
|
|
265
360
|
: { ...result, executionRef: parseRef(processing.executionRef, "WorkMailbox processing executionRef") };
|
|
266
361
|
}
|
|
362
|
+
function parseInputDelivery(value) {
|
|
363
|
+
const input = record(value, "WorkMailbox inputDelivery");
|
|
364
|
+
const required = [
|
|
365
|
+
"attemptId",
|
|
366
|
+
"lane",
|
|
367
|
+
"mode",
|
|
368
|
+
"batch",
|
|
369
|
+
"owner",
|
|
370
|
+
"status",
|
|
371
|
+
"startedAt",
|
|
372
|
+
"executionRef"
|
|
373
|
+
];
|
|
374
|
+
const optional = [
|
|
375
|
+
...(input.pushedAt === undefined ? [] : ["pushedAt"]),
|
|
376
|
+
...(input.unknownReason === undefined ? [] : ["unknownReason"]),
|
|
377
|
+
...(input.providerFence === undefined ? [] : ["providerFence"])
|
|
378
|
+
];
|
|
379
|
+
exact(input, [...required, ...optional], "WorkMailbox inputDelivery");
|
|
380
|
+
const lane = parseLane(input.lane, "WorkMailbox inputDelivery lane");
|
|
381
|
+
const mode = parseDeliveryMode(input.mode, "WorkMailbox inputDelivery mode");
|
|
382
|
+
if (input.status !== "dispatching" && input.status !== "delivery-unknown") {
|
|
383
|
+
throw new Error("WorkMailbox inputDelivery status is invalid");
|
|
384
|
+
}
|
|
385
|
+
const status = input.status;
|
|
386
|
+
if ((status === "delivery-unknown") !== (input.unknownReason !== undefined)) {
|
|
387
|
+
throw new Error("WorkMailbox delivery-unknown requires exactly one unknownReason");
|
|
388
|
+
}
|
|
389
|
+
const result = {
|
|
390
|
+
attemptId: requireString(input.attemptId, "WorkMailbox inputDelivery attemptId"),
|
|
391
|
+
lane,
|
|
392
|
+
mode,
|
|
393
|
+
batch: parseBatch(input.batch, "WorkMailbox inputDelivery batch"),
|
|
394
|
+
owner: requireString(input.owner, "WorkMailbox inputDelivery owner"),
|
|
395
|
+
status,
|
|
396
|
+
startedAt: requireTimestamp(input.startedAt, "WorkMailbox inputDelivery startedAt"),
|
|
397
|
+
executionRef: parseRef(input.executionRef, "WorkMailbox inputDelivery executionRef"),
|
|
398
|
+
...(input.pushedAt === undefined
|
|
399
|
+
? {}
|
|
400
|
+
: { pushedAt: requireTimestamp(input.pushedAt, "WorkMailbox inputDelivery pushedAt") }),
|
|
401
|
+
...(input.unknownReason === undefined
|
|
402
|
+
? {}
|
|
403
|
+
: { unknownReason: requireString(input.unknownReason, "WorkMailbox inputDelivery unknownReason") }),
|
|
404
|
+
...(input.providerFence === undefined
|
|
405
|
+
? {}
|
|
406
|
+
: { providerFence: parseProviderFence(input.providerFence) })
|
|
407
|
+
};
|
|
408
|
+
if (result.pushedAt !== undefined
|
|
409
|
+
&& Date.parse(result.pushedAt) < Date.parse(result.startedAt)) {
|
|
410
|
+
throw new Error("WorkMailbox inputDelivery pushedAt is earlier than startedAt");
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
function parseProviderFence(value) {
|
|
415
|
+
const fence = record(value, "WorkMailbox inputDelivery providerFence");
|
|
416
|
+
exact(fence, fence.nativeTurnId === undefined
|
|
417
|
+
? ["conversationId", "activationId"]
|
|
418
|
+
: ["conversationId", "activationId", "nativeTurnId"], "WorkMailbox inputDelivery providerFence");
|
|
419
|
+
return {
|
|
420
|
+
conversationId: requireString(fence.conversationId, "Provider Conversation id"),
|
|
421
|
+
activationId: requireString(fence.activationId, "Provider Activation id"),
|
|
422
|
+
...(fence.nativeTurnId === undefined
|
|
423
|
+
? {}
|
|
424
|
+
: { nativeTurnId: requireString(fence.nativeTurnId, "Provider Turn id") })
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function parseLane(value, label) {
|
|
428
|
+
if (value !== "normal" && value !== "user-correction") {
|
|
429
|
+
throw new Error(`${label} is invalid`);
|
|
430
|
+
}
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
function parseDeliveryMode(value, label) {
|
|
434
|
+
if (value !== "followup" && value !== "steer-if-safe" && value !== "inject") {
|
|
435
|
+
throw new Error(`${label} is invalid`);
|
|
436
|
+
}
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
267
439
|
export function enqueueSignal(mailbox, signal) {
|
|
268
440
|
const reason = requireText(signal.reason, "signal reason");
|
|
269
441
|
const occurredAt = requireText(signal.occurredAt, "signal occurredAt");
|
|
442
|
+
const lane = signal.lane ?? "normal";
|
|
443
|
+
const laneKey = lane === "normal" ? "normal" : "userCorrection";
|
|
444
|
+
const deliveryMode = signal.deliveryMode ?? "followup";
|
|
445
|
+
const source = requireText(signal.source ?? "yui", "signal source");
|
|
270
446
|
const refs = signal.refs.map(copyRef);
|
|
271
447
|
const sequence = mailbox.nextSequence;
|
|
448
|
+
const dedupeKey = signal.dedupeKey === undefined
|
|
449
|
+
? `sequence:${sequence}`
|
|
450
|
+
: requireText(signal.dedupeKey, "signal dedupeKey");
|
|
451
|
+
if (mailboxContainsDedupeKey(mailbox, dedupeKey))
|
|
452
|
+
return mailbox;
|
|
272
453
|
const incoming = {
|
|
273
454
|
fromSequence: sequence,
|
|
274
455
|
toSequence: sequence,
|
|
@@ -276,38 +457,98 @@ export function enqueueSignal(mailbox, signal) {
|
|
|
276
457
|
refs: appendUnique([], refs, mailboxEntityRefKey),
|
|
277
458
|
requestCount: 1,
|
|
278
459
|
firstQueuedAt: occurredAt,
|
|
279
|
-
lastQueuedAt: occurredAt
|
|
460
|
+
lastQueuedAt: occurredAt,
|
|
461
|
+
sources: [source],
|
|
462
|
+
dedupeKeys: [dedupeKey],
|
|
463
|
+
deliveryModes: [deliveryMode],
|
|
464
|
+
...(signal.factRevision === undefined
|
|
465
|
+
? {}
|
|
466
|
+
: { highestFactRevision: requireInteger(signal.factRevision, 0, "signal factRevision") })
|
|
280
467
|
};
|
|
281
468
|
return {
|
|
282
469
|
...mailbox,
|
|
283
470
|
nextSequence: sequence + 1,
|
|
284
|
-
pending:
|
|
285
|
-
|
|
286
|
-
:
|
|
471
|
+
pending: {
|
|
472
|
+
...mailbox.pending,
|
|
473
|
+
[laneKey]: mailbox.pending[laneKey] === null
|
|
474
|
+
? incoming
|
|
475
|
+
: mergeBatches(mailbox.pending[laneKey], incoming)
|
|
476
|
+
}
|
|
287
477
|
};
|
|
288
478
|
}
|
|
479
|
+
function mailboxContainsDedupeKey(mailbox, key) {
|
|
480
|
+
if (mailbox.pending.recentDedupeKeys.includes(key))
|
|
481
|
+
return true;
|
|
482
|
+
return [
|
|
483
|
+
mailbox.pending.normal,
|
|
484
|
+
mailbox.pending.userCorrection,
|
|
485
|
+
mailbox.processing?.batch,
|
|
486
|
+
mailbox.inputDelivery?.batch
|
|
487
|
+
].some((batch) => batch?.dedupeKeys.includes(key) === true);
|
|
488
|
+
}
|
|
489
|
+
export function pendingLane(mailbox, lane) {
|
|
490
|
+
return lane === "normal" ? mailbox.pending.normal : mailbox.pending.userCorrection;
|
|
491
|
+
}
|
|
492
|
+
export function mailboxHasPending(mailbox) {
|
|
493
|
+
return mailbox.pending.normal !== null || mailbox.pending.userCorrection !== null;
|
|
494
|
+
}
|
|
495
|
+
export function nextPendingLane(mailbox) {
|
|
496
|
+
if (mailbox.pending.userCorrection !== null)
|
|
497
|
+
return "user-correction";
|
|
498
|
+
return mailbox.pending.normal === null ? null : "normal";
|
|
499
|
+
}
|
|
500
|
+
export function nextPendingBatch(mailbox) {
|
|
501
|
+
const lane = nextPendingLane(mailbox);
|
|
502
|
+
return lane === null ? null : pendingLane(mailbox, lane);
|
|
503
|
+
}
|
|
504
|
+
export function mailboxHasWork(mailbox) {
|
|
505
|
+
return mailbox.processing !== null
|
|
506
|
+
|| mailbox.inputDelivery !== null
|
|
507
|
+
|| mailboxHasPending(mailbox);
|
|
508
|
+
}
|
|
509
|
+
export function mailboxBatches(mailbox) {
|
|
510
|
+
return [
|
|
511
|
+
mailbox.processing?.batch,
|
|
512
|
+
mailbox.inputDelivery?.batch,
|
|
513
|
+
mailbox.pending.userCorrection,
|
|
514
|
+
mailbox.pending.normal
|
|
515
|
+
].filter((batch) => batch !== null && batch !== undefined);
|
|
516
|
+
}
|
|
289
517
|
function mailboxTaskRecordKind(type) {
|
|
290
518
|
switch (type) {
|
|
291
519
|
case "run": return "agentRun";
|
|
292
520
|
case "work-item": return "workItem";
|
|
293
521
|
case "input": return "inputRequest";
|
|
294
522
|
case "message": return "message";
|
|
523
|
+
case "event": return "event";
|
|
295
524
|
}
|
|
296
525
|
}
|
|
297
526
|
export function claimPending(mailbox, options) {
|
|
298
527
|
if (mailbox.processing !== null) {
|
|
299
528
|
throw new Error("Mailbox is already processing a batch");
|
|
300
529
|
}
|
|
301
|
-
|
|
530
|
+
const lane = options.lane ?? nextPendingLane(mailbox);
|
|
531
|
+
if (lane === null) {
|
|
302
532
|
throw new Error("Mailbox has no pending work to claim");
|
|
303
533
|
}
|
|
534
|
+
const batch = pendingLane(mailbox, lane);
|
|
535
|
+
if (batch === null)
|
|
536
|
+
throw new Error(`Mailbox has no pending ${lane} work to claim`);
|
|
304
537
|
const processing = {
|
|
305
538
|
batchId: requireText(options.batchId, "batch id"),
|
|
306
|
-
|
|
539
|
+
lane,
|
|
540
|
+
batch,
|
|
307
541
|
owner: requireText(options.owner, "claim owner"),
|
|
308
542
|
startedAt: requireText(options.startedAt, "claim startedAt")
|
|
309
543
|
};
|
|
310
|
-
return {
|
|
544
|
+
return {
|
|
545
|
+
...mailbox,
|
|
546
|
+
processing,
|
|
547
|
+
pending: {
|
|
548
|
+
...mailbox.pending,
|
|
549
|
+
[lane === "normal" ? "normal" : "userCorrection"]: null
|
|
550
|
+
}
|
|
551
|
+
};
|
|
311
552
|
}
|
|
312
553
|
export function bindExecution(mailbox, batchId, executionRef) {
|
|
313
554
|
const processing = requireProcessing(mailbox, batchId);
|
|
@@ -317,13 +558,152 @@ export function bindExecution(mailbox, batchId, executionRef) {
|
|
|
317
558
|
};
|
|
318
559
|
}
|
|
319
560
|
export function completeProcessing(mailbox, batchId) {
|
|
320
|
-
requireProcessing(mailbox, batchId);
|
|
321
|
-
|
|
561
|
+
const processing = requireProcessing(mailbox, batchId);
|
|
562
|
+
const lane = processing.lane;
|
|
563
|
+
return validateWorkMailbox({
|
|
564
|
+
...mailbox,
|
|
565
|
+
processing: null,
|
|
566
|
+
pending: advanceLaneCursor(mailbox.pending, lane, processing.batch)
|
|
567
|
+
});
|
|
322
568
|
}
|
|
323
569
|
export function releaseProcessing(mailbox, batchId) {
|
|
324
570
|
const processing = requireProcessing(mailbox, batchId);
|
|
325
|
-
const
|
|
571
|
+
const lane = processing.lane;
|
|
572
|
+
const key = lane === "normal" ? "normal" : "userCorrection";
|
|
573
|
+
const released = mailbox.pending[key] === null
|
|
326
574
|
? processing.batch
|
|
327
|
-
: mergeBatches(processing.batch, mailbox.pending);
|
|
328
|
-
return {
|
|
575
|
+
: mergeBatches(processing.batch, mailbox.pending[key]);
|
|
576
|
+
return {
|
|
577
|
+
...mailbox,
|
|
578
|
+
processing: null,
|
|
579
|
+
pending: { ...mailbox.pending, [key]: released }
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function requireInputDelivery(mailbox, attemptId) {
|
|
583
|
+
const id = requireText(attemptId, "input delivery attempt id");
|
|
584
|
+
if (mailbox.inputDelivery === null)
|
|
585
|
+
throw new Error("Mailbox has no input delivery");
|
|
586
|
+
if (mailbox.inputDelivery.attemptId !== id) {
|
|
587
|
+
throw new Error(`Mailbox input delivery does not match ${id}`);
|
|
588
|
+
}
|
|
589
|
+
return mailbox.inputDelivery;
|
|
590
|
+
}
|
|
591
|
+
export function claimInputDelivery(mailbox, options) {
|
|
592
|
+
if (mailbox.inputDelivery !== null) {
|
|
593
|
+
if (mailbox.inputDelivery.attemptId === options.attemptId)
|
|
594
|
+
return mailbox;
|
|
595
|
+
throw new Error("Mailbox already has an input delivery");
|
|
596
|
+
}
|
|
597
|
+
const batch = pendingLane(mailbox, options.lane);
|
|
598
|
+
if (batch === null)
|
|
599
|
+
throw new Error(`Mailbox has no pending ${options.lane} input`);
|
|
600
|
+
const inputDelivery = {
|
|
601
|
+
attemptId: requireText(options.attemptId, "input delivery attempt id"),
|
|
602
|
+
lane: options.lane,
|
|
603
|
+
mode: options.mode,
|
|
604
|
+
batch,
|
|
605
|
+
owner: requireText(options.owner, "input delivery owner"),
|
|
606
|
+
status: "dispatching",
|
|
607
|
+
startedAt: requireTimestamp(options.startedAt, "input delivery startedAt"),
|
|
608
|
+
executionRef: copyRef(options.executionRef),
|
|
609
|
+
...(options.providerFence === undefined
|
|
610
|
+
? {}
|
|
611
|
+
: { providerFence: parseProviderFence(options.providerFence) })
|
|
612
|
+
};
|
|
613
|
+
return validateWorkMailbox({
|
|
614
|
+
...mailbox,
|
|
615
|
+
pending: {
|
|
616
|
+
...mailbox.pending,
|
|
617
|
+
[options.lane === "normal" ? "normal" : "userCorrection"]: null
|
|
618
|
+
},
|
|
619
|
+
inputDelivery
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
export function markInputDeliveryPushed(mailbox, attemptId, pushedAt) {
|
|
623
|
+
const delivery = requireInputDelivery(mailbox, attemptId);
|
|
624
|
+
if (delivery.pushedAt !== undefined)
|
|
625
|
+
return mailbox;
|
|
626
|
+
const timestamp = requireTimestamp(pushedAt.toISOString(), "input delivery pushedAt");
|
|
627
|
+
return validateWorkMailbox({
|
|
628
|
+
...mailbox,
|
|
629
|
+
inputDelivery: { ...delivery, pushedAt: timestamp }
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
export function markInputDeliveryUnknown(mailbox, attemptId, reason, observedAt) {
|
|
633
|
+
const delivery = requireInputDelivery(mailbox, attemptId);
|
|
634
|
+
const timestamp = requireTimestamp(observedAt.toISOString(), "delivery unknown observedAt");
|
|
635
|
+
return validateWorkMailbox({
|
|
636
|
+
...mailbox,
|
|
637
|
+
inputDelivery: {
|
|
638
|
+
...delivery,
|
|
639
|
+
status: "delivery-unknown",
|
|
640
|
+
unknownReason: requireText(reason, "delivery unknown reason"),
|
|
641
|
+
pushedAt: delivery.pushedAt ?? timestamp
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
export function completeInputDelivery(mailbox, attemptId, _acceptedAt) {
|
|
646
|
+
const delivery = requireInputDelivery(mailbox, attemptId);
|
|
647
|
+
return validateWorkMailbox({
|
|
648
|
+
...mailbox,
|
|
649
|
+
inputDelivery: null,
|
|
650
|
+
pending: advanceLaneCursor(mailbox.pending, delivery.lane, delivery.batch)
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
export function consumePendingBatch(mailbox, lane) {
|
|
654
|
+
const key = lane === "normal" ? "normal" : "userCorrection";
|
|
655
|
+
const batch = mailbox.pending[key];
|
|
656
|
+
if (batch === null)
|
|
657
|
+
throw new Error(`Mailbox has no pending ${lane} batch`);
|
|
658
|
+
return validateWorkMailbox({
|
|
659
|
+
...mailbox,
|
|
660
|
+
pending: {
|
|
661
|
+
...advanceLaneCursor(mailbox.pending, lane, batch),
|
|
662
|
+
[key]: null
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function advanceLaneCursor(pending, lane, batch) {
|
|
667
|
+
const key = lane === "normal" ? "normal" : "userCorrection";
|
|
668
|
+
const recentDedupeKeys = appendUnique(pending.recentDedupeKeys, batch.dedupeKeys, (value) => value).slice(-RECENT_DEDUPE_KEY_LIMIT);
|
|
669
|
+
return {
|
|
670
|
+
...pending,
|
|
671
|
+
cursors: {
|
|
672
|
+
...pending.cursors,
|
|
673
|
+
[key]: Math.max(pending.cursors[key], batch.toSequence)
|
|
674
|
+
},
|
|
675
|
+
recentDedupeKeys
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
export function releaseInputDelivery(mailbox, attemptId) {
|
|
679
|
+
const delivery = requireInputDelivery(mailbox, attemptId);
|
|
680
|
+
if (delivery.pushedAt !== undefined || delivery.status === "delivery-unknown") {
|
|
681
|
+
throw new Error("A pushed or delivery-unknown input cannot return to pending");
|
|
682
|
+
}
|
|
683
|
+
const key = delivery.lane === "normal" ? "normal" : "userCorrection";
|
|
684
|
+
return validateWorkMailbox({
|
|
685
|
+
...mailbox,
|
|
686
|
+
pending: {
|
|
687
|
+
...mailbox.pending,
|
|
688
|
+
[key]: mailbox.pending[key] === null
|
|
689
|
+
? delivery.batch
|
|
690
|
+
: mergeBatches(delivery.batch, mailbox.pending[key])
|
|
691
|
+
},
|
|
692
|
+
inputDelivery: null
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/** Exact Provider readback proved that a previously uncertain mutation is absent. */
|
|
696
|
+
export function resolveInputDeliveryNotAccepted(mailbox, attemptId) {
|
|
697
|
+
const delivery = requireInputDelivery(mailbox, attemptId);
|
|
698
|
+
const key = delivery.lane === "normal" ? "normal" : "userCorrection";
|
|
699
|
+
return validateWorkMailbox({
|
|
700
|
+
...mailbox,
|
|
701
|
+
pending: {
|
|
702
|
+
...mailbox.pending,
|
|
703
|
+
[key]: mailbox.pending[key] === null
|
|
704
|
+
? delivery.batch
|
|
705
|
+
: mergeBatches(delivery.batch, mailbox.pending[key])
|
|
706
|
+
},
|
|
707
|
+
inputDelivery: null
|
|
708
|
+
});
|
|
329
709
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { completeProcessing, createWorkMailbox, enqueueSignal, mailboxEntityRefKey } from "./workMailbox.js";
|
|
1
|
+
import { completeProcessing, consumePendingBatch, createWorkMailbox, enqueueSignal, mailboxBatches, mailboxEntityRefKey } from "./workMailbox.js";
|
|
2
2
|
/** Atomically useful when called inside the caller's TaskStore transaction. */
|
|
3
|
-
export function enqueueWork(store, target, reason, occurredAt, refs = []) {
|
|
3
|
+
export function enqueueWork(store, target, reason, occurredAt, refs = [], metadata = {}) {
|
|
4
4
|
const mailbox = store.getWorkMailbox(target) ?? createWorkMailbox(target);
|
|
5
5
|
const queued = enqueueSignal(mailbox, {
|
|
6
6
|
reason,
|
|
7
7
|
refs,
|
|
8
|
-
occurredAt: timestamp(occurredAt)
|
|
8
|
+
occurredAt: timestamp(occurredAt),
|
|
9
|
+
...metadata
|
|
9
10
|
});
|
|
10
11
|
store.saveWorkMailbox(queued);
|
|
11
12
|
return queued;
|
|
@@ -59,17 +60,18 @@ export function settleExactWorkExecution(store, target, executionRef) {
|
|
|
59
60
|
const mailbox = store.getWorkMailbox(target);
|
|
60
61
|
if (mailbox === null)
|
|
61
62
|
return "absent";
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
63
|
+
const matching = mailboxBatches(mailbox).filter((batch) => batch.refs.some((ref) => mailboxEntityRefKey(ref) === mailboxEntityRefKey(executionRef)));
|
|
64
|
+
if (matching.length === 0)
|
|
64
65
|
return "absent";
|
|
65
|
-
|
|
66
|
-
if (!matches)
|
|
67
|
-
return "absent";
|
|
68
|
-
if (pending.requestCount !== 1) {
|
|
66
|
+
if (matching.length !== 1 || matching[0].requestCount !== 1) {
|
|
69
67
|
throw new Error(`Cannot settle a merged pending mailbox batch for ${targetLabel(target)}: `
|
|
70
68
|
+ `${executionRef.type}/${executionRef.id}.`);
|
|
71
69
|
}
|
|
72
|
-
|
|
70
|
+
const pending = matching[0];
|
|
71
|
+
const pendingKey = mailbox.pending.normal === pending ? "normal" : "userCorrection";
|
|
72
|
+
if (mailbox.pending[pendingKey] !== pending)
|
|
73
|
+
return "absent";
|
|
74
|
+
store.saveWorkMailbox(consumePendingBatch(mailbox, pendingKey === "normal" ? "normal" : "user-correction"));
|
|
73
75
|
return "pending";
|
|
74
76
|
}
|
|
75
77
|
function timestamp(value) {
|