@zq-silk/yui 0.12.0 → 0.12.1
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 +8 -9
- package/dist/cli/commandCatalog.js +11 -6
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +51 -14
- package/dist/commands/configCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +2 -1
- package/dist/commands/taskCommands.js +80 -29
- package/dist/commands/taskContextCommand.js +6 -2
- package/dist/commands/taskRoleRuntimeStatus.js +31 -7
- package/dist/config/configCatalog.js +1 -1
- package/dist/controller/clientRuntime.js +38 -2
- package/dist/controller/controller.js +23 -15
- package/dist/controller/fileSchedulerStoreAdapter.js +248 -138
- package/dist/controller/runtime.js +56 -1
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/executor/agentExecutor.js +48 -46
- package/dist/executor/fileRoleLaunchPlanner.js +94 -30
- package/dist/lifecycle/exactRunTerminalization.js +68 -3
- package/dist/observability/executionAudit.js +5 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +15 -0
- package/dist/runtime/runtimeProjection.js +6 -7
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +31 -196
- package/dist/scheduler/leaderWakeupProcessor.js +39 -133
- package/dist/scheduler/roleRunStall.js +53 -17
- package/dist/storage/sqliteSchema.js +57 -24
- package/dist/storage/sqliteStore.js +23 -4
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/nextAction.js +0 -34
- package/dist/web/webSnapshot.js +3 -1
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +7 -4
- package/skills/yui-operator/SKILL.md +4 -4
- package/skills/yui-reviewer/SKILL.md +7 -4
- package/dist/lifecycle/taskRoleSessionReset.js +0 -118
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun,
|
|
4
|
-
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
3
|
+
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, prepareTaskRoleRunRedispatch, recordRoleAgentSession, replaceTaskRoleAgentSessionForConversationSwitch, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, stopTaskRoleRuntimeAfterPhysicalExit, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
|
+
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
5
|
+
import { CONVERSATION_SWITCH_DETACHED_EVENT, CONVERSATION_SWITCH_RESOLVED_EVENT, conversationDetachmentBasis, conversationReplacementBasis, pendingConversationSwitch, roleSessionDispatchModeWithConversationSwitch } from "../runtime/conversationSwitch.js";
|
|
5
6
|
import { decideProviderRecovery } from "../runtime/providerRecoveryDecision.js";
|
|
6
7
|
import { hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
7
8
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
@@ -21,11 +22,11 @@ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } fro
|
|
|
21
22
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
22
23
|
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
23
24
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
24
|
-
import {
|
|
25
|
+
import { recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
|
|
25
26
|
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
26
27
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
27
28
|
import { wakeReason } from "../scheduler/wakeReason.js";
|
|
28
|
-
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
29
|
+
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
29
30
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
30
31
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
31
32
|
import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus, workItemOwnsUnresolvedExecutionLane } from "../workItem/workItem.js";
|
|
@@ -36,7 +37,7 @@ import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
|
36
37
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, RuntimeLifecycleBusyError, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
37
38
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
38
39
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
39
|
-
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
40
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload, runHasActiveRuntimeOperations } from "../runtime/runtimeObservation.js";
|
|
40
41
|
import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
41
42
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
42
43
|
import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
@@ -386,7 +387,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
386
387
|
const message = [
|
|
387
388
|
`Leader native Session became unavailable while Run ${active.id} remains active.`,
|
|
388
389
|
"Yui preserved the Run because Session/host termination is not a workflow outcome.",
|
|
389
|
-
"Inspect native-child and mailbox facts, then explicitly
|
|
390
|
+
"Inspect native-child and mailbox facts, then explicitly diagnose, recover the Run, request a Conversation switch, or request user input."
|
|
390
391
|
].join(" ");
|
|
391
392
|
recordLeaderAttentionRequired(store, {
|
|
392
393
|
taskId: input.fence.taskId,
|
|
@@ -718,6 +719,27 @@ export class FileSchedulerStoreAdapter {
|
|
|
718
719
|
// the fold as present and skips the per-candidate fallback scans.
|
|
719
720
|
return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
|
|
720
721
|
}
|
|
722
|
+
recordRoleRunDiagnostic(input) {
|
|
723
|
+
return this.store.transaction((store) => {
|
|
724
|
+
const task = store.getTask(input.taskId);
|
|
725
|
+
const run = store.getActiveAgentRun(input.taskId, input.roleName);
|
|
726
|
+
if (task === null || task.status !== "active"
|
|
727
|
+
|| run === null || run.id !== input.runId || run.status !== "active") {
|
|
728
|
+
return "state-changed";
|
|
729
|
+
}
|
|
730
|
+
const latest = latestRunEventTime(store.listEvents(input.taskId), RUN_DIAGNOSTIC_FINISHED_EVENT, input.runId);
|
|
731
|
+
if (latest !== undefined && Date.parse(latest) >= Date.parse(input.startedAt)) {
|
|
732
|
+
return "already-recorded";
|
|
733
|
+
}
|
|
734
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_DIAGNOSTIC_FINISHED_EVENT, {
|
|
735
|
+
runId: input.runId,
|
|
736
|
+
roleName: input.roleName,
|
|
737
|
+
outcome: input.outcome,
|
|
738
|
+
startedAt: input.startedAt
|
|
739
|
+
}, input.now));
|
|
740
|
+
return "recorded";
|
|
741
|
+
});
|
|
742
|
+
}
|
|
721
743
|
recordRoleRunStall(input) {
|
|
722
744
|
return this.store.transaction((store) => {
|
|
723
745
|
const task = store.getTask(input.taskId);
|
|
@@ -749,15 +771,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
749
771
|
progressAt: input.progressAt,
|
|
750
772
|
idleMs: String(Math.max(0, Math.floor(input.idleMs))),
|
|
751
773
|
evidenceKey: input.evidenceKey,
|
|
752
|
-
status: "
|
|
774
|
+
status: "diagnostic-only"
|
|
753
775
|
}, input.now);
|
|
754
776
|
store.saveEvent(task.id, event);
|
|
755
|
-
if (role.name === "leader") {
|
|
756
|
-
enqueueOperatorEvent(store, event, "leader-run-stalled", input.now);
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
queueLeaderWakeup(store, task.id, wakeReason("role-run-stalled"), input.now);
|
|
760
|
-
}
|
|
761
777
|
return "raised";
|
|
762
778
|
});
|
|
763
779
|
}
|
|
@@ -785,10 +801,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
785
801
|
|| (typeof event.payload.progressAt === "string"
|
|
786
802
|
&& Number.isFinite(Date.parse(event.payload.progressAt))
|
|
787
803
|
&& Date.parse(event.payload.progressAt) >= Date.parse(input.progressAt)))));
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const recovered =
|
|
791
|
-
&& (recoveredAt === undefined || Date.parse(recoveredAt) <= Date.parse(stalledAt));
|
|
804
|
+
// Advisory 30-minute diagnostics are intentionally not lifecycle
|
|
805
|
+
// episodes and therefore must never synthesize run.recovered.
|
|
806
|
+
const recovered = isRoleRunStalled(events, run.id);
|
|
792
807
|
if (!existing) {
|
|
793
808
|
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_PROGRESS_EVENT, {
|
|
794
809
|
runId: run.id,
|
|
@@ -813,11 +828,92 @@ export class FileSchedulerStoreAdapter {
|
|
|
813
828
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
814
829
|
return sessions !== null && sessions.inFlight !== null;
|
|
815
830
|
}
|
|
831
|
+
detachAgentHostProviderForConversationSwitch(input) {
|
|
832
|
+
return this.store.transaction((store) => {
|
|
833
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
834
|
+
const run = store.getAgentRun(input.taskId, input.runId);
|
|
835
|
+
const mailbox = store.getWorkMailbox({
|
|
836
|
+
kind: "role",
|
|
837
|
+
taskId: input.taskId,
|
|
838
|
+
roleName: input.roleName
|
|
839
|
+
});
|
|
840
|
+
const binding = sessions?.providerBinding;
|
|
841
|
+
const session = sessions?.sessions[input.agentId];
|
|
842
|
+
const task = store.getTask(input.taskId);
|
|
843
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
844
|
+
const activeRun = store.getActiveAgentRun(input.taskId, input.roleName);
|
|
845
|
+
if (task === null || task.status !== "active"
|
|
846
|
+
|| role === null || role.activeAgentId !== input.agentId
|
|
847
|
+
|| sessions === null || sessions === undefined
|
|
848
|
+
|| run === null || run.status !== "active" || run.mode !== "new"
|
|
849
|
+
|| activeRun?.id !== run.id
|
|
850
|
+
|| run.effective.agentId !== input.agentId
|
|
851
|
+
|| session?.nativeSessionId !== input.previousNativeSessionId
|
|
852
|
+
|| binding === null || binding === undefined
|
|
853
|
+
|| currentProviderConversation(binding).conversationId !== input.previousConversationId
|
|
854
|
+
|| sessions.inFlight?.runId !== input.runId
|
|
855
|
+
|| mailbox?.processing?.batchId !== sessions.inFlight.receiptId
|
|
856
|
+
|| mailbox.processing.owner !== "controller"
|
|
857
|
+
|| mailbox.processing.executionRef?.type !== "run"
|
|
858
|
+
|| mailbox.processing.executionRef.taskId !== input.taskId
|
|
859
|
+
|| mailbox.processing.executionRef.id !== input.runId
|
|
860
|
+
|| mailbox.inputDelivery !== null
|
|
861
|
+
|| pendingConversationSwitch(store.listEvents(input.taskId), input.roleName, sessions) === null
|
|
862
|
+
|| !runtimeHookMatchesReservation(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, input.launchId)
|
|
863
|
+
|| input.nextAuthorityHolderId !== input.launchId) {
|
|
864
|
+
throw new Error("Provider Conversation detachment no longer matches the exact switch Run.");
|
|
865
|
+
}
|
|
866
|
+
const target = binding.activations.find((entry) => (entry.activationId === input.previousActivationId
|
|
867
|
+
&& entry.conversationId === input.previousConversationId));
|
|
868
|
+
if (target === undefined) {
|
|
869
|
+
throw new Error("Provider Conversation detachment targets an unknown Activation.");
|
|
870
|
+
}
|
|
871
|
+
if (target.status !== "active") {
|
|
872
|
+
if (binding.authority.owner !== "none"
|
|
873
|
+
|| input.nextAuthorityEpoch !== binding.authority.epoch + 1) {
|
|
874
|
+
throw new Error("Provider Conversation detachment replay has a stale authority fence.");
|
|
875
|
+
}
|
|
876
|
+
return "already-detached";
|
|
877
|
+
}
|
|
878
|
+
if (conversationDetachmentBasis({
|
|
879
|
+
sessions,
|
|
880
|
+
events: store.listEvents(input.taskId),
|
|
881
|
+
mailbox,
|
|
882
|
+
roleName: input.roleName,
|
|
883
|
+
runId: input.runId,
|
|
884
|
+
runMode: run.mode
|
|
885
|
+
}) !== "actor-request"
|
|
886
|
+
|| binding.authority.owner !== "controller"
|
|
887
|
+
|| binding.authority.holderId !== target.activationId
|
|
888
|
+
|| input.nextAuthorityEpoch !== binding.authority.epoch + 2) {
|
|
889
|
+
throw new Error("Provider Conversation detachment is not authorized at this boundary.");
|
|
890
|
+
}
|
|
891
|
+
const detached = endProviderActivation(binding, target.activationId, {
|
|
892
|
+
status: "ended",
|
|
893
|
+
endedAt: input.now.toISOString(),
|
|
894
|
+
reason: "conversation-switch"
|
|
895
|
+
});
|
|
896
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, detached, input.now));
|
|
897
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, CONVERSATION_SWITCH_DETACHED_EVENT, {
|
|
898
|
+
runId: input.runId,
|
|
899
|
+
roleName: input.roleName,
|
|
900
|
+
launchId: input.launchId,
|
|
901
|
+
conversationId: input.previousConversationId,
|
|
902
|
+
activationId: input.previousActivationId
|
|
903
|
+
}, input.now));
|
|
904
|
+
return "detached";
|
|
905
|
+
});
|
|
906
|
+
}
|
|
816
907
|
beginAgentHostProviderTurn(input) {
|
|
817
908
|
this.store.transaction((store) => {
|
|
818
909
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
819
910
|
const session = sessions?.sessions[input.agentId];
|
|
820
911
|
const binding = sessions?.providerBinding;
|
|
912
|
+
const mailbox = store.getWorkMailbox({
|
|
913
|
+
kind: "role",
|
|
914
|
+
taskId: input.taskId,
|
|
915
|
+
roleName: input.roleName
|
|
916
|
+
});
|
|
821
917
|
if (sessions === null || sessions === undefined
|
|
822
918
|
|| binding === null || binding === undefined
|
|
823
919
|
|| sessions.inFlight?.runId !== input.runId
|
|
@@ -827,7 +923,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
827
923
|
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
828
924
|
|| binding.authority.owner !== input.authorityOwner
|
|
829
925
|
|| binding.authority.epoch !== input.authorityEpoch
|
|
830
|
-
|| binding.authority.holderId !== input.holderId
|
|
926
|
+
|| binding.authority.holderId !== input.holderId
|
|
927
|
+
|| !processingOwnsExactInitialDelivery(mailbox, input.taskId, input.runId, input.attemptId)) {
|
|
831
928
|
throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
|
|
832
929
|
}
|
|
833
930
|
const exactReplay = binding.turn?.attemptId === input.attemptId
|
|
@@ -852,21 +949,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
852
949
|
if (sessions === null || sessions === undefined
|
|
853
950
|
|| binding === null || binding === undefined
|
|
854
951
|
|| binding.runId !== input.runId
|
|
855
|
-
|| binding.turn?.attemptId !== input.attemptId
|
|
856
|
-
|| binding.turn.status !== "submitting") {
|
|
952
|
+
|| binding.turn?.attemptId !== input.attemptId) {
|
|
857
953
|
throw new Error("Agent Host Provider Turn submission is no longer current.");
|
|
858
954
|
}
|
|
859
|
-
const updated =
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
: rejectProviderTurn(binding, {
|
|
866
|
-
attemptId: input.attemptId,
|
|
867
|
-
rejectedAt: input.now.toISOString(),
|
|
868
|
-
reason: input.reason
|
|
869
|
-
});
|
|
955
|
+
const updated = settleProviderTurnSubmission(binding, {
|
|
956
|
+
attemptId: input.attemptId,
|
|
957
|
+
status: input.status,
|
|
958
|
+
reason: input.reason,
|
|
959
|
+
resolvedAt: input.now.toISOString()
|
|
960
|
+
});
|
|
870
961
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
871
962
|
});
|
|
872
963
|
}
|
|
@@ -889,68 +980,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
889
980
|
...binding.authority
|
|
890
981
|
};
|
|
891
982
|
}
|
|
892
|
-
beginRoleRunProviderTurn(input) {
|
|
893
|
-
return this.store.transaction((store) => {
|
|
894
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
895
|
-
const session = sessions?.sessions[input.agentId];
|
|
896
|
-
const binding = sessions?.providerBinding;
|
|
897
|
-
if (sessions === null || sessions === undefined
|
|
898
|
-
|| binding === null || binding === undefined
|
|
899
|
-
|| sessions.inFlight?.runId !== input.runId
|
|
900
|
-
|| binding.runId !== input.runId
|
|
901
|
-
|| session?.launchId !== input.launchId
|
|
902
|
-
|| session.nativeSessionId !== input.nativeSessionId
|
|
903
|
-
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
904
|
-
|| binding.authority.owner !== "controller")
|
|
905
|
-
return false;
|
|
906
|
-
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
907
|
-
attemptId: input.attemptId,
|
|
908
|
-
authorityEpoch: binding.authority.epoch,
|
|
909
|
-
submittedAt: input.now.toISOString()
|
|
910
|
-
}), input.now));
|
|
911
|
-
return true;
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
resolveRoleRunProviderSubmission(input) {
|
|
915
|
-
return this.store.transaction((store) => {
|
|
916
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
917
|
-
const binding = sessions?.providerBinding;
|
|
918
|
-
if (sessions === null || sessions === undefined
|
|
919
|
-
|| binding === null || binding === undefined
|
|
920
|
-
|| binding.runId !== input.runId
|
|
921
|
-
|| binding.turn?.attemptId !== input.attemptId
|
|
922
|
-
|| binding.turn.status !== "submitting")
|
|
923
|
-
return false;
|
|
924
|
-
const updated = input.status === "delivery-unknown"
|
|
925
|
-
? markProviderTurnDeliveryUnknown(binding, {
|
|
926
|
-
attemptId: input.attemptId,
|
|
927
|
-
observedAt: input.now.toISOString(),
|
|
928
|
-
reason: input.reason
|
|
929
|
-
})
|
|
930
|
-
: rejectProviderTurn(binding, {
|
|
931
|
-
attemptId: input.attemptId,
|
|
932
|
-
rejectedAt: input.now.toISOString(),
|
|
933
|
-
reason: input.reason
|
|
934
|
-
});
|
|
935
|
-
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
936
|
-
return true;
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
isRoleGenerationProviderReady(input) {
|
|
940
|
-
// A session.ready observation for this exact generation is the durable
|
|
941
|
-
// pre-input fence. This reads folded Driver truth
|
|
942
|
-
// only — no liveness, screen, or pane/PID inference. Served from the
|
|
943
|
-
// revision projection: a stale miss only repeats an idempotent first push.
|
|
944
|
-
return this.listEvents(input.taskId).some((event) => {
|
|
945
|
-
const observation = runtimeObservationFromTaskEvent(event);
|
|
946
|
-
return observation?.kind === "session.ready"
|
|
947
|
-
&& observation.fence.roleName === input.roleName
|
|
948
|
-
&& observation.fence.agentId === input.agentId
|
|
949
|
-
&& (input.launchId === undefined || observation.fence.launchId === input.launchId)
|
|
950
|
-
&& (input.nativeSessionId === undefined
|
|
951
|
-
|| observation.fence.nativeSessionId === input.nativeSessionId);
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
983
|
peekNextAgentRunId(taskId) {
|
|
955
984
|
return this.store.peekNextAgentRunId(taskId);
|
|
956
985
|
}
|
|
@@ -1211,12 +1240,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1211
1240
|
}
|
|
1212
1241
|
const owner = runtimeOwnerFromTarget(target);
|
|
1213
1242
|
markRuntimeOwnerSessionStopped(store, owner, now);
|
|
1214
|
-
if (owner.scope === "task") {
|
|
1215
|
-
const sessions = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
1216
|
-
if (sessions !== null && sessions.providerBinding !== null) {
|
|
1217
|
-
store.saveTaskRoleSessionSet(clearTaskRoleProviderRuntimeForCleanup(sessions, now));
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
1243
|
saveRuntimeLifecycleMailbox(store, mailbox);
|
|
1221
1244
|
return true;
|
|
1222
1245
|
});
|
|
@@ -1353,7 +1376,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1353
1376
|
now: input.now
|
|
1354
1377
|
}));
|
|
1355
1378
|
}
|
|
1356
|
-
if (input.
|
|
1379
|
+
if (input.run.mode !== "new"
|
|
1380
|
+
&& input.session !== null
|
|
1381
|
+
&& input.session.nativeSessionId !== undefined) {
|
|
1357
1382
|
saveTaskSession(store, role, {
|
|
1358
1383
|
...input.session,
|
|
1359
1384
|
nativeSessionId: input.session.nativeSessionId
|
|
@@ -1450,9 +1475,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
1450
1475
|
if (input.session !== null && input.session.nativeSessionId !== undefined) {
|
|
1451
1476
|
const existing = store.getRoleSession(input.task.id, input.role.name);
|
|
1452
1477
|
matchingPreparedRuntimeReservation(store, input);
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1478
|
+
const defersConversationReplacement = active.mode === "new"
|
|
1479
|
+
&& existing !== null
|
|
1480
|
+
&& existing.nativeSessionId !== input.session.nativeSessionId;
|
|
1481
|
+
if (!defersConversationReplacement
|
|
1482
|
+
&& (existing?.nativeSessionId !== input.session.nativeSessionId
|
|
1483
|
+
|| (input.launchId !== undefined && existing.launchId !== input.launchId)
|
|
1484
|
+
|| existing.status !== "running")) {
|
|
1456
1485
|
saveTaskSession(store, role, {
|
|
1457
1486
|
...input.session,
|
|
1458
1487
|
nativeSessionId: input.session.nativeSessionId
|
|
@@ -1496,7 +1525,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1496
1525
|
|| !preparedReservationMatches(runtimeMailbox, input.taskId, input.runId, input.launchId)) {
|
|
1497
1526
|
return "state-changed";
|
|
1498
1527
|
}
|
|
1499
|
-
const summary =
|
|
1528
|
+
const summary = input.summary
|
|
1529
|
+
?? `Role delivery retry limit exhausted before exact Run input delivery: ${input.runId}.`;
|
|
1500
1530
|
const result = terminalizeExactTaskRun(store, {
|
|
1501
1531
|
taskId: input.taskId,
|
|
1502
1532
|
roleName: input.roleName,
|
|
@@ -1512,11 +1542,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
1512
1542
|
if (result.disposition !== "applied" || result.run === null) {
|
|
1513
1543
|
return "state-changed";
|
|
1514
1544
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1545
|
+
if (input.cleanupRequired !== false) {
|
|
1546
|
+
enqueueWork(store, runtimeLifecycleTarget({
|
|
1547
|
+
scope: "task",
|
|
1548
|
+
taskId: input.taskId,
|
|
1549
|
+
roleName: input.roleName
|
|
1550
|
+
}), RUNTIME_CLEANUP_REQUIRED_REASON, input.now, [{ type: "task", id: input.taskId }]);
|
|
1551
|
+
}
|
|
1520
1552
|
const terminal = result.run;
|
|
1521
1553
|
const deliveryFailureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
|
|
1522
1554
|
runId: terminal.id,
|
|
@@ -2430,7 +2462,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2430
2462
|
/**
|
|
2431
2463
|
* Issue 04: reopens each due retry on its original Native Session. A Run
|
|
2432
2464
|
* whose Session is proven dead terminalizes with an exact replacement
|
|
2433
|
-
* blocker; a live Session is
|
|
2465
|
+
* blocker; a live Session is reopened for the existing delivery path, which
|
|
2434
2466
|
* re-pushes the exact same input in the same pass.
|
|
2435
2467
|
*/
|
|
2436
2468
|
resolveDueProviderRetries(now, taskIds) {
|
|
@@ -2463,9 +2495,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
2463
2495
|
|| !providerRetryIsDue(run.providerRetry, now)) {
|
|
2464
2496
|
return;
|
|
2465
2497
|
}
|
|
2466
|
-
|
|
2498
|
+
const sessions = store.getTaskRoleSessionSet(entry.taskId, entry.roleName);
|
|
2467
2499
|
const providerBinding = sessions?.providerBinding;
|
|
2468
2500
|
let recoveryMode = "resume";
|
|
2501
|
+
let replacementRequired = false;
|
|
2469
2502
|
if (providerBinding === null || providerBinding === undefined) {
|
|
2470
2503
|
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider Conversation identity is missing; automatic recovery is fenced.", now);
|
|
2471
2504
|
return;
|
|
@@ -2510,13 +2543,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2510
2543
|
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider replacement lacks an exact missing-Conversation observation.", now);
|
|
2511
2544
|
return;
|
|
2512
2545
|
}
|
|
2513
|
-
|
|
2514
|
-
const currentSession = sessions?.sessions[run.effective.agentId];
|
|
2515
|
-
if (sessions !== null && currentSession !== undefined
|
|
2516
|
-
&& currentSession.status !== "broken") {
|
|
2517
|
-
sessions = updateRoleAgentSessionStatus(sessions, run.effective.agentId, "broken", now);
|
|
2518
|
-
store.saveTaskRoleSessionSet(sessions);
|
|
2519
|
-
}
|
|
2546
|
+
replacementRequired = true;
|
|
2520
2547
|
}
|
|
2521
2548
|
const session = sessions?.sessions[run.effective.agentId];
|
|
2522
2549
|
const identityMatches = session !== undefined
|
|
@@ -2549,7 +2576,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
2549
2576
|
routeRoleEvent(store, attentionEvent, entry.roleName, "provider-retry-native-resume-unproven", now);
|
|
2550
2577
|
return;
|
|
2551
2578
|
}
|
|
2552
|
-
if (
|
|
2579
|
+
if (runHasActiveRuntimeOperations(store.listEvents(entry.taskId), {
|
|
2580
|
+
taskId: entry.taskId,
|
|
2581
|
+
roleName: entry.roleName,
|
|
2582
|
+
runId: run.id,
|
|
2583
|
+
agentId: run.effective.agentId
|
|
2584
|
+
})) {
|
|
2553
2585
|
const deferred = deferProviderRetry(run.providerRetry, now);
|
|
2554
2586
|
if (deferred === null) {
|
|
2555
2587
|
finalizeProviderRetryDeadline(store, run, "Provider retry stopped because tool/subagent outcome remained uncertain through the bounded episode deadline.", "operation-outcome-uncertain", now);
|
|
@@ -2558,6 +2590,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
2558
2590
|
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
2559
2591
|
return;
|
|
2560
2592
|
}
|
|
2593
|
+
if (replacementRequired) {
|
|
2594
|
+
try {
|
|
2595
|
+
recoveryMode = roleSessionDispatchModeWithConversationSwitch(sessions, store.listEvents(entry.taskId), roleMailbox, entry.roleName, run.effective.agentId, run.effective);
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, error instanceof Error
|
|
2599
|
+
? error.message
|
|
2600
|
+
: "Independent Conversation-switch evaluation failed closed.", now);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
if (recoveryMode !== "new") {
|
|
2604
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, "Independent Conversation-switch evaluation kept the current Conversation.", now);
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2561
2608
|
// Reopen the Run on its original Session: reset transport markers,
|
|
2562
2609
|
// switch to resume, and mark the projection in-flight. The delivery
|
|
2563
2610
|
// pass re-pushes the exact same input in this same pass.
|
|
@@ -2667,7 +2714,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
2667
2714
|
recordCanonicalObservationObsolete(store, input, "bind-state-missing", now);
|
|
2668
2715
|
return "obsolete";
|
|
2669
2716
|
}
|
|
2670
|
-
const
|
|
2717
|
+
const mailbox = store.getWorkMailbox({
|
|
2718
|
+
kind: "role",
|
|
2719
|
+
taskId,
|
|
2720
|
+
roleName: input.fence.roleName
|
|
2721
|
+
});
|
|
2722
|
+
const existingNativeSessionId = sessions.sessions[input.fence.agentId]
|
|
2723
|
+
?.nativeSessionId;
|
|
2724
|
+
const replacingNativeSession = existingNativeSessionId !== undefined
|
|
2725
|
+
&& existingNativeSessionId !== decision.outcome.nativeSessionId;
|
|
2726
|
+
const replacementBasis = replacingNativeSession
|
|
2727
|
+
? conversationReplacementBasis({
|
|
2728
|
+
sessions,
|
|
2729
|
+
events: store.listEvents(taskId),
|
|
2730
|
+
mailbox,
|
|
2731
|
+
roleName: input.fence.roleName,
|
|
2732
|
+
runId: run.id,
|
|
2733
|
+
runMode: run.mode
|
|
2734
|
+
})
|
|
2735
|
+
: null;
|
|
2736
|
+
if (replacingNativeSession && replacementBasis === null) {
|
|
2737
|
+
recordCanonicalObservationObsolete(store, input, "conversation-switch-not-authorized", now);
|
|
2738
|
+
return "obsolete";
|
|
2739
|
+
}
|
|
2740
|
+
const sessionInput = {
|
|
2671
2741
|
agentId: input.fence.agentId,
|
|
2672
2742
|
adapterId,
|
|
2673
2743
|
nativeSessionId: decision.outcome.nativeSessionId,
|
|
@@ -2675,9 +2745,20 @@ export class FileSchedulerStoreAdapter {
|
|
|
2675
2745
|
policy: "fixed",
|
|
2676
2746
|
status: "running",
|
|
2677
2747
|
effective: run.effective
|
|
2678
|
-
}
|
|
2679
|
-
const
|
|
2748
|
+
};
|
|
2749
|
+
const bound = replacementBasis === null
|
|
2750
|
+
? recordRoleAgentSession(sessions, sessionInput, now)
|
|
2751
|
+
: replaceTaskRoleAgentSessionForConversationSwitch(sessions, sessionInput, now);
|
|
2752
|
+
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis ?? undefined);
|
|
2680
2753
|
store.saveTaskRoleSessionSet(withProvider);
|
|
2754
|
+
if (replacementBasis === "actor-request") {
|
|
2755
|
+
resolveConversationSwitchRequest(store, taskId, input.fence.roleName, sessions, "applied", now, {
|
|
2756
|
+
conversationId: input.fence.conversationId
|
|
2757
|
+
?? decision.outcome.nativeSessionId,
|
|
2758
|
+
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2759
|
+
launchId: input.fence.launchId
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2681
2762
|
const driver = this.drivers.require(input.fence.driverId);
|
|
2682
2763
|
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
2683
2764
|
&& run.pushedAt === undefined) {
|
|
@@ -2898,7 +2979,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
2898
2979
|
return null;
|
|
2899
2980
|
const sessionSet = store.getTaskRoleSessionSet(fence.taskId, fence.roleName);
|
|
2900
2981
|
const session = sessionSet?.sessions[fence.agentId] ?? null;
|
|
2901
|
-
const boundNativeSessionId = session?.nativeSessionId;
|
|
2902
2982
|
if (runId === undefined) {
|
|
2903
2983
|
return {
|
|
2904
2984
|
fence,
|
|
@@ -2907,7 +2987,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2907
2987
|
pushed: false,
|
|
2908
2988
|
accepted: false,
|
|
2909
2989
|
terminal: false,
|
|
2910
|
-
...(
|
|
2990
|
+
...(session?.nativeSessionId === undefined
|
|
2991
|
+
? {}
|
|
2992
|
+
: { boundNativeSessionId: session.nativeSessionId })
|
|
2911
2993
|
};
|
|
2912
2994
|
}
|
|
2913
2995
|
const run = store.getAgentRun(fence.taskId, runId);
|
|
@@ -2919,8 +3001,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
2919
3001
|
|| inFlight.agentId !== run.effective.agentId)
|
|
2920
3002
|
return null;
|
|
2921
3003
|
const owner = { scope: "task", taskId: fence.taskId, roleName: fence.roleName };
|
|
2922
|
-
const
|
|
2923
|
-
|
|
3004
|
+
const freshConversationLaunch = run.mode === "new"
|
|
3005
|
+
&& runtimeHookMatchesReservation(store, owner, fence.launchId);
|
|
3006
|
+
const boundNativeSessionId = freshConversationLaunch
|
|
3007
|
+
? undefined
|
|
3008
|
+
: session?.nativeSessionId;
|
|
3009
|
+
const launchId = freshConversationLaunch
|
|
3010
|
+
? fence.launchId
|
|
3011
|
+
: session?.launchId
|
|
3012
|
+
?? (runtimeHookMatchesReservation(store, owner, fence.launchId) ? fence.launchId : undefined);
|
|
2924
3013
|
if (launchId === undefined)
|
|
2925
3014
|
return null;
|
|
2926
3015
|
const expectedFence = {
|
|
@@ -3037,14 +3126,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
3037
3126
|
return "apply";
|
|
3038
3127
|
}
|
|
3039
3128
|
}
|
|
3040
|
-
function hasUnsettledRuntimeOperations(store, taskId, runId) {
|
|
3041
|
-
return store.listEvents(taskId).some((event) => {
|
|
3042
|
-
const observation = runtimeObservationFromTaskEvent(event);
|
|
3043
|
-
return observation !== null
|
|
3044
|
-
&& observation.fence.runId === runId
|
|
3045
|
-
&& observation.kind === "operation.started";
|
|
3046
|
-
});
|
|
3047
|
-
}
|
|
3048
3129
|
function runtimeTurnFailureSummary(input) {
|
|
3049
3130
|
return [
|
|
3050
3131
|
"Agent runtime turn failed.",
|
|
@@ -3270,9 +3351,12 @@ function markRuntimeOwnerSessionStopped(store, owner, now) {
|
|
|
3270
3351
|
if (sessions === null)
|
|
3271
3352
|
return false;
|
|
3272
3353
|
const active = sessions.sessions[sessions.activeAgentId];
|
|
3273
|
-
if (active === undefined
|
|
3354
|
+
if (active === undefined)
|
|
3274
3355
|
return false;
|
|
3275
|
-
|
|
3356
|
+
const stopped = stopTaskRoleRuntimeAfterPhysicalExit(sessions, now);
|
|
3357
|
+
if (stopped === sessions)
|
|
3358
|
+
return false;
|
|
3359
|
+
store.saveTaskRoleSessionSet(stopped);
|
|
3276
3360
|
return true;
|
|
3277
3361
|
}
|
|
3278
3362
|
const sessions = store.getGlobalRoleSessionSet(owner.roleName);
|
|
@@ -3688,6 +3772,19 @@ function schedulerRoleSessionsMatch(current, expected) {
|
|
|
3688
3772
|
&& current.launchId === expected.launchId
|
|
3689
3773
|
&& isDeepStrictEqual(current.effective, expected.effective);
|
|
3690
3774
|
}
|
|
3775
|
+
/**
|
|
3776
|
+
* The durable Run/mailbox claim is the pre-Host delivery intent. The Host may
|
|
3777
|
+
* create a ProviderTurn only while that exact reservation still owns the Run;
|
|
3778
|
+
* this closes the gap where a Session could start without any user Turn.
|
|
3779
|
+
*/
|
|
3780
|
+
function processingOwnsExactInitialDelivery(mailbox, taskId, runId, attemptId) {
|
|
3781
|
+
const processing = mailbox?.processing;
|
|
3782
|
+
return processing?.batchId === attemptId
|
|
3783
|
+
&& processing.owner === "controller"
|
|
3784
|
+
&& processing.executionRef?.type === "run"
|
|
3785
|
+
&& processing.executionRef.taskId === taskId
|
|
3786
|
+
&& processing.executionRef.id === runId;
|
|
3787
|
+
}
|
|
3691
3788
|
function matchesStallSessionFence(current, expected) {
|
|
3692
3789
|
if (current === null || expected === null)
|
|
3693
3790
|
return current === expected;
|
|
@@ -3838,7 +3935,7 @@ function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId)
|
|
|
3838
3935
|
? turn.turnId
|
|
3839
3936
|
: observedTurnId;
|
|
3840
3937
|
}
|
|
3841
|
-
function bindOrSupersedeProviderRuntime(sessions, input, now) {
|
|
3938
|
+
function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
|
|
3842
3939
|
if (sessions.inFlight === null)
|
|
3843
3940
|
return sessions;
|
|
3844
3941
|
const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
@@ -3873,9 +3970,22 @@ function bindOrSupersedeProviderRuntime(sessions, input, now) {
|
|
|
3873
3970
|
conversationId,
|
|
3874
3971
|
activationId,
|
|
3875
3972
|
switchedAt: input.observedAt ?? input.receivedAt,
|
|
3876
|
-
noUnsettledInputDelivery
|
|
3973
|
+
noUnsettledInputDelivery,
|
|
3974
|
+
...(replacementBasis === undefined ? {} : { basis: replacementBasis })
|
|
3877
3975
|
}), now);
|
|
3878
3976
|
}
|
|
3977
|
+
function resolveConversationSwitchRequest(store, taskId, roleName, previousSessions, status, now, replacement) {
|
|
3978
|
+
const request = pendingConversationSwitch(store.listEvents(taskId), roleName, previousSessions);
|
|
3979
|
+
if (request === null)
|
|
3980
|
+
return;
|
|
3981
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, CONVERSATION_SWITCH_RESOLVED_EVENT, {
|
|
3982
|
+
requestId: request.requestId,
|
|
3983
|
+
roleName,
|
|
3984
|
+
generation: request.generation,
|
|
3985
|
+
status,
|
|
3986
|
+
...(replacement === undefined ? {} : replacement)
|
|
3987
|
+
}, now));
|
|
3988
|
+
}
|
|
3879
3989
|
function rejectSubmittingProviderTurn(store, target, delivery, now, reason) {
|
|
3880
3990
|
if (target.kind !== "role"
|
|
3881
3991
|
|| delivery.executionRef.type !== "run"
|