@zq-silk/yui 2.16.0 → 2.18.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/dist/cli/commandCatalog.js +19 -12
- package/dist/cli/interactionCandidates.js +20 -5
- package/dist/cli/interactionPolicy.js +5 -4
- package/dist/cli/roleWizard.js +7 -0
- package/dist/commands/controllerCommands.js +13 -4
- package/dist/commands/durableJobCommands.js +50 -3
- package/dist/commands/globalRoleCommands.js +31 -14
- package/dist/commands/inboxCommands.js +1 -0
- package/dist/commands/operatorCommands.js +12 -2
- package/dist/commands/profileCommands.js +19 -2
- package/dist/commands/roleConfiguration.js +3 -0
- package/dist/commands/taskCommands.js +16 -0
- package/dist/commands/taskRoleRuntimeStatus.js +3 -0
- package/dist/controlPlaneCli.js +21 -4
- package/dist/controller/agentJobControl.js +9 -1
- package/dist/controller/clientRuntime.js +34 -13
- package/dist/controller/controller.js +35 -22
- package/dist/controller/diagnosticJournal.js +214 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +174 -105
- package/dist/controller/globalInputDelivery.js +19 -9
- package/dist/controller/globalRuntimeAttention.js +11 -3
- package/dist/controller/jobControl.js +1 -1
- package/dist/controller/jobDefinitionControl.js +24 -13
- package/dist/controller/jobSupervisor.js +4 -0
- package/dist/controller/providerRetryAdmission.js +9 -0
- package/dist/controller/providerRetryDelivery.js +16 -0
- package/dist/controller/runtime.js +80 -4
- package/dist/controller/runtimeLaunchCoordinator.js +3 -1
- package/dist/controller/workerSchedulerStore.js +24 -0
- package/dist/core/boundedRpc.js +4 -0
- package/dist/core/controllerServer.js +1 -1
- package/dist/core/controllerTelemetry.js +92 -19
- package/dist/executor/fileRoleLaunchPlanner.js +3 -0
- package/dist/job/globalShell.js +51 -0
- package/dist/job/jobBusinessResult.js +4 -3
- package/dist/job/jobDefinition.js +6 -1
- package/dist/job/jobRunner.js +9 -0
- package/dist/job/scheduleService.js +2 -0
- package/dist/message/globalInboxRecovery.js +30 -19
- package/dist/message/globalProviderRetry.js +7 -1
- package/dist/message/messageContinuation.js +4 -0
- package/dist/output/rolePresentation.js +3 -0
- package/dist/profile/agentProfile.js +8 -0
- package/dist/profile/agentProfileRuntime.js +16 -1
- package/dist/review/reviewDecision.js +6 -0
- package/dist/role/availability.js +28 -0
- package/dist/role/role.js +6 -0
- package/dist/runtime/builtinAgentErrorMappers.js +4 -0
- package/dist/runtime/codexNativeAccess.js +21 -4
- package/dist/runtime/globalSessionHealth.js +39 -0
- package/dist/runtime/tmuxAdapters.js +5 -0
- package/dist/scheduler/activeRoleRunDelivery.js +9 -0
- package/dist/scheduler/leaderWakeupProcessor.js +8 -1
- package/dist/scheduler/roleRunLiveness.js +3 -0
- package/dist/scheduler/roleRunStall.js +10 -10
- package/dist/storage/persistenceWorker.js +41 -19
- package/dist/storage/sqliteSchema.js +43 -9
- package/dist/storage/sqliteStore.js +86 -8
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +25 -11
- package/dist/storage/taskReadRevisionSchema.js +39 -0
- package/dist/web/assets/client/app/catalog.js +0 -29
- package/dist/web/assets/client/app/jobs.js +115 -0
- package/dist/web/assets/client/app/main.js +32 -28
- package/dist/web/assets/client/app/manage.js +1 -0
- package/dist/web/assets/client/app/settings.js +1 -5
- package/dist/web/assets/client/app/taskView.js +7 -11
- package/dist/web/assets/client/i18n/en.js +265 -36
- package/dist/web/assets/client/i18n/zh-CN.js +264 -35
- package/dist/web/assets/client/index.js +4 -0
- package/dist/web/assets/client/layout/manageShell.js +61 -15
- package/dist/web/assets/client/layout/router.js +2 -1
- package/dist/web/assets/client/layout/shortcuts.js +2 -2
- package/dist/web/assets/client/layout/workspace.js +99 -112
- package/dist/web/assets/client/lib/prefs.js +1 -1
- package/dist/web/assets/client/ui/primitives.js +15 -2
- package/dist/web/assets/client/views/dock/conversation.js +421 -123
- package/dist/web/assets/client/views/dock/session.js +229 -0
- package/dist/web/assets/client/views/dock/terminal.js +48 -39
- package/dist/web/assets/client/views/jobs.js +1042 -160
- package/dist/web/assets/client/views/overview.js +2 -2
- package/dist/web/assets/client/views/sidebar.js +3 -0
- package/dist/web/assets/client/views/task/page.js +2 -12
- package/dist/web/assets/shared/manageNav.js +3 -1
- package/dist/web/assets/shell/appRail.js +2 -1
- package/dist/web/assets/shell/dock.js +9 -27
- package/dist/web/assets/shell/manage.js +7 -2
- package/dist/web/assets/shell/models.js +4 -3
- package/dist/web/assets/shell/settingsContent.js +0 -3
- package/dist/web/assets/shell/sidebar.js +0 -1
- package/dist/web/assets/styles/views/conversation.js +96 -17
- package/dist/web/assets/styles/views/dock.js +17 -5
- package/dist/web/assets/styles/views/jobs.js +301 -12
- package/dist/web/assets/styles/views/sidebar.js +1 -2
- package/dist/web/assets/styles/views/task.js +1 -2
- package/dist/web/assets/styles/views/terminal.js +12 -8
- package/dist/web/webConversation.js +1 -0
- package/dist/web/webServer.js +28 -1
- package/dist/web/webSettings.js +10 -2
- package/docs/global-shell-jobs.md +78 -0
- package/docs/job-runs.md +6 -3
- package/docs/managed-turn-and-session-runtime.md +84 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +57 -0
- package/docs/release-workflow.md +67 -0
- package/docs/release-workflow.zh-CN.md +48 -0
- package/docs/roles-and-configuration.md +45 -1
- package/docs/roles-and-configuration.zh-CN.md +35 -0
- package/docs/storage-baseline.md +19 -1
- package/docs/storage-baseline.zh-CN.md +10 -1
- package/docs/testing/review-evaluation.md +35 -1
- package/docs/testing/review-evaluation.zh-CN.md +25 -1
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/execution.md +19 -8
- package/skills/yui-reviewer/SKILL.md +13 -2
- package/skills/yui-reviewer/VERSION +1 -1
|
@@ -3,6 +3,7 @@ import { assertJobRoleRunnable } from "../job/agentRoleScope.js";
|
|
|
3
3
|
import { recordJobAgentObservation } from "../job/agentJobObservation.js";
|
|
4
4
|
import { isDeepStrictEqual } from "node:util";
|
|
5
5
|
import { nonReplayableMessageWake, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
6
|
+
import { ControllerWorkObserver, compactWorkObservation } from "../core/controllerTelemetry.js";
|
|
6
7
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
7
8
|
import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
8
9
|
import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
|
|
@@ -34,6 +35,8 @@ import { recordTaskAgentError } from "./taskAgentError.js";
|
|
|
34
35
|
import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
|
|
35
36
|
import { checkInboxTurn } from "./inboxTurnCheck.js";
|
|
36
37
|
import { queueOperatorNotification } from "./operatorNotification.js";
|
|
38
|
+
import { reconcileProjectSync } from "../job/projectSync.js";
|
|
39
|
+
import { serializeError } from "../core/boundedRpc.js";
|
|
37
40
|
import { claimOperatorAttention } from "../scheduler/operatorEvent.js";
|
|
38
41
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
39
42
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
@@ -65,6 +68,26 @@ export class FileSchedulerStoreAdapter {
|
|
|
65
68
|
telemetry;
|
|
66
69
|
drivers;
|
|
67
70
|
snapshotExecutionLaneWorkspace;
|
|
71
|
+
#reads = new ControllerWorkObserver();
|
|
72
|
+
#projectionCache = { hits: 0, rebuilds: 0, invalidations: 0 };
|
|
73
|
+
readObservationMetrics() {
|
|
74
|
+
return { ...this.#reads.snapshot(), projectionCache: { ...this.#projectionCache } };
|
|
75
|
+
}
|
|
76
|
+
compactReadObservationMetrics() {
|
|
77
|
+
return compactWorkObservation(this.#reads);
|
|
78
|
+
}
|
|
79
|
+
#observeRead(phase, target, read) {
|
|
80
|
+
const finish = this.#reads.start(phase, target);
|
|
81
|
+
try {
|
|
82
|
+
const result = read();
|
|
83
|
+
finish();
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
finish(true);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
68
91
|
/** Diagnostic telemetry is optional and never participates in runtime truth. */
|
|
69
92
|
constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry(), snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspaceSync) {
|
|
70
93
|
this.store = store;
|
|
@@ -72,6 +95,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
72
95
|
this.drivers = drivers;
|
|
73
96
|
this.snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspace;
|
|
74
97
|
}
|
|
98
|
+
/** Registry maintenance only; this never executes a Job or starts a process. */
|
|
99
|
+
reconcileProjectSync(now) {
|
|
100
|
+
const errors = [];
|
|
101
|
+
reconcileProjectSync(this.store, now, error => errors.push(serializeError(error)));
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
75
104
|
observeAgentHostObservation(event, now = new Date()) {
|
|
76
105
|
// Match canonical ingress: archive and Host fact settlement share the
|
|
77
106
|
// same write boundary, including the original transport envelope.
|
|
@@ -168,7 +197,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
168
197
|
&& sessions?.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId;
|
|
169
198
|
if (!runFolded && !(isSteer && input.fence.runId === undefined))
|
|
170
199
|
return "obsolete";
|
|
171
|
-
if (runFolded && !hasPersistedRuntimeObservation(store
|
|
200
|
+
if (runFolded && !hasPersistedRuntimeObservation(store, input) && run.status === "active") {
|
|
172
201
|
const updated = appendRunInput(run, createRunInput({
|
|
173
202
|
source: input.fence.receiptId.startsWith("native-input:")
|
|
174
203
|
? { type: "provider", channel: "visible-input" }
|
|
@@ -196,7 +225,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
196
225
|
if ((input.kind === "input.rejected" || input.kind === "input.delivery-unknown")
|
|
197
226
|
&& input.fence.receiptId?.startsWith("steer:") === true) {
|
|
198
227
|
return this.store.transaction((store) => {
|
|
199
|
-
if (hasPersistedRuntimeObservation(store
|
|
228
|
+
if (hasPersistedRuntimeObservation(store, input))
|
|
200
229
|
return "applied";
|
|
201
230
|
const recorded = this.recordSteerControlOutcome(store, input.fence.receiptId, input.kind === "input.rejected" ? "rejected" : "delivery-unknown", now);
|
|
202
231
|
if (!recorded) {
|
|
@@ -217,7 +246,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
217
246
|
// together, including a retry after a previously interrupted fold.
|
|
218
247
|
if (!isRunTerminalObservation(input)
|
|
219
248
|
&& input.kind !== "turn.accepted" && input.kind !== "input.accepted"
|
|
220
|
-
&& hasPersistedRuntimeObservation(this.store
|
|
249
|
+
&& hasPersistedRuntimeObservation(this.store, input))
|
|
221
250
|
return "applied";
|
|
222
251
|
let outcome;
|
|
223
252
|
switch (input.kind) {
|
|
@@ -330,7 +359,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
330
359
|
recordCanonicalObservationObsolete(store, input, "steer-receipt-fence-mismatch", now);
|
|
331
360
|
return "obsolete";
|
|
332
361
|
}
|
|
333
|
-
if (hasPersistedRuntimeObservation(store
|
|
362
|
+
if (hasPersistedRuntimeObservation(store, input))
|
|
334
363
|
return "applied";
|
|
335
364
|
const target = { kind: "role", taskId, roleName: "leader" };
|
|
336
365
|
const mailbox = store.getWorkMailbox(target);
|
|
@@ -408,7 +437,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
408
437
|
}
|
|
409
438
|
if (input.kind === "turn.accepted") {
|
|
410
439
|
if (input.authority === "transport" && matches) {
|
|
411
|
-
const message = store
|
|
440
|
+
const message = globalMessageForReceipt(store, fence.roleName, receipt);
|
|
412
441
|
if (message !== undefined)
|
|
413
442
|
store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at), "transport"));
|
|
414
443
|
return "applied";
|
|
@@ -428,9 +457,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
428
457
|
}
|
|
429
458
|
else if (binding.run?.status !== "accepted")
|
|
430
459
|
return "applied";
|
|
431
|
-
const message = store
|
|
432
|
-
|
|
433
|
-
&& binding.retry.input.messageId === entry.id);
|
|
460
|
+
const message = globalMessageForReceipt(store, fence.roleName, receipt, "global-input", binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
|
|
461
|
+
? binding.retry.input.messageId : undefined);
|
|
434
462
|
if (message !== undefined)
|
|
435
463
|
store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at)));
|
|
436
464
|
}
|
|
@@ -443,9 +471,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
443
471
|
if (input.kind === "turn.completed" && binding.run != null
|
|
444
472
|
&& ["submitting", "delivery-unknown"].includes(binding.run.status)) {
|
|
445
473
|
binding = acceptProviderTurn(binding, { attemptId: receipt, nativeTurnId: fence.nativeTurnId, acceptedAt: at });
|
|
446
|
-
const message = store
|
|
447
|
-
|
|
448
|
-
&& binding.retry.input.messageId === entry.id);
|
|
474
|
+
const message = globalMessageForReceipt(store, fence.roleName, receipt, "global-input", binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
|
|
475
|
+
? binding.retry.input.messageId : undefined);
|
|
449
476
|
if (message !== undefined)
|
|
450
477
|
store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at)));
|
|
451
478
|
}
|
|
@@ -466,7 +493,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
466
493
|
if (input.authority !== "provider-structured" || binding.run?.nativeTurnId !== fence.nativeTurnId
|
|
467
494
|
|| binding.run?.status !== "accepted")
|
|
468
495
|
return "obsolete";
|
|
469
|
-
const message = store
|
|
496
|
+
const message = globalMessageForReceipt(store, fence.roleName, receipt, "steer");
|
|
470
497
|
if (message !== undefined && message.inputControl !== undefined) {
|
|
471
498
|
const outcome = input.kind === "input.accepted" ? "accepted"
|
|
472
499
|
: input.kind === "input.rejected" ? "rejected" : "delivery-unknown";
|
|
@@ -548,7 +575,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
548
575
|
}
|
|
549
576
|
validateCanonicalRunObservation(input, now) {
|
|
550
577
|
return this.store.transaction((store) => {
|
|
551
|
-
if (hasPersistedRuntimeObservation(store
|
|
578
|
+
if (hasPersistedRuntimeObservation(store, input)) {
|
|
552
579
|
return "applied";
|
|
553
580
|
}
|
|
554
581
|
const run = input.fence.runId === undefined ? null : store.getRun(input.fence.taskId, input.fence.runId);
|
|
@@ -565,7 +592,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
565
592
|
? "applied" : "obsolete";
|
|
566
593
|
}
|
|
567
594
|
const knownContinuation = (input.kind.startsWith("continuation.") || input.kind === "observer.health")
|
|
568
|
-
&& projectProviderContinuations(store.
|
|
595
|
+
&& projectProviderContinuations(store.listEventsByType(input.fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT])).some((entry) => (entry.runId === input.fence.runId
|
|
569
596
|
&& entry.identity.providerNamespace === input.fence.driverId
|
|
570
597
|
&& entry.identity.accountScope === input.fence.agentId
|
|
571
598
|
&& entry.identity.conversationId === input.fence.conversationId
|
|
@@ -647,7 +674,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
647
674
|
const status = "ended";
|
|
648
675
|
let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now, input.kind === "session.failed" ? "failed" : "stopped");
|
|
649
676
|
store.saveTaskRoleSessionSet(updatedSessions);
|
|
650
|
-
for (const continuation of projectProviderContinuations(store.
|
|
677
|
+
for (const continuation of projectProviderContinuations(store.listEventsByType(input.fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]))) {
|
|
651
678
|
if (continuation.runId !== input.fence.runId || continuation.identity.conversationId
|
|
652
679
|
!== (input.fence.conversationId ?? input.fence.nativeSessionId) || continuation.execution === "quiescent" || continuation.attachment === "detached")
|
|
653
680
|
continue;
|
|
@@ -721,9 +748,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
721
748
|
persistRuntimeObservation(input, now, notifyGoal = true) {
|
|
722
749
|
this.store.transaction((store) => {
|
|
723
750
|
const taskId = input.fence.taskId;
|
|
724
|
-
|
|
725
|
-
if (hasPersistedRuntimeObservation(events, input))
|
|
751
|
+
if (hasPersistedRuntimeObservation(store, input))
|
|
726
752
|
return;
|
|
753
|
+
const events = store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]);
|
|
727
754
|
if (usageSnapshotIsSuperseded(events, input))
|
|
728
755
|
return;
|
|
729
756
|
const removable = compactedRuntimeObservationIds(events, input);
|
|
@@ -851,43 +878,53 @@ export class FileSchedulerStoreAdapter {
|
|
|
851
878
|
return "obsolete";
|
|
852
879
|
}
|
|
853
880
|
/**
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
881
|
+
* Task-scoped cache tokens cover the exact source tables across connections.
|
|
882
|
+
* Unrelated Tasks/Global traffic do not invalidate these projections. Retain
|
|
883
|
+
* only the 16 most recently read Tasks; eviction changes cost, never truth.
|
|
857
884
|
*/
|
|
858
|
-
#readProjection =
|
|
859
|
-
#
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const events = this.store.listEvents(taskId);
|
|
871
|
-
task = {
|
|
872
|
-
events,
|
|
873
|
-
runFacts: foldRunProgressFacts(events),
|
|
874
|
-
runs: this.store.listRuns(taskId),
|
|
875
|
-
workItems: this.store.listWorkItems(taskId),
|
|
876
|
-
reviewRounds: this.store.listReviewRounds(taskId),
|
|
877
|
-
changeSets: this.store.listChangeSets(taskId),
|
|
878
|
-
integrationAttempts: this.store.listIntegrationAttempts(taskId),
|
|
879
|
-
inputRequests: this.store.listInputRequests(taskId),
|
|
880
|
-
durableJobs: this.store.listDurableJobs(taskId),
|
|
881
|
-
messages: this.store.listMessages(taskId)
|
|
882
|
-
};
|
|
883
|
-
tasks.set(taskId, task);
|
|
885
|
+
#readProjection = new Map();
|
|
886
|
+
#projected(taskId, family, read) {
|
|
887
|
+
const observed = this.store.getTaskReadRevision(taskId);
|
|
888
|
+
const previous = this.#readProjection.get(taskId);
|
|
889
|
+
const hit = previous?.revision === observed ? previous.value[family] : undefined;
|
|
890
|
+
if (hit !== undefined) {
|
|
891
|
+
// The token read is the read's snapshot point. A cache hit needs no new
|
|
892
|
+
// transaction: its value was built under that exact committed token.
|
|
893
|
+
this.#projectionCache.hits++;
|
|
894
|
+
this.#readProjection.delete(taskId);
|
|
895
|
+
this.#readProjection.set(taskId, previous);
|
|
896
|
+
return hit;
|
|
884
897
|
}
|
|
885
|
-
|
|
898
|
+
// Pin and recheck misses before reading any source rows: an external writer
|
|
899
|
+
// may have advanced the token after the optimistic lookup above.
|
|
900
|
+
return this.store.readTransaction(() => {
|
|
901
|
+
const revision = this.store.getTaskReadRevision(taskId);
|
|
902
|
+
let task = this.#readProjection.get(taskId);
|
|
903
|
+
if (task === undefined || task.revision !== revision) {
|
|
904
|
+
if (task !== undefined)
|
|
905
|
+
this.#projectionCache.invalidations++;
|
|
906
|
+
this.#projectionCache.rebuilds++;
|
|
907
|
+
task = { revision, value: {} };
|
|
908
|
+
}
|
|
909
|
+
this.#readProjection.delete(taskId);
|
|
910
|
+
this.#readProjection.set(taskId, task);
|
|
911
|
+
if (this.#readProjection.size > 16) {
|
|
912
|
+
this.#readProjection.delete(this.#readProjection.keys().next().value);
|
|
913
|
+
}
|
|
914
|
+
const cached = task.value[family];
|
|
915
|
+
if (cached !== undefined) {
|
|
916
|
+
this.#projectionCache.hits++;
|
|
917
|
+
return cached;
|
|
918
|
+
}
|
|
919
|
+
const value = this.#observeRead(`task-${family}-read`, taskId, read);
|
|
920
|
+
task.value[family] = value;
|
|
921
|
+
return value;
|
|
922
|
+
});
|
|
886
923
|
}
|
|
887
924
|
withRuntimeEventTransaction(execute) {
|
|
888
925
|
return this.store.withRuntimeEventTransaction(execute);
|
|
889
926
|
}
|
|
890
|
-
listTasks() { return this.store.listTasks(); }
|
|
927
|
+
listTasks() { return this.#observeRead("list-tasks", "home", () => this.store.listTasks()); }
|
|
891
928
|
listActiveTaskIds() {
|
|
892
929
|
return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
893
930
|
}
|
|
@@ -980,51 +1017,48 @@ export class FileSchedulerStoreAdapter {
|
|
|
980
1017
|
return this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
981
1018
|
}
|
|
982
1019
|
listEvents(taskId) {
|
|
983
|
-
return this.#
|
|
1020
|
+
return this.#projected(taskId, "events", () => this.store.listEvents(taskId));
|
|
984
1021
|
}
|
|
985
1022
|
listRuns(taskId) {
|
|
986
|
-
return this.#
|
|
1023
|
+
return this.#projected(taskId, "runs", () => this.store.listRuns(taskId));
|
|
987
1024
|
}
|
|
988
1025
|
listWorkItems(taskId) {
|
|
989
|
-
return this.#
|
|
1026
|
+
return this.#projected(taskId, "workItems", () => this.store.listWorkItems(taskId));
|
|
990
1027
|
}
|
|
991
1028
|
listReviewRounds(taskId) {
|
|
992
|
-
return this.#
|
|
1029
|
+
return this.#projected(taskId, "reviewRounds", () => this.store.listReviewRounds(taskId));
|
|
993
1030
|
}
|
|
994
1031
|
listIntegrationAttempts(taskId) {
|
|
995
|
-
return this.#
|
|
1032
|
+
return this.#projected(taskId, "integrationAttempts", () => this.store.listIntegrationAttempts(taskId));
|
|
996
1033
|
}
|
|
997
1034
|
listDurableJobs(taskId) {
|
|
998
|
-
return this.#
|
|
1035
|
+
return this.#projected(taskId, "durableJobs", () => this.store.listDurableJobs(taskId));
|
|
999
1036
|
}
|
|
1000
1037
|
listInputRequests(taskId) {
|
|
1001
|
-
return this.#
|
|
1038
|
+
return this.#projected(taskId, "inputRequests", () => this.store.listInputRequests(taskId));
|
|
1002
1039
|
}
|
|
1003
1040
|
listMessages(taskId) {
|
|
1004
|
-
return this.#
|
|
1041
|
+
return this.#projected(taskId, "messages", () => this.store.listMessages(taskId));
|
|
1005
1042
|
}
|
|
1006
1043
|
getRunProgressFacts(taskId, runId) {
|
|
1007
|
-
return this.#
|
|
1044
|
+
return this.#projected(taskId, "runFacts", () => foldRunProgressFacts(this.listEvents(taskId))).get(runId);
|
|
1008
1045
|
}
|
|
1009
1046
|
getRunDurableProgress(taskId, roleName, runId) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
// re-scan the whole history. Pass {} so latestTurnDurableProgressAt treats
|
|
1026
|
-
// the fold as present and skips the per-candidate fallback scans.
|
|
1027
|
-
return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
|
|
1047
|
+
return this.store.readTransaction(() => {
|
|
1048
|
+
// Pin related reads to one snapshot while loading only required families.
|
|
1049
|
+
const view = {
|
|
1050
|
+
getRun: (id, runId) => this.store.getRun(id, runId),
|
|
1051
|
+
listEvents: () => this.listEvents(taskId),
|
|
1052
|
+
getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
|
|
1053
|
+
listReviewRounds: () => this.listReviewRounds(taskId),
|
|
1054
|
+
listChangeSets: () => this.#projected(taskId, "changeSets", () => this.store.listChangeSets(taskId)),
|
|
1055
|
+
listIntegrationAttempts: () => this.listIntegrationAttempts(taskId),
|
|
1056
|
+
listInputRequests: () => this.listInputRequests(taskId)
|
|
1057
|
+
};
|
|
1058
|
+
// A missing fold entry is authoritative empty evidence, not permission
|
|
1059
|
+
// to fall back to a per-candidate full-history scan.
|
|
1060
|
+
return latestRunDurableProgressAt(view, taskId, roleName, runId, this.getRunProgressFacts(taskId, runId) ?? {});
|
|
1061
|
+
});
|
|
1028
1062
|
}
|
|
1029
1063
|
recordRoleRunDiagnostic(input) {
|
|
1030
1064
|
return this.store.transaction((store) => {
|
|
@@ -1160,12 +1194,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1160
1194
|
if (turn != null && ["submitting", "accepted", "delivery-unknown"].includes(turn.status)) {
|
|
1161
1195
|
throw new AgentHostProviderSessionBusyError("Global Conversation has an unsettled input.");
|
|
1162
1196
|
}
|
|
1163
|
-
const message = store
|
|
1164
|
-
const reserved = store.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
&& entry.interruptThen.targetAuthorityHolderId === input.holderId);
|
|
1197
|
+
const message = globalMessageForReceipt(store, input.roleName, input.attemptId);
|
|
1198
|
+
const reserved = store.queryGlobalRoleMessages(input.roleName, {
|
|
1199
|
+
limit: 1, reservedThen: { nativeSessionId: input.nativeSessionId,
|
|
1200
|
+
agentId: input.agentId, authorityEpoch: input.authorityEpoch, holderId: input.holderId }
|
|
1201
|
+
}).messages[0];
|
|
1169
1202
|
if (reserved !== undefined && reserved.id !== message?.id) {
|
|
1170
1203
|
throw new AgentHostProviderSessionBusyError("The next Global input belongs to an explicit then handoff.");
|
|
1171
1204
|
}
|
|
@@ -1285,7 +1318,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1285
1318
|
// This shares the exact writer/session admission and its transaction.
|
|
1286
1319
|
if ((input.authorityOwner === "human" || input.attemptId.startsWith(`human:${input.holderId}:`)) && input.roleName === "leader"
|
|
1287
1320
|
&& input.runId === undefined && input.boundedText !== undefined) {
|
|
1288
|
-
const prior = store.
|
|
1321
|
+
const prior = store.listEventsByType(input.taskId, ["message.native-user-input"]).find(event => event.payload.attemptId === input.attemptId);
|
|
1289
1322
|
if (prior !== undefined) {
|
|
1290
1323
|
const message = store.listMessages(input.taskId).find(entry => entry.id === prior.payload.messageId);
|
|
1291
1324
|
if (message?.body !== input.boundedText || prior.payload.nativeSessionId !== input.nativeSessionId) {
|
|
@@ -1344,7 +1377,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1344
1377
|
unknown: "External effects are not established by submission settlement."
|
|
1345
1378
|
}, input.now);
|
|
1346
1379
|
}
|
|
1347
|
-
const message = store
|
|
1380
|
+
const message = globalMessageForReceipt(store, input.roleName, input.attemptId);
|
|
1348
1381
|
if (message !== undefined && message.delivery?.via !== "provider") {
|
|
1349
1382
|
const outcome = input.status === "delivery-unknown" ? "delivery-unknown" : "rejected";
|
|
1350
1383
|
const updated = { ...message, control: {
|
|
@@ -1890,9 +1923,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1890
1923
|
return null;
|
|
1891
1924
|
const wakeId = processing.owner.slice("leader-notification:".length);
|
|
1892
1925
|
const attemptId = processing.batchId;
|
|
1893
|
-
const previous = store.
|
|
1926
|
+
const previous = store.listEventsByType(taskId, ["notification.delivery"]).filter((event) => event.payload.attemptId === attemptId).at(-1);
|
|
1894
1927
|
const native = provider?.attemptId === attemptId ? provider : undefined;
|
|
1895
|
-
const accepted = store.
|
|
1928
|
+
const accepted = store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).some((event) => {
|
|
1896
1929
|
const observation = runtimeObservationFromTaskEvent(event);
|
|
1897
1930
|
return observation?.fence.roleName === "leader"
|
|
1898
1931
|
&& observation.fence.receiptId === attemptId
|
|
@@ -2016,8 +2049,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2016
2049
|
if (mailbox == null || processing == null || processing.batchId !== attemptId
|
|
2017
2050
|
|| !processing.owner.startsWith("leader-notification:"))
|
|
2018
2051
|
return;
|
|
2019
|
-
if (!store.
|
|
2020
|
-
&& event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
|
|
2052
|
+
if (!store.listEventsByType(taskId, ["notification.delivery"]).some((event) => event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
|
|
2021
2053
|
// Native ingress may already have persisted the same rejection. Reuse
|
|
2022
2054
|
// that original failure rather than reporting it twice from the caller.
|
|
2023
2055
|
const existing = store.listEventsByType(taskId, ["runtime.agent-error"])
|
|
@@ -2083,7 +2115,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2083
2115
|
nativeSessionId: input.session.nativeSessionId
|
|
2084
2116
|
}, "active", input.now);
|
|
2085
2117
|
}
|
|
2086
|
-
if (!defersConversationReplacement && !store.
|
|
2118
|
+
if (!defersConversationReplacement && !store.listEventsByType(input.task.id, ["run.session-prepared"]).some((event) => event.payload.runId === input.run.id
|
|
2087
2119
|
&& event.payload.nativeSessionId === input.session.nativeSessionId)) {
|
|
2088
2120
|
store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "run.session-prepared", { runId: input.run.id, roleName: role.name,
|
|
2089
2121
|
nativeSessionId: input.session.nativeSessionId }, input.now));
|
|
@@ -2235,7 +2267,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2235
2267
|
&& preparedRun.executionLaneId !== undefined
|
|
2236
2268
|
&& preparedRun.workspace !== undefined
|
|
2237
2269
|
&& input.outcome.status === "completed"
|
|
2238
|
-
? this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace)
|
|
2270
|
+
? this.#observeRead("git-workspace-snapshot", preparedRun.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace))
|
|
2239
2271
|
: undefined;
|
|
2240
2272
|
return this.store.transaction((store) => {
|
|
2241
2273
|
const task = store.getTask(input.taskId);
|
|
@@ -2476,7 +2508,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2476
2508
|
observeProviderGoal(input, now) {
|
|
2477
2509
|
return this.store.transaction((store) => {
|
|
2478
2510
|
const taskId = input.fence.taskId;
|
|
2479
|
-
if (hasPersistedRuntimeObservation(store
|
|
2511
|
+
if (hasPersistedRuntimeObservation(store, input))
|
|
2480
2512
|
return "applied";
|
|
2481
2513
|
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
2482
2514
|
const binding = sessions?.providerBinding;
|
|
@@ -2798,7 +2830,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2798
2830
|
...(boundNativeSessionId === undefined ? {} : { nativeSessionId: boundNativeSessionId })
|
|
2799
2831
|
};
|
|
2800
2832
|
const driverId = this.drivers.requireByAdapterId(run.effective.adapterId).id;
|
|
2801
|
-
const lifecycleEvents = store.
|
|
2833
|
+
const lifecycleEvents = store.listEventsByType(fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).flatMap((event) => {
|
|
2802
2834
|
const observation = runtimeObservationFromTaskEvent(event);
|
|
2803
2835
|
return observation !== null && (observation.kind === "session.started" || observation.kind === "session.ready") && observation.fence.roleName === fence.roleName && observation.fence.agentId === run.effective.agentId && observation.fence.driverId === driverId && observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
|
|
2804
2836
|
? [observation]
|
|
@@ -2966,7 +2998,7 @@ function runtimeReceiptBelongsToRun(store, input) {
|
|
|
2966
2998
|
&& sessions?.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId) {
|
|
2967
2999
|
return true;
|
|
2968
3000
|
}
|
|
2969
|
-
return store.
|
|
3001
|
+
return store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).some((event) => {
|
|
2970
3002
|
const accepted = runtimeObservationFromTaskEvent(event);
|
|
2971
3003
|
return accepted?.kind === "turn.accepted" && accepted.fence.runId === runId && accepted.fence.roleName === input.fence.roleName && accepted.fence.agentId === input.fence.agentId && accepted.fence.nativeSessionId === input.fence.nativeSessionId && accepted.fence.receiptId === receiptId;
|
|
2972
3004
|
});
|
|
@@ -2988,8 +3020,7 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
|
2988
3020
|
}, reason, now);
|
|
2989
3021
|
}
|
|
2990
3022
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
2991
|
-
if (store.
|
|
2992
|
-
&& (event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
|
|
3023
|
+
if (store.listEventsByType(input.taskId, ["runtime.event-obsolete"]).some((event) => ((event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
|
|
2993
3024
|
return;
|
|
2994
3025
|
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.event-obsolete", {
|
|
2995
3026
|
eventId: input.eventId,
|
|
@@ -3145,9 +3176,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
3145
3176
|
if (task.status === "archived") {
|
|
3146
3177
|
throw new Error(`Cannot register a native session for archived Task: ${input.taskId}.`);
|
|
3147
3178
|
}
|
|
3148
|
-
// A Draft
|
|
3149
|
-
//
|
|
3150
|
-
//
|
|
3179
|
+
// A Draft admits its first planning Run, restoration of that exact Session,
|
|
3180
|
+
// or the selected successor's initialization after verified replacement.
|
|
3181
|
+
// Ordinary Draft notifications and native hooks cannot create a Session.
|
|
3151
3182
|
const admittingRun = store.getActiveRun(input.taskId, input.roleName);
|
|
3152
3183
|
const existingPlanning = activeLiveRoleAgentSession(store.getTaskRoleSessionSet(input.taskId, input.roleName));
|
|
3153
3184
|
if (!(admittingRun !== null
|
|
@@ -3156,6 +3187,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
3156
3187
|
&& !(task.status === "draft" && task.executionGate.state === "enabled" && input.roleName === "leader"
|
|
3157
3188
|
&& existingPlanning?.effective.executionAuthority === "planning"
|
|
3158
3189
|
&& existingPlanning.nativeSessionId === input.nativeSessionId)
|
|
3190
|
+
&& !(task.status === "draft" && task.executionGate.state === "enabled" && input.roleName === "leader"
|
|
3191
|
+
&& input.effective?.executionAuthority === "planning"
|
|
3192
|
+
&& draftReplacementInitializationIsCurrent(store, input.taskId, input.agentId))
|
|
3159
3193
|
&& (task.status !== "active" || task.executionGate.state !== "enabled")) {
|
|
3160
3194
|
throw new Error(`Cannot register a native session for a Task that is not active: ${input.taskId}.`);
|
|
3161
3195
|
}
|
|
@@ -3191,6 +3225,32 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
3191
3225
|
store.saveRoleSessionSet(updated);
|
|
3192
3226
|
return updated.sessions[input.agentId];
|
|
3193
3227
|
}
|
|
3228
|
+
/** The existing initialization wake is the launch authority, not historical
|
|
3229
|
+
* planning success or an arbitrary "session-replaced" reason. It is retained
|
|
3230
|
+
* on startup rejection for the normal explicit wake retry/replacement path. */
|
|
3231
|
+
function draftReplacementInitializationIsCurrent(store, taskId, agentId) {
|
|
3232
|
+
const sessions = store.getTaskRoleSessionSet(taskId, "leader");
|
|
3233
|
+
if (sessions === null || sessions.activeAgentId !== agentId
|
|
3234
|
+
|| sessions.sessions[agentId] !== undefined || sessions.providerBinding !== null
|
|
3235
|
+
|| hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({
|
|
3236
|
+
scope: "task", taskId, roleName: "leader"
|
|
3237
|
+
}))))
|
|
3238
|
+
return false;
|
|
3239
|
+
const processing = store.getWorkMailbox({ kind: "role", taskId, roleName: "leader" })?.processing;
|
|
3240
|
+
if (!processing?.owner.startsWith("leader-notification:"))
|
|
3241
|
+
return false;
|
|
3242
|
+
const wake = store.getTaskWake(taskId, processing.owner.slice("leader-notification:".length));
|
|
3243
|
+
if (wake === null || !wake.reasons.includes("session-replaced"))
|
|
3244
|
+
return false;
|
|
3245
|
+
const events = store.listEventsByType(taskId, ["runtime.session-replaced", "notification.delivery"]);
|
|
3246
|
+
const replacement = events.filter(event => event.type === "runtime.session-replaced"
|
|
3247
|
+
&& event.payload.roleName === "leader").at(-1);
|
|
3248
|
+
return replacement !== undefined
|
|
3249
|
+
&& (wake.refs ?? []).some(ref => ref.type === "event" && ref.taskId === taskId && ref.id === replacement.id)
|
|
3250
|
+
&& !events.some(event => event.type === "notification.delivery"
|
|
3251
|
+
&& event.payload.attemptId === processing.batchId
|
|
3252
|
+
&& event.payload.outcome !== "deferred");
|
|
3253
|
+
}
|
|
3194
3254
|
function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
3195
3255
|
const role = store.getGlobalRole(input.roleName);
|
|
3196
3256
|
if (role === null)
|
|
@@ -3271,6 +3331,7 @@ function mapRole(store, role) {
|
|
|
3271
3331
|
return {
|
|
3272
3332
|
taskId: role.taskId,
|
|
3273
3333
|
name: role.name,
|
|
3334
|
+
enabled: role.enabled !== false,
|
|
3274
3335
|
activeAgentId: role.activeAgentId,
|
|
3275
3336
|
adapterId: binding.adapterId,
|
|
3276
3337
|
...(binding.config.model === undefined ? {} : { model: binding.config.model }),
|
|
@@ -3307,8 +3368,9 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
|
|
|
3307
3368
|
?? store.getTaskWorkspace(taskId)
|
|
3308
3369
|
?? undefined;
|
|
3309
3370
|
const effective = resolveEffectiveLaunch({
|
|
3371
|
+
store,
|
|
3310
3372
|
role,
|
|
3311
|
-
purpose: "execution",
|
|
3373
|
+
purpose: store.getTask(taskId)?.status === "draft" && roleName === "leader" ? "planning" : "execution",
|
|
3312
3374
|
...(workspace === undefined ? {} : { workspace }),
|
|
3313
3375
|
...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
|
|
3314
3376
|
});
|
|
@@ -3482,7 +3544,7 @@ function resolveTerminalExecution(store, input) {
|
|
|
3482
3544
|
const session = sessions?.sessions[input.agentId];
|
|
3483
3545
|
const binding = sessions?.providerBinding;
|
|
3484
3546
|
const pending = binding?.run;
|
|
3485
|
-
const evidence = store.
|
|
3547
|
+
const evidence = store.listEventsByType(input.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).map(runtimeObservationFromTaskEvent)
|
|
3486
3548
|
.filter((event) => event !== null
|
|
3487
3549
|
&& (event.kind === "turn.accepted" || isRunTerminalObservation(event)));
|
|
3488
3550
|
if (input.attemptId !== undefined && evidence.some((event) => (event.fence.roleName === input.roleName
|
|
@@ -3543,7 +3605,7 @@ function isRunTerminalObservation(input) {
|
|
|
3543
3605
|
return ["turn.completed", "turn.failed", "turn.cancelled"].includes(input.kind);
|
|
3544
3606
|
}
|
|
3545
3607
|
function assertConsistentTerminal(store, input, run) {
|
|
3546
|
-
const previous = store.
|
|
3608
|
+
const previous = store.listEventsByType(input.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).map(runtimeObservationFromTaskEvent)
|
|
3547
3609
|
.filter((event) => event !== null
|
|
3548
3610
|
&& isRunTerminalObservation(event)
|
|
3549
3611
|
&& event.fence.roleName === input.roleName
|
|
@@ -3669,14 +3731,21 @@ function usageSnapshotIsSuperseded(events, incoming) {
|
|
|
3669
3731
|
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)
|
|
3670
3732
|
&& observationIsStrictlyNewer(observation, incoming));
|
|
3671
3733
|
}
|
|
3672
|
-
function hasPersistedRuntimeObservation(
|
|
3734
|
+
function hasPersistedRuntimeObservation(store, incoming) {
|
|
3673
3735
|
const usageIdentity = isRuntimeUsageEvidence(incoming)
|
|
3674
3736
|
? incoming.eventId
|
|
3675
3737
|
: undefined;
|
|
3676
|
-
return
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3738
|
+
return store.hasRuntimeObservation(incoming.fence.taskId, usageIdentity === undefined
|
|
3739
|
+
? { semanticKey: incoming.semanticKey } : { eventId: usageIdentity });
|
|
3740
|
+
}
|
|
3741
|
+
function globalMessageForReceipt(store, roleName, receipt, kind = "global-input", retryMessageId) {
|
|
3742
|
+
const prefix = `${kind}:${roleName}/`;
|
|
3743
|
+
const id = receipt?.startsWith(prefix) ? receipt.slice(prefix.length) : undefined;
|
|
3744
|
+
// A retry references the original Message, not a second input. Preserve the
|
|
3745
|
+
// old numeric-id ordering if both explicit identities name different rows.
|
|
3746
|
+
return [...new Set([id, retryMessageId].filter((value) => value !== undefined))]
|
|
3747
|
+
.flatMap(id => store.queryGlobalRoleMessages(roleName, { id, limit: 1 }).messages)
|
|
3748
|
+
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))[0];
|
|
3680
3749
|
}
|
|
3681
3750
|
function observationIsStrictlyNewer(left, right) {
|
|
3682
3751
|
return compareCanonicalObservationOrder(left, right) > 0;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isEnabled } from "../role/availability.js";
|
|
1
2
|
import { sendAgentHostRunControl, reconcileAgentHostTurn, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
2
3
|
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
3
4
|
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
4
5
|
import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
|
|
5
6
|
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
6
|
-
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
|
+
import { globalRetryInputSettlement, settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
8
|
import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
|
|
8
9
|
import { recordGlobalRuntimeAttention, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
|
|
9
10
|
import { globalInboxRecovery } from "../message/globalInboxRecovery.js";
|
|
@@ -25,14 +26,6 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
|
|
|
25
26
|
if (sessions !== null && selected?.status !== "active"
|
|
26
27
|
&& !(role.jobOwner && selected === undefined))
|
|
27
28
|
continue;
|
|
28
|
-
if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
|
|
29
|
-
store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
|
|
30
|
-
pending = store.queryGlobalRoleMessages(role.name, { pending: true, unclaimed: true, limit: 64 }).messages;
|
|
31
|
-
if (pending.length === 0)
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (providerRetryPending(sessions?.providerBinding))
|
|
35
|
-
continue;
|
|
36
29
|
let current = sessions?.providerBinding?.run;
|
|
37
30
|
const bindingBefore = sessions?.providerBinding;
|
|
38
31
|
if (options.reconcile && current != null
|
|
@@ -95,6 +88,23 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
|
|
|
95
88
|
if (pending.length === 0)
|
|
96
89
|
continue;
|
|
97
90
|
}
|
|
91
|
+
// Availability blocks new work, not observation of the original writer's
|
|
92
|
+
// exact terminal. Re-read after the asynchronous reconciliation above.
|
|
93
|
+
const currentRole = store.getGlobalRole(role.name);
|
|
94
|
+
if (currentRole === null || !isEnabled(currentRole))
|
|
95
|
+
continue;
|
|
96
|
+
if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
|
|
97
|
+
const now = options.now ?? new Date();
|
|
98
|
+
// Retained terminal retry history is not new write work each pass.
|
|
99
|
+
const settlement = store.readTransaction(tx => globalRetryInputSettlement(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, now));
|
|
100
|
+
if (settlement !== undefined)
|
|
101
|
+
store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, now));
|
|
102
|
+
pending = store.queryGlobalRoleMessages(role.name, { pending: true, unclaimed: true, limit: 64 }).messages;
|
|
103
|
+
if (pending.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (providerRetryPending(sessions?.providerBinding))
|
|
107
|
+
continue;
|
|
98
108
|
const recovery = globalInboxRecovery(store, role.name, options.now ?? new Date());
|
|
99
109
|
const recovering = recovery !== undefined && recovery.inbox === undefined && recovery.delivery === undefined
|
|
100
110
|
&& recovery.notDelivered === undefined && recovery.control === undefined;
|