@zq-silk/yui 0.8.1 → 0.8.3
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/ARCHITECTURE.md +27 -28
- package/README.md +57 -46
- package/dist/cli/commandCatalog.js +57 -17
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +106 -27
- package/dist/commands/taskCommands.js +458 -77
- package/dist/commands/taskCompletionGate.js +152 -0
- package/dist/context/runContextPack.js +19 -4
- package/dist/context/sessionBootstrapManifest.js +1 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
- package/dist/controller/resourceInventory.js +9 -5
- package/dist/controller/runtime.js +80 -7
- package/dist/controller/runtimeLaunchCoordinator.js +18 -78
- package/dist/controller/structuredProviderObservation.js +273 -0
- package/dist/executor/agentAdapter.js +40 -0
- package/dist/executor/agentExecutor.js +31 -7
- package/dist/executor/executorRegistry.js +11 -49
- package/dist/executor/fileRoleLaunchPlanner.js +115 -37
- package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
- package/dist/repository/gitWorkspace.js +7 -4
- package/dist/repository/taskBaseFreshness.js +4 -2
- package/dist/run/agentRun.js +4 -4
- package/dist/runtime/agentHost.js +767 -158
- package/dist/runtime/builtinAgentDrivers.js +1 -5
- package/dist/runtime/codexAppServerRuntime.js +67 -60
- package/dist/runtime/exactControlPlane.js +7 -2
- package/dist/runtime/index.js +6 -2
- package/dist/runtime/launchBroker.js +30 -8
- package/dist/runtime/providerAuthorityFence.js +24 -0
- package/dist/runtime/providerControl.js +63 -0
- package/dist/runtime/providerRecoveryDecision.js +55 -0
- package/dist/runtime/providerRuntimeIdentity.js +269 -19
- package/dist/runtime/runtimeBinding.js +20 -11
- package/dist/runtime/structuredProviderHost.js +476 -0
- package/dist/runtime/tmuxAdapters.js +143 -42
- package/dist/scheduler/activeRoleRunDelivery.js +206 -120
- package/dist/scheduler/leaderWakeupProcessor.js +141 -16
- package/dist/scheduler/wakeReason.js +1 -0
- package/dist/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/sqliteStore.js +2 -0
- package/dist/storage/taskStore.js +3 -1
- package/dist/task/completionReadiness.js +43 -0
- package/dist/task/nextAction.js +6 -4
- package/dist/task/publicationReference.js +1 -0
- package/dist/tmux/tmuxManager.js +1 -1
- package/dist/workItem/workItem.js +12 -0
- package/dist/workspace/workItemChangeSetManager.js +2 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -3
- package/skills/yui-runtime/SKILL.md +7 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, recordRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
|
-
import { createProviderRuntimeBinding, endProviderActivation, startProviderActivation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
3
|
+
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, prepareTaskRoleRunRedispatch, recordRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
|
+
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
5
|
+
import { decideProviderRecovery } from "../runtime/providerRecoveryDecision.js";
|
|
5
6
|
import { hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
6
7
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
7
8
|
import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
|
|
@@ -40,6 +41,12 @@ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObserv
|
|
|
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";
|
|
44
|
+
export class AgentHostProviderTurnFenceError extends Error {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "AgentHostProviderTurnFenceError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
/** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
|
|
44
51
|
export class FileSchedulerStoreAdapter {
|
|
45
52
|
store;
|
|
@@ -145,7 +152,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
145
152
|
nativeSessionId: input.fence.nativeSessionId,
|
|
146
153
|
turnId: input.fence.nativeTurnId,
|
|
147
154
|
runId: input.fence.runId,
|
|
148
|
-
summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}
|
|
155
|
+
summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`,
|
|
156
|
+
providerStatus: "failed"
|
|
149
157
|
}, now);
|
|
150
158
|
outcome = boundary.disposition === "obsolete" ? "obsolete" : "applied";
|
|
151
159
|
break;
|
|
@@ -812,42 +820,129 @@ export class FileSchedulerStoreAdapter {
|
|
|
812
820
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
813
821
|
return sessions !== null && sessions.inFlight !== null;
|
|
814
822
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
823
|
+
beginAgentHostProviderTurn(input) {
|
|
824
|
+
this.store.transaction((store) => {
|
|
825
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
826
|
+
const session = sessions?.sessions[input.agentId];
|
|
827
|
+
const binding = sessions?.providerBinding;
|
|
828
|
+
if (sessions === null || sessions === undefined
|
|
829
|
+
|| binding === null || binding === undefined
|
|
830
|
+
|| sessions.inFlight?.runId !== input.runId
|
|
831
|
+
|| binding.runId !== input.runId
|
|
832
|
+
|| session?.launchId !== input.launchId
|
|
833
|
+
|| session.nativeSessionId !== input.nativeSessionId
|
|
834
|
+
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
835
|
+
|| binding.authority.owner !== input.authorityOwner
|
|
836
|
+
|| binding.authority.epoch !== input.authorityEpoch
|
|
837
|
+
|| binding.authority.holderId !== input.holderId) {
|
|
838
|
+
throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
|
|
839
|
+
}
|
|
840
|
+
const exactReplay = binding.turn?.attemptId === input.attemptId
|
|
841
|
+
&& binding.turn.authorityEpoch === input.authorityEpoch
|
|
842
|
+
&& binding.turn.status === "submitting";
|
|
843
|
+
if (!exactReplay && binding.turn !== null
|
|
844
|
+
&& ["submitting", "accepted", "running", "delivery-unknown"]
|
|
845
|
+
.includes(binding.turn.status)) {
|
|
846
|
+
throw new AgentHostProviderTurnFenceError("Provider Conversation already has an unsettled Turn.");
|
|
847
|
+
}
|
|
848
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
849
|
+
attemptId: input.attemptId,
|
|
850
|
+
authorityEpoch: input.authorityEpoch,
|
|
851
|
+
submittedAt: input.now.toISOString()
|
|
852
|
+
}), input.now));
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
resolveAgentHostProviderTurnSubmission(input) {
|
|
856
|
+
this.store.transaction((store) => {
|
|
857
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
858
|
+
const binding = sessions?.providerBinding;
|
|
859
|
+
if (sessions === null || sessions === undefined
|
|
860
|
+
|| binding === null || binding === undefined
|
|
861
|
+
|| binding.runId !== input.runId
|
|
862
|
+
|| binding.turn?.attemptId !== input.attemptId
|
|
863
|
+
|| binding.turn.status !== "submitting") {
|
|
864
|
+
throw new Error("Agent Host Provider Turn submission is no longer current.");
|
|
865
|
+
}
|
|
866
|
+
const updated = input.status === "delivery-unknown"
|
|
867
|
+
? markProviderTurnDeliveryUnknown(binding, {
|
|
868
|
+
attemptId: input.attemptId,
|
|
869
|
+
observedAt: input.now.toISOString(),
|
|
870
|
+
reason: input.reason
|
|
871
|
+
})
|
|
872
|
+
: rejectProviderTurn(binding, {
|
|
873
|
+
attemptId: input.attemptId,
|
|
874
|
+
rejectedAt: input.now.toISOString(),
|
|
875
|
+
reason: input.reason
|
|
876
|
+
});
|
|
877
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
getProviderAuthorityFence(input) {
|
|
881
|
+
const sessions = this.store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
882
|
+
const session = sessions?.sessions[input.agentId];
|
|
883
|
+
const binding = sessions?.providerBinding;
|
|
884
|
+
if (binding === null || binding === undefined
|
|
885
|
+
|| binding.runId !== input.runId
|
|
886
|
+
|| session?.launchId !== input.launchId
|
|
887
|
+
|| session.nativeSessionId !== input.nativeSessionId
|
|
888
|
+
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId)
|
|
889
|
+
return null;
|
|
890
|
+
const activation = currentProviderActivation(binding);
|
|
891
|
+
if (activation === null)
|
|
843
892
|
return null;
|
|
844
|
-
const observation = fences.get(active[0][0]);
|
|
845
893
|
return {
|
|
846
|
-
conversationId:
|
|
847
|
-
activationId:
|
|
848
|
-
|
|
894
|
+
conversationId: currentProviderConversation(binding).conversationId,
|
|
895
|
+
activationId: activation.activationId,
|
|
896
|
+
...binding.authority
|
|
849
897
|
};
|
|
850
898
|
}
|
|
899
|
+
beginRoleRunProviderTurn(input) {
|
|
900
|
+
return this.store.transaction((store) => {
|
|
901
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
902
|
+
const session = sessions?.sessions[input.agentId];
|
|
903
|
+
const binding = sessions?.providerBinding;
|
|
904
|
+
if (sessions === null || sessions === undefined
|
|
905
|
+
|| binding === null || binding === undefined
|
|
906
|
+
|| sessions.inFlight?.runId !== input.runId
|
|
907
|
+
|| binding.runId !== input.runId
|
|
908
|
+
|| session?.launchId !== input.launchId
|
|
909
|
+
|| session.nativeSessionId !== input.nativeSessionId
|
|
910
|
+
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
911
|
+
|| binding.authority.owner !== "controller")
|
|
912
|
+
return false;
|
|
913
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
914
|
+
attemptId: input.attemptId,
|
|
915
|
+
authorityEpoch: binding.authority.epoch,
|
|
916
|
+
submittedAt: input.now.toISOString()
|
|
917
|
+
}), input.now));
|
|
918
|
+
return true;
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
resolveRoleRunProviderSubmission(input) {
|
|
922
|
+
return this.store.transaction((store) => {
|
|
923
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
924
|
+
const binding = sessions?.providerBinding;
|
|
925
|
+
if (sessions === null || sessions === undefined
|
|
926
|
+
|| binding === null || binding === undefined
|
|
927
|
+
|| binding.runId !== input.runId
|
|
928
|
+
|| binding.turn?.attemptId !== input.attemptId
|
|
929
|
+
|| binding.turn.status !== "submitting")
|
|
930
|
+
return false;
|
|
931
|
+
const updated = input.status === "delivery-unknown"
|
|
932
|
+
? markProviderTurnDeliveryUnknown(binding, {
|
|
933
|
+
attemptId: input.attemptId,
|
|
934
|
+
observedAt: input.now.toISOString(),
|
|
935
|
+
reason: input.reason
|
|
936
|
+
})
|
|
937
|
+
: rejectProviderTurn(binding, {
|
|
938
|
+
attemptId: input.attemptId,
|
|
939
|
+
rejectedAt: input.now.toISOString(),
|
|
940
|
+
reason: input.reason
|
|
941
|
+
});
|
|
942
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
943
|
+
return true;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
851
946
|
isRoleGenerationProviderReady(input) {
|
|
852
947
|
// A session.ready observation for this exact generation is the durable
|
|
853
948
|
// pre-input fence. This reads folded Driver truth
|
|
@@ -971,6 +1066,31 @@ export class FileSchedulerStoreAdapter {
|
|
|
971
1066
|
executionRef: input.executionRef,
|
|
972
1067
|
providerFence: input.providerFence
|
|
973
1068
|
});
|
|
1069
|
+
if (input.target.kind === "role"
|
|
1070
|
+
&& input.executionRef.type === "run"
|
|
1071
|
+
&& input.executionRef.taskId === input.target.taskId) {
|
|
1072
|
+
const sessions = store.getTaskRoleSessionSet(input.target.taskId, input.target.roleName);
|
|
1073
|
+
const binding = sessions?.providerBinding;
|
|
1074
|
+
if (sessions === null || sessions === undefined
|
|
1075
|
+
|| binding === null || binding === undefined
|
|
1076
|
+
|| binding.runId !== input.executionRef.id
|
|
1077
|
+
|| binding.authority.owner !== "controller") {
|
|
1078
|
+
throw new Error("Controller does not own the Provider writer authority.");
|
|
1079
|
+
}
|
|
1080
|
+
const conversation = currentProviderConversation(binding);
|
|
1081
|
+
const activation = currentProviderActivation(binding);
|
|
1082
|
+
if (input.providerFence === undefined
|
|
1083
|
+
|| conversation.conversationId !== input.providerFence.conversationId
|
|
1084
|
+
|| activation?.activationId !== input.providerFence.activationId) {
|
|
1085
|
+
throw new Error("Provider input claim carries a stale Conversation/Activation fence.");
|
|
1086
|
+
}
|
|
1087
|
+
const next = beginProviderTurn(binding, {
|
|
1088
|
+
attemptId: input.attemptId,
|
|
1089
|
+
authorityEpoch: binding.authority.epoch,
|
|
1090
|
+
submittedAt: input.now.toISOString()
|
|
1091
|
+
});
|
|
1092
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, next, input.now));
|
|
1093
|
+
}
|
|
974
1094
|
store.saveWorkMailbox(claimed);
|
|
975
1095
|
return { status: "claimed", delivery: claimed.inputDelivery };
|
|
976
1096
|
});
|
|
@@ -1009,20 +1129,22 @@ export class FileSchedulerStoreAdapter {
|
|
|
1009
1129
|
return true;
|
|
1010
1130
|
});
|
|
1011
1131
|
}
|
|
1012
|
-
releaseInputDelivery(target, attemptId) {
|
|
1132
|
+
releaseInputDelivery(target, attemptId, now) {
|
|
1013
1133
|
return this.store.transaction((store) => {
|
|
1014
1134
|
const mailbox = store.getWorkMailbox(target);
|
|
1015
1135
|
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
1016
1136
|
return false;
|
|
1137
|
+
rejectSubmittingProviderTurn(store, target, mailbox.inputDelivery, now, "Provider write was not attempted.");
|
|
1017
1138
|
store.saveWorkMailbox(releaseMailboxInputDelivery(mailbox, attemptId));
|
|
1018
1139
|
return true;
|
|
1019
1140
|
});
|
|
1020
1141
|
}
|
|
1021
|
-
resolveInputDeliveryNotAccepted(target, attemptId) {
|
|
1142
|
+
resolveInputDeliveryNotAccepted(target, attemptId, now) {
|
|
1022
1143
|
return this.store.transaction((store) => {
|
|
1023
1144
|
const mailbox = store.getWorkMailbox(target);
|
|
1024
1145
|
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
1025
1146
|
return false;
|
|
1147
|
+
rejectSubmittingProviderTurn(store, target, mailbox.inputDelivery, now, "Provider returned an exact negative acknowledgement.");
|
|
1026
1148
|
store.saveWorkMailbox(resolveMailboxInputDeliveryNotAccepted(mailbox, attemptId));
|
|
1027
1149
|
return true;
|
|
1028
1150
|
});
|
|
@@ -1033,6 +1155,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1033
1155
|
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
1034
1156
|
return false;
|
|
1035
1157
|
const unknown = markMailboxInputDeliveryUnknown(mailbox, attemptId, reason, now);
|
|
1158
|
+
markSubmittingProviderTurnUnknown(store, target, mailbox.inputDelivery, now, reason);
|
|
1036
1159
|
store.saveWorkMailbox(unknown);
|
|
1037
1160
|
const taskId = target.kind === "role" ? target.taskId : undefined;
|
|
1038
1161
|
if (taskId !== undefined) {
|
|
@@ -1872,6 +1995,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1872
1995
|
: sessions.inFlight === null ? idleStatus : "running",
|
|
1873
1996
|
effective
|
|
1874
1997
|
}, now);
|
|
1998
|
+
sessions = settleStructuredProviderTurn(sessions, input.turnId, input.providerStatus ?? "completed", now);
|
|
1875
1999
|
if (sessions.inFlight === null) {
|
|
1876
2000
|
sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId, input.turnId, now);
|
|
1877
2001
|
store.saveTaskRoleSessionSet(sessions);
|
|
@@ -2137,16 +2261,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2137
2261
|
}
|
|
2138
2262
|
const retry = retryDecision.retry;
|
|
2139
2263
|
store.saveAgentRun(withProviderRetry(run, retry));
|
|
2140
|
-
//
|
|
2141
|
-
|
|
2142
|
-
//
|
|
2143
|
-
if (sessions !== null && sessions.inFlight !== null) {
|
|
2144
|
-
store.saveTaskRoleSessionSet(clearTaskRoleRun(sessions, {
|
|
2145
|
-
agentId: input.agentId,
|
|
2146
|
-
runId: input.runId,
|
|
2147
|
-
receiptId: agentRunDeliveryReceiptId(run)
|
|
2148
|
-
}, now));
|
|
2149
|
-
}
|
|
2264
|
+
// The in-flight fence and Provider Conversation stay bound while the
|
|
2265
|
+
// retry waits. resolveDueProviderRetries advances only the delivery
|
|
2266
|
+
// receipt immediately before the exact redispatch.
|
|
2150
2267
|
// Settle the delivery claim so the retry push can re-claim the mailbox.
|
|
2151
2268
|
const target = runtimeLifecycleTarget({
|
|
2152
2269
|
scope: "task",
|
|
@@ -2387,7 +2504,61 @@ export class FileSchedulerStoreAdapter {
|
|
|
2387
2504
|
|| !providerRetryIsDue(run.providerRetry, now)) {
|
|
2388
2505
|
return;
|
|
2389
2506
|
}
|
|
2390
|
-
|
|
2507
|
+
let sessions = store.getTaskRoleSessionSet(entry.taskId, entry.roleName);
|
|
2508
|
+
const providerBinding = sessions?.providerBinding;
|
|
2509
|
+
let recoveryMode = "resume";
|
|
2510
|
+
if (providerBinding === null || providerBinding === undefined) {
|
|
2511
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider Conversation identity is missing; automatic recovery is fenced.", now);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const conversation = currentProviderConversation(providerBinding);
|
|
2515
|
+
const roleMailbox = store.getWorkMailbox({
|
|
2516
|
+
kind: "role",
|
|
2517
|
+
taskId: entry.taskId,
|
|
2518
|
+
roleName: entry.roleName
|
|
2519
|
+
});
|
|
2520
|
+
const activeTurnId = providerBinding.turn !== null
|
|
2521
|
+
&& ["accepted", "running"].includes(providerBinding.turn.status)
|
|
2522
|
+
? providerBinding.turn.turnId
|
|
2523
|
+
: undefined;
|
|
2524
|
+
const recovery = decideProviderRecovery({
|
|
2525
|
+
binding: providerBinding,
|
|
2526
|
+
probe: {
|
|
2527
|
+
state: conversation.recoverability === "recoverable"
|
|
2528
|
+
? "exists"
|
|
2529
|
+
: conversation.recoverability === "unrecoverable" ? "missing" : "unknown",
|
|
2530
|
+
conversationId: conversation.conversationId,
|
|
2531
|
+
...(activeTurnId === undefined ? {} : { activeTurnId })
|
|
2532
|
+
},
|
|
2533
|
+
unsettledInputDelivery: roleMailbox?.inputDelivery != null
|
|
2534
|
+
});
|
|
2535
|
+
if (recovery.action === "attention") {
|
|
2536
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, recovery.reason, now);
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (recovery.action === "observe-active-turn") {
|
|
2540
|
+
const deferred = deferProviderRetry(run.providerRetry, now);
|
|
2541
|
+
if (deferred === null) {
|
|
2542
|
+
finalizeProviderRetryDeadline(store, run, `Provider Turn ${recovery.turnId} remained active through the bounded recovery deadline.`, "provider-progress-timeout", now);
|
|
2543
|
+
}
|
|
2544
|
+
else {
|
|
2545
|
+
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
2546
|
+
}
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (recovery.action === "replace") {
|
|
2550
|
+
if (conversation.recoverability !== "unrecoverable") {
|
|
2551
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider replacement lacks an exact missing-Conversation observation.", now);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
recoveryMode = "new";
|
|
2555
|
+
const currentSession = sessions?.sessions[run.effective.agentId];
|
|
2556
|
+
if (sessions !== null && currentSession !== undefined
|
|
2557
|
+
&& currentSession.status !== "broken") {
|
|
2558
|
+
sessions = updateRoleAgentSessionStatus(sessions, run.effective.agentId, "broken", now);
|
|
2559
|
+
store.saveTaskRoleSessionSet(sessions);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2391
2562
|
const session = sessions?.sessions[run.effective.agentId];
|
|
2392
2563
|
const identityMatches = session !== undefined
|
|
2393
2564
|
&& session.adapterId === run.effective.adapterId
|
|
@@ -2432,7 +2603,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
2432
2603
|
// Reopen the Run on its original Session: reset transport markers,
|
|
2433
2604
|
// switch to resume, and mark the projection in-flight. The delivery
|
|
2434
2605
|
// pass re-pushes the exact same input in this same pass.
|
|
2435
|
-
|
|
2606
|
+
const reopenedRun = reopenRunForProviderRetry(run, `provider-retry-${run.providerRetry.episodeId}-${run.providerRetry.dispatchedRetries + 1}`, now, recoveryMode);
|
|
2607
|
+
store.saveAgentRun(reopenedRun);
|
|
2608
|
+
if (sessions !== null && sessions.inFlight !== null) {
|
|
2609
|
+
store.saveTaskRoleSessionSet(prepareTaskRoleRunRedispatch(sessions, {
|
|
2610
|
+
agentId: run.effective.agentId,
|
|
2611
|
+
runId: run.id,
|
|
2612
|
+
receiptId: agentRunDeliveryReceiptId(reopenedRun)
|
|
2613
|
+
}, now));
|
|
2614
|
+
}
|
|
2436
2615
|
// The retry decision settled the original delivery claim; re-queue the
|
|
2437
2616
|
// Role mailbox so the delivery pass can claim and re-push the Run.
|
|
2438
2617
|
enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: entry.roleName }, "provider-retry-repush", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
|
|
@@ -2451,7 +2630,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2451
2630
|
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
2452
2631
|
const run = store.getAgentRun(taskId, input.fence.runId);
|
|
2453
2632
|
if (sessions === null || sessions.providerBinding === null || run === null
|
|
2454
|
-
||
|
|
2633
|
+
|| sessions.providerBinding.runId !== input.fence.runId
|
|
2455
2634
|
|| input.fence.conversationId === undefined) {
|
|
2456
2635
|
recordCanonicalObservationObsolete(store, input, "provider-binding-missing", now);
|
|
2457
2636
|
return "obsolete";
|
|
@@ -2539,16 +2718,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2539
2718
|
status: "running",
|
|
2540
2719
|
effective: run.effective
|
|
2541
2720
|
}, now);
|
|
2542
|
-
const withProvider = bound
|
|
2543
|
-
? bound
|
|
2544
|
-
: bindTaskRoleProviderRuntime(bound, createProviderRuntimeBinding({
|
|
2545
|
-
providerNamespace: input.fence.driverId,
|
|
2546
|
-
accountScope: input.fence.agentId,
|
|
2547
|
-
runId: bound.inFlight.runId,
|
|
2548
|
-
conversationId: input.fence.conversationId ?? decision.outcome.nativeSessionId,
|
|
2549
|
-
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2550
|
-
startedAt: bound.inFlight.preparedAt
|
|
2551
|
-
}), now);
|
|
2721
|
+
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now);
|
|
2552
2722
|
store.saveTaskRoleSessionSet(withProvider);
|
|
2553
2723
|
const driver = this.drivers.require(input.fence.driverId);
|
|
2554
2724
|
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
@@ -2563,17 +2733,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2563
2733
|
const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2564
2734
|
const currentSession = current?.sessions[input.fence.agentId];
|
|
2565
2735
|
if (current !== null && current !== undefined
|
|
2566
|
-
&& current.providerBinding === null
|
|
2567
2736
|
&& current.inFlight !== null
|
|
2568
2737
|
&& currentSession?.nativeSessionId === input.fence.nativeSessionId) {
|
|
2569
|
-
store.saveTaskRoleSessionSet(
|
|
2570
|
-
providerNamespace: input.fence.driverId,
|
|
2571
|
-
accountScope: input.fence.agentId,
|
|
2572
|
-
runId: current.inFlight.runId,
|
|
2573
|
-
conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
|
|
2574
|
-
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2575
|
-
startedAt: current.inFlight.preparedAt
|
|
2576
|
-
}), now));
|
|
2738
|
+
store.saveTaskRoleSessionSet(bindOrSupersedeProviderRuntime(current, input, now));
|
|
2577
2739
|
}
|
|
2578
2740
|
return "applied";
|
|
2579
2741
|
}
|
|
@@ -2598,6 +2760,32 @@ export class FileSchedulerStoreAdapter {
|
|
|
2598
2760
|
&& inputDelivery.executionRef.taskId === input.fence.taskId
|
|
2599
2761
|
&& inputDelivery.executionRef.id === input.fence.runId
|
|
2600
2762
|
&& inputDelivery.attemptId !== formatAgentRunReceiptId(input.fence.taskId, input.fence.runId);
|
|
2763
|
+
const humanAttemptId = input.fence.receiptId;
|
|
2764
|
+
if (humanAttemptId !== undefined && humanAttemptId.startsWith("human:")) {
|
|
2765
|
+
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
2766
|
+
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2767
|
+
const session = sessions?.sessions[input.fence.agentId];
|
|
2768
|
+
const binding = sessions?.providerBinding;
|
|
2769
|
+
if (active === null || active.id !== input.fence.runId
|
|
2770
|
+
|| sessions === null || sessions === undefined
|
|
2771
|
+
|| binding === null || binding === undefined
|
|
2772
|
+
|| binding.authority.owner !== "human"
|
|
2773
|
+
|| !humanAttemptId.startsWith(`human:${binding.authority.holderId}:`)
|
|
2774
|
+
|| binding.turn?.attemptId !== humanAttemptId
|
|
2775
|
+
|| session?.launchId !== input.fence.launchId
|
|
2776
|
+
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2777
|
+
recordCanonicalObservationObsolete(store, input, "human-turn-fence-mismatch", now);
|
|
2778
|
+
return "obsolete";
|
|
2779
|
+
}
|
|
2780
|
+
store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(updateRoleAgentSessionStatus(sessions, input.fence.agentId, "running", now), input, now));
|
|
2781
|
+
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.human-input-delivered", {
|
|
2782
|
+
attemptId: humanAttemptId,
|
|
2783
|
+
runId: active.id,
|
|
2784
|
+
authorityEpoch: String(binding.authority.epoch),
|
|
2785
|
+
nativeTurnId: input.fence.nativeTurnId
|
|
2786
|
+
}, now));
|
|
2787
|
+
return "applied";
|
|
2788
|
+
}
|
|
2601
2789
|
if (continuation) {
|
|
2602
2790
|
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
2603
2791
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
@@ -2612,7 +2800,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2612
2800
|
recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
|
|
2613
2801
|
return "obsolete";
|
|
2614
2802
|
}
|
|
2615
|
-
store.saveTaskRoleSessionSet(updateRoleAgentSessionStatus(sessions, input.fence.agentId, "running", now));
|
|
2803
|
+
store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(updateRoleAgentSessionStatus(sessions, input.fence.agentId, "running", now), input, now));
|
|
2616
2804
|
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
2617
2805
|
if (role !== null && role.status !== "running") {
|
|
2618
2806
|
store.saveRole(input.fence.taskId, updateRoleStatus(role, "running", now));
|
|
@@ -2659,6 +2847,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
2659
2847
|
recordCanonicalObservationObsolete(store, input, "role-missing", now);
|
|
2660
2848
|
return "obsolete";
|
|
2661
2849
|
}
|
|
2850
|
+
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2851
|
+
if (sessions !== null) {
|
|
2852
|
+
store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
|
|
2853
|
+
}
|
|
2662
2854
|
const progressed = clearProviderRetryOnProgress(active);
|
|
2663
2855
|
if (progressed !== active) {
|
|
2664
2856
|
store.saveAgentRun(progressed);
|
|
@@ -3648,6 +3840,133 @@ function compactedRuntimeObservationIds(events, incoming) {
|
|
|
3648
3840
|
};
|
|
3649
3841
|
return existing.filter(remove).map(({ event }) => event.id);
|
|
3650
3842
|
}
|
|
3843
|
+
function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
3844
|
+
const current = sessions.providerBinding;
|
|
3845
|
+
const attemptId = input.fence.receiptId;
|
|
3846
|
+
const turnId = input.fence.nativeTurnId;
|
|
3847
|
+
if (current === null || attemptId === undefined || turnId === undefined)
|
|
3848
|
+
return sessions;
|
|
3849
|
+
if (current.turn?.attemptId === attemptId
|
|
3850
|
+
&& current.turn.turnId === turnId
|
|
3851
|
+
&& ["accepted", "running", "completed", "failed", "cancelled"].includes(current.turn.status))
|
|
3852
|
+
return sessions;
|
|
3853
|
+
const binding = acceptProviderTurn(current, {
|
|
3854
|
+
attemptId,
|
|
3855
|
+
turnId,
|
|
3856
|
+
acceptedAt: input.observedAt ?? input.receivedAt
|
|
3857
|
+
});
|
|
3858
|
+
return updateTaskRoleProviderRuntime(sessions, binding, now);
|
|
3859
|
+
}
|
|
3860
|
+
function bindOrSupersedeProviderRuntime(sessions, input, now) {
|
|
3861
|
+
if (sessions.inFlight === null)
|
|
3862
|
+
return sessions;
|
|
3863
|
+
const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
3864
|
+
const activationId = input.fence.activationId ?? input.fence.launchId;
|
|
3865
|
+
if (sessions.providerBinding === null) {
|
|
3866
|
+
return bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
|
|
3867
|
+
providerNamespace: input.fence.driverId,
|
|
3868
|
+
accountScope: input.fence.agentId,
|
|
3869
|
+
runId: sessions.inFlight.runId,
|
|
3870
|
+
conversationId,
|
|
3871
|
+
activationId,
|
|
3872
|
+
startedAt: sessions.inFlight.preparedAt
|
|
3873
|
+
}), now);
|
|
3874
|
+
}
|
|
3875
|
+
const current = currentProviderConversation(sessions.providerBinding);
|
|
3876
|
+
if (current.conversationId === conversationId) {
|
|
3877
|
+
const active = currentProviderActivation(sessions.providerBinding);
|
|
3878
|
+
if (active?.activationId === activationId)
|
|
3879
|
+
return sessions;
|
|
3880
|
+
if (active !== null) {
|
|
3881
|
+
throw new Error("Provider Conversation cannot start a second Activation while the current one is active.");
|
|
3882
|
+
}
|
|
3883
|
+
return updateTaskRoleProviderRuntime(sessions, startProviderActivation(sessions.providerBinding, {
|
|
3884
|
+
activationId,
|
|
3885
|
+
startedAt: input.observedAt ?? input.receivedAt
|
|
3886
|
+
}), now);
|
|
3887
|
+
}
|
|
3888
|
+
const turn = sessions.providerBinding.turn;
|
|
3889
|
+
const noUnsettledInputDelivery = turn === null
|
|
3890
|
+
|| ["completed", "failed", "cancelled", "rejected"].includes(turn.status);
|
|
3891
|
+
return updateTaskRoleProviderRuntime(sessions, supersedeProviderConversation(sessions.providerBinding, {
|
|
3892
|
+
conversationId,
|
|
3893
|
+
activationId,
|
|
3894
|
+
switchedAt: input.observedAt ?? input.receivedAt,
|
|
3895
|
+
noUnsettledInputDelivery
|
|
3896
|
+
}), now);
|
|
3897
|
+
}
|
|
3898
|
+
function rejectSubmittingProviderTurn(store, target, delivery, now, reason) {
|
|
3899
|
+
if (target.kind !== "role"
|
|
3900
|
+
|| delivery.executionRef.type !== "run"
|
|
3901
|
+
|| delivery.executionRef.taskId !== target.taskId)
|
|
3902
|
+
return;
|
|
3903
|
+
const sessions = store.getTaskRoleSessionSet(target.taskId, target.roleName);
|
|
3904
|
+
const binding = sessions?.providerBinding;
|
|
3905
|
+
if (sessions === null || sessions === undefined
|
|
3906
|
+
|| binding === null || binding === undefined
|
|
3907
|
+
|| binding.runId !== delivery.executionRef.id
|
|
3908
|
+
|| binding.turn?.attemptId !== delivery.attemptId
|
|
3909
|
+
|| (binding.turn.status !== "submitting"
|
|
3910
|
+
&& binding.turn.status !== "delivery-unknown"))
|
|
3911
|
+
return;
|
|
3912
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, rejectProviderTurn(binding, {
|
|
3913
|
+
attemptId: delivery.attemptId,
|
|
3914
|
+
rejectedAt: now.toISOString(),
|
|
3915
|
+
reason
|
|
3916
|
+
}), now));
|
|
3917
|
+
}
|
|
3918
|
+
function deferUnprovenProviderRecovery(store, run, roleName, reason, now) {
|
|
3919
|
+
if (run.providerRetry === undefined)
|
|
3920
|
+
return;
|
|
3921
|
+
const deferred = deferProviderRetry(run.providerRetry, now);
|
|
3922
|
+
if (deferred === null) {
|
|
3923
|
+
finalizeProviderRetryDeadline(store, run, reason, "native-resume-unproven", now);
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
3927
|
+
store.saveEvent(run.taskId, createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-recovery-attention", {
|
|
3928
|
+
runId: run.id,
|
|
3929
|
+
roleName,
|
|
3930
|
+
reason,
|
|
3931
|
+
nextAttemptAt: deferred.nextAttemptAt ?? ""
|
|
3932
|
+
}, now));
|
|
3933
|
+
enqueueWork(store, { kind: "operator" }, "provider-recovery-attention", now, [
|
|
3934
|
+
{ type: "task", id: run.taskId },
|
|
3935
|
+
{ type: "run", taskId: run.taskId, id: run.id }
|
|
3936
|
+
]);
|
|
3937
|
+
}
|
|
3938
|
+
function markSubmittingProviderTurnUnknown(store, target, delivery, now, reason) {
|
|
3939
|
+
if (target.kind !== "role"
|
|
3940
|
+
|| delivery.executionRef.type !== "run"
|
|
3941
|
+
|| delivery.executionRef.taskId !== target.taskId)
|
|
3942
|
+
return;
|
|
3943
|
+
const sessions = store.getTaskRoleSessionSet(target.taskId, target.roleName);
|
|
3944
|
+
const binding = sessions?.providerBinding;
|
|
3945
|
+
if (sessions === null || sessions === undefined
|
|
3946
|
+
|| binding === null || binding === undefined
|
|
3947
|
+
|| binding.runId !== delivery.executionRef.id
|
|
3948
|
+
|| binding.turn?.attemptId !== delivery.attemptId
|
|
3949
|
+
|| binding.turn.status !== "submitting")
|
|
3950
|
+
return;
|
|
3951
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, markProviderTurnDeliveryUnknown(binding, {
|
|
3952
|
+
attemptId: delivery.attemptId,
|
|
3953
|
+
observedAt: now.toISOString(),
|
|
3954
|
+
reason
|
|
3955
|
+
}), now));
|
|
3956
|
+
}
|
|
3957
|
+
function settleStructuredProviderTurn(sessions, turnId, status, now) {
|
|
3958
|
+
const binding = sessions.providerBinding;
|
|
3959
|
+
if (binding === null || binding.turn === null || binding.turn.turnId !== turnId) {
|
|
3960
|
+
return sessions;
|
|
3961
|
+
}
|
|
3962
|
+
if (["completed", "failed", "cancelled"].includes(binding.turn.status))
|
|
3963
|
+
return sessions;
|
|
3964
|
+
return updateTaskRoleProviderRuntime(sessions, settleProviderTurn(binding, {
|
|
3965
|
+
turnId,
|
|
3966
|
+
status,
|
|
3967
|
+
settledAt: now.toISOString()
|
|
3968
|
+
}), now);
|
|
3969
|
+
}
|
|
3651
3970
|
function confirmedUsageActivityObservation(events, incoming) {
|
|
3652
3971
|
const usage = incoming.payload.usage;
|
|
3653
3972
|
if (incoming.kind !== "activity.observed" || usage === undefined)
|
|
@@ -113,16 +113,17 @@ export function buildControllerResourceInventory(facts) {
|
|
|
113
113
|
for (const process of paneProcesses)
|
|
114
114
|
claimed.add(process.pid);
|
|
115
115
|
const role = findRole(homeFact.roles, pane);
|
|
116
|
-
const
|
|
116
|
+
const terminalIsolation = role?.taskStatus === "retired"
|
|
117
|
+
|| role?.taskStatus === "archived";
|
|
117
118
|
resources.push(processResource({
|
|
118
119
|
kind: "agent-session",
|
|
119
120
|
state: pane.dead ? "dead" : role === undefined ? "orphaned" : "running",
|
|
120
121
|
disposition: role === undefined
|
|
121
122
|
? pane.dead ? "safe" : "review"
|
|
122
|
-
:
|
|
123
|
+
: terminalIsolation ? "safe" : "protected",
|
|
123
124
|
reasonCode: role === undefined
|
|
124
125
|
? pane.dead ? "dead-orphan-pane" : "orphan-pane"
|
|
125
|
-
:
|
|
126
|
+
: terminalIsolation ? `${role.taskStatus}-task-pane` : "owned-role-pane",
|
|
126
127
|
yuiHome,
|
|
127
128
|
owner: role === undefined ? { kind: "none" } : roleOwner(role),
|
|
128
129
|
processes: paneProcesses,
|
|
@@ -359,7 +360,10 @@ function artifactResource(artifact, yuiHome, domain, homeId) {
|
|
|
359
360
|
...(domain === undefined ? {} : { domain })
|
|
360
361
|
};
|
|
361
362
|
}
|
|
362
|
-
function domainDisposition(base,
|
|
363
|
+
function domainDisposition(base, reason, domain, target) {
|
|
364
|
+
if (reason === "retired-task-pane" || reason === "archived-task-pane") {
|
|
365
|
+
return "safe";
|
|
366
|
+
}
|
|
363
367
|
if (domain === undefined)
|
|
364
368
|
return base;
|
|
365
369
|
if (domain.disposition === "safe") {
|
|
@@ -377,7 +381,7 @@ function domainDisposition(base, _reason, domain, target) {
|
|
|
377
381
|
// A tmux server with panes is protected until the pane resources have
|
|
378
382
|
// been removed and a later bounded pass can revalidate the empty
|
|
379
383
|
// server. This avoids killing a server while a target race is in flight.
|
|
380
|
-
if (base === "protected" &&
|
|
384
|
+
if (base === "protected" && reason === "owned-tmux-server")
|
|
381
385
|
return "protected";
|
|
382
386
|
// `report-only` is deliberately reserved for an unrecognized process;
|
|
383
387
|
// the YUI_HOME environment alone is not a cleanup ownership proof.
|