@zq-silk/yui 0.13.4 → 0.13.6

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.
Files changed (54) hide show
  1. package/ARCHITECTURE.md +13 -13
  2. package/README.md +19 -20
  3. package/dist/cli/commandCatalog.js +22 -21
  4. package/dist/cli/interactionPolicy.js +0 -14
  5. package/dist/cli.js +42 -0
  6. package/dist/commands/executionAuditCommands.js +1 -1
  7. package/dist/commands/taskCommands.js +60 -326
  8. package/dist/commands/taskContextCommand.js +3 -14
  9. package/dist/commands/taskExecutionCommands.js +254 -0
  10. package/dist/commands/taskNextActionCommand.js +1 -3
  11. package/dist/commands/taskOverviewCommand.js +9 -2
  12. package/dist/commands/taskRoleRuntimeStatus.js +2 -25
  13. package/dist/controller/agentRuntimeObserver.js +4 -2
  14. package/dist/controller/clientRuntime.js +45 -2
  15. package/dist/controller/controller.js +6 -3
  16. package/dist/controller/fileSchedulerStoreAdapter.js +59 -188
  17. package/dist/controller/jobControl.js +3 -2
  18. package/dist/controller/runtime.js +6 -33
  19. package/dist/controller/runtimeEventProcessor.js +8 -4
  20. package/dist/controller/runtimeHookRunFence.js +4 -10
  21. package/dist/execution/executionHealth.js +8 -16
  22. package/dist/executor/agentAdapter.js +3 -8
  23. package/dist/executor/agentExecutor.js +13 -14
  24. package/dist/executor/fileRoleLaunchPlanner.js +10 -26
  25. package/dist/lifecycle/exactRunTerminalization.js +24 -322
  26. package/dist/repository/taskWorkspaceCoordinator.js +0 -9
  27. package/dist/runtime/agentHost.js +22 -83
  28. package/dist/runtime/builtinAgentDrivers.js +1 -1
  29. package/dist/runtime/exactControlPlane.js +15 -9
  30. package/dist/runtime/launchBroker.js +1 -11
  31. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  32. package/dist/runtime/providerRecoveryDecision.js +1 -1
  33. package/dist/runtime/providerRuntimeIdentity.js +25 -15
  34. package/dist/runtime/structuredProviderHost.js +0 -57
  35. package/dist/scheduler/activeRoleRunDelivery.js +1 -19
  36. package/dist/scheduler/leaderWakeupProcessor.js +12 -63
  37. package/dist/scheduler/ports.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +4 -1
  39. package/dist/scheduler/roleRunStall.js +0 -2
  40. package/dist/scheduler/taskExecutionProjection.js +18 -1
  41. package/dist/scheduler/wakeupQueue.js +2 -1
  42. package/dist/storage/migration/productionRegistry.js +65 -0
  43. package/dist/storage/sqliteStore.js +10 -2
  44. package/dist/storage/taskStore.js +11 -3
  45. package/dist/task/completionReadiness.js +0 -67
  46. package/dist/task/nextAction.js +16 -32
  47. package/dist/task/task.js +38 -3
  48. package/dist/web/assets/client/i18n.js +0 -4
  49. package/dist/web/assets/client/view.js +0 -18
  50. package/dist/web/webSnapshot.js +7 -13
  51. package/i18n/README.zh-CN.md +4 -4
  52. package/package.json +1 -1
  53. package/dist/run/recoveryProjection.js +0 -252
  54. package/dist/runtime/conversationSwitch.js +0 -277
@@ -1,252 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
3
- import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
4
- import { runOwnsBlockingProviderContinuation } from "../runtime/runtimeContinuationProjection.js";
5
- import { runHasActiveRuntimeOperations } from "../runtime/runtimeObservation.js";
6
- export const RUN_RECOVERY_ACTIONS = [
7
- "diagnose",
8
- "retry",
9
- "terminate"
10
- ];
11
- /**
12
- * Reads every durable record the recovery projection needs. Returns null
13
- * only when the Run itself is absent.
14
- */
15
- export function readRunRecoveryFacts(store, taskId, runId) {
16
- const run = store.getAgentRun(taskId, runId);
17
- if (run === null || run.taskId !== taskId)
18
- return null;
19
- const task = store.getTask(taskId);
20
- const sessionSet = store.getTaskRoleSessionSet(taskId, run.roleName);
21
- const events = store.listEvents(taskId);
22
- const roleMailbox = store.getWorkMailbox?.({
23
- kind: "role",
24
- taskId,
25
- roleName: run.roleName
26
- }) ?? null;
27
- const progress = latestRunDurableProgressAt(store, taskId, run.roleName, runId);
28
- return {
29
- run,
30
- task: task === null ? null : { id: task.id, status: task.status },
31
- sessionSet,
32
- inputDeliveryUnsettled: roleMailbox?.inputDelivery != null,
33
- blockingProviderContinuation: runOwnsBlockingProviderContinuation(events, {
34
- taskId,
35
- roleName: run.roleName,
36
- runId: run.id,
37
- agentId: run.effective.agentId
38
- }),
39
- activeRuntimeOperation: runHasActiveRuntimeOperations(events, {
40
- taskId,
41
- roleName: run.roleName,
42
- runId: run.id,
43
- agentId: run.effective.agentId
44
- }),
45
- progress,
46
- latestProviderObservation: latestRunProviderObservation(events, runId)
47
- };
48
- }
49
- /**
50
- * Resolve the exact live-Run recovery plans referenced by Lane health. Failed
51
- * terminal Lanes use `task run retry` directly and therefore need no live-Run
52
- * recovery projection here.
53
- */
54
- export function projectExecutionLaneRunRecoveries(store, taskId, groups) {
55
- const runIds = new Set(actionableExecutionLaneRecoveries(groups).flatMap((lane) => (lane.runId === undefined || lane.recovery === "retry-new-agent-run"
56
- ? []
57
- : [lane.runId])));
58
- return [...runIds].flatMap((runId) => {
59
- const facts = readRunRecoveryFacts(store, taskId, runId);
60
- return facts === null ? [] : [projectRunRecovery(facts)];
61
- });
62
- }
63
- /**
64
- * Latest Provider observation for a Run. Provider timestamps are evidence:
65
- * they explain why a stale fence was supplied but never authorize recovery.
66
- */
67
- function latestRunProviderObservation(events, runId) {
68
- let latest = null;
69
- for (const event of events) {
70
- if (event.type !== "runtime.observation")
71
- continue;
72
- if (event.payload.runId !== runId)
73
- continue;
74
- const kind = typeof event.payload.kind === "string" ? event.payload.kind : "unknown";
75
- const receivedAt = typeof event.payload.receivedAt === "string"
76
- && Number.isFinite(Date.parse(event.payload.receivedAt))
77
- ? event.payload.receivedAt
78
- : event.createdAt;
79
- const at = Date.parse(receivedAt);
80
- if (latest === null || at > latest.at) {
81
- latest = { kind, receivedAt, at };
82
- }
83
- }
84
- return latest === null ? null : { kind: latest.kind, receivedAt: latest.receivedAt };
85
- }
86
- export function projectRunRecovery(facts) {
87
- const { run, task, sessionSet, progress } = facts;
88
- const session = activeSession(facts);
89
- const canonicalProgressAt = progress?.progressAt ?? null;
90
- const accepted = run.deliveredAt !== undefined;
91
- const acceptanceOptions = accepted
92
- ? ["accepted", "ambiguous"]
93
- : ["rejected", "ambiguous"];
94
- const blocked = recoveryBlocker(facts, session, canonicalProgressAt);
95
- const sessionTerminal = session?.status === "stopped" || session?.status === "broken";
96
- const providerTerminal = sessionSet?.providerBinding?.turn?.status === "failed"
97
- || sessionSet?.providerBinding?.turn?.status === "cancelled"
98
- || sessionSet?.providerBinding?.turn?.status === "rejected";
99
- const supportedActions = RUN_RECOVERY_ACTIONS.filter((action) => ((action !== "retry" || !sessionTerminal)
100
- && (action !== "terminate" || sessionTerminal || providerTerminal)));
101
- const actions = blocked === null
102
- ? supportedActions.map((action) => buildActionPlan(facts, action, session, canonicalProgressAt))
103
- : [];
104
- const judgmentRequired = blocked === null && actions.some((plan) => plan.argv.includes(PROVIDER_ACCEPTANCE_PLACEHOLDER))
105
- ? "Provider acceptance is not durably determined for every action; pass --provider-acceptance explicitly."
106
- : undefined;
107
- return {
108
- taskId: run.taskId,
109
- runId: run.id,
110
- roleName: run.roleName,
111
- runStatus: run.status,
112
- recoverable: blocked === null,
113
- canonicalProgressAt,
114
- ...(progress?.evidence === undefined ? {} : { canonicalProgressEvidence: progress.evidence }),
115
- provider: {
116
- acceptedAt: run.deliveredAt ?? null,
117
- observedAt: facts.latestProviderObservation?.receivedAt ?? null,
118
- observationKind: facts.latestProviderObservation?.kind ?? null
119
- },
120
- providerAcceptance: {
121
- accepted,
122
- options: acceptanceOptions
123
- },
124
- session: session === null ? null : {
125
- status: session.status,
126
- ...(session.nativeSessionId === undefined ? {} : { nativeSessionId: session.nativeSessionId }),
127
- ...(session.launchId === undefined ? {} : { launchId: session.launchId })
128
- },
129
- actions,
130
- ...(judgmentRequired === undefined ? {} : { judgmentRequired }),
131
- ...(blocked === null ? {} : { reason: blocked })
132
- };
133
- }
134
- const PROVIDER_ACCEPTANCE_PLACEHOLDER = "<accepted|rejected|ambiguous>";
135
- function activeSession(facts) {
136
- const sessions = facts.sessionSet;
137
- if (sessions === null)
138
- return null;
139
- const session = sessions.sessions[sessions.activeAgentId];
140
- if (session === undefined)
141
- return null;
142
- if (session.agentId !== facts.run.effective.agentId)
143
- return null;
144
- if (session.adapterId !== facts.run.effective.adapterId)
145
- return null;
146
- return session;
147
- }
148
- /**
149
- * Mirrors the fail-closed checks of `recoverExactAgentRun` that are visible
150
- * from durable records. A non-null result means recovery cannot currently be
151
- * applied; the canonical fence is still projected for diagnosis.
152
- */
153
- function recoveryBlocker(facts, session, canonicalProgressAt) {
154
- const { run, task } = facts;
155
- if (task === null)
156
- return "task-missing";
157
- if (task.status !== "active")
158
- return "task-terminal";
159
- if (run.status !== "active")
160
- return "run-terminal";
161
- if (canonicalProgressAt === null)
162
- return "progress-unavailable";
163
- if (session === null)
164
- return "session-missing";
165
- if (facts.inputDeliveryUnsettled)
166
- return "provider-input-delivery-unsettled";
167
- if (facts.blockingProviderContinuation)
168
- return "provider-continuation-writer-owned";
169
- if (facts.activeRuntimeOperation)
170
- return "provider-operation-active";
171
- const binding = facts.sessionSet?.providerBinding;
172
- if (run.deliveredAt !== undefined
173
- && (binding === null || binding?.turn === null))
174
- return "provider-turn-state-missing";
175
- if (binding !== null && binding !== undefined) {
176
- if (["submitting", "accepted", "running", "delivery-unknown"].includes(binding.turn?.status ?? ""))
177
- return "provider-turn-unsettled";
178
- if (binding.authority.owner === "human" || binding.authority.owner === "unknown") {
179
- return "provider-writer-authority-unavailable";
180
- }
181
- }
182
- return null;
183
- }
184
- function buildActionPlan(facts, action, session, canonicalProgressAt) {
185
- const { run } = facts;
186
- const acceptance = actionAcceptance(facts, action);
187
- const argv = [
188
- "task",
189
- "run",
190
- "recover",
191
- `${run.taskId}/${run.id}`,
192
- "--action",
193
- action,
194
- "--expected-progress-at",
195
- canonicalProgressAt,
196
- "--provider-acceptance",
197
- acceptance,
198
- "--reason",
199
- "<text>",
200
- "--agent-id",
201
- run.effective.agentId,
202
- "--adapter-id",
203
- run.effective.adapterId,
204
- ...(session.nativeSessionId === undefined
205
- ? []
206
- : ["--native-session-id", session.nativeSessionId]),
207
- ...(session.launchId === undefined
208
- ? []
209
- : ["--launch-id", session.launchId])
210
- ];
211
- const command = `yui ${argv
212
- .map((part) => (part === "<text>" ? '"<text>"' : part))
213
- .join(" ")}`;
214
- const fingerprintSource = [
215
- run.id,
216
- action,
217
- canonicalProgressAt,
218
- run.effective.agentId,
219
- run.effective.adapterId,
220
- session.nativeSessionId ?? "",
221
- session.launchId ?? ""
222
- ].join("|");
223
- return {
224
- action,
225
- reason: ACTION_REASONS[action],
226
- expectedProgressAt: canonicalProgressAt,
227
- agentId: run.effective.agentId,
228
- adapterId: run.effective.adapterId,
229
- ...(session.nativeSessionId === undefined
230
- ? {}
231
- : { nativeSessionId: session.nativeSessionId }),
232
- ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
233
- command,
234
- argv,
235
- fingerprint: createHash("sha256").update(fingerprintSource).digest("hex")
236
- };
237
- }
238
- /**
239
- * The acceptance value for the copy-paste command. When exactly one value is
240
- * durably valid it is filled in (the durable record, not a guess); otherwise
241
- * the Leader must choose and the command carries an explicit placeholder.
242
- */
243
- function actionAcceptance(facts, action) {
244
- if (action === "diagnose")
245
- return PROVIDER_ACCEPTANCE_PLACEHOLDER;
246
- return facts.run.deliveredAt === undefined ? "rejected" : "accepted";
247
- }
248
- const ACTION_REASONS = {
249
- diagnose: "Collect bounded diagnostics before any state-changing recovery.",
250
- retry: "Request another provider turn on the same native Session when the failure is transient.",
251
- terminate: "Fail the Run explicitly when recovery is not viable."
252
- };
@@ -1,277 +0,0 @@
1
- import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
2
- import { effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
3
- import { currentProviderActivation, currentProviderConversation } from "./providerRuntimeIdentity.js";
4
- import { blockingProviderContinuations } from "./runtimeContinuationProjection.js";
5
- import { runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
6
- export const CONVERSATION_SWITCH_REQUESTED_EVENT = "runtime.conversation-switch-requested";
7
- export const CONVERSATION_SWITCH_RESOLVED_EVENT = "runtime.conversation-switch-resolved";
8
- export const CONVERSATION_SWITCH_DETACHED_EVENT = "runtime.conversation-switch-detached";
9
- export function providerConversationGeneration(sessions) {
10
- const binding = sessions?.providerBinding;
11
- if (binding === null || binding === undefined)
12
- return null;
13
- const current = currentProviderConversation(binding);
14
- return `${binding.providerNamespace}:${binding.accountScope}:${current.epoch}:${current.conversationId}`;
15
- }
16
- export function projectConversationSwitch(events, roleName, sessions) {
17
- const requests = events.filter((event) => (event.type === CONVERSATION_SWITCH_REQUESTED_EVENT
18
- && event.payload.roleName === roleName));
19
- const request = requests.at(-1);
20
- if (request === undefined)
21
- return null;
22
- const requestId = request.payload.requestId;
23
- const generation = request.payload.generation;
24
- const requestedBy = request.payload.requestedBy;
25
- const reason = request.payload.reason;
26
- if (requestId === undefined || generation === undefined || reason === undefined
27
- || (requestedBy !== "user" && requestedBy !== "operator" && requestedBy !== "leader")) {
28
- return null;
29
- }
30
- const resolution = [...events].reverse().find((event) => (event.type === CONVERSATION_SWITCH_RESOLVED_EVENT
31
- && event.payload.requestId === requestId));
32
- const explicitStatus = resolution?.payload.status;
33
- const currentGeneration = providerConversationGeneration(sessions);
34
- const status = explicitStatus === "applied" || explicitStatus === "obsolete"
35
- ? explicitStatus
36
- : currentGeneration !== null && currentGeneration !== generation
37
- ? "obsolete"
38
- : "pending";
39
- return {
40
- requestId,
41
- roleName,
42
- generation,
43
- requestedBy,
44
- reason,
45
- requestedAt: request.createdAt,
46
- status,
47
- ...(resolution === undefined ? {} : { resolvedAt: resolution.createdAt })
48
- };
49
- }
50
- export function pendingConversationSwitch(events, roleName, sessions) {
51
- const projected = projectConversationSwitch(events, roleName, sessions);
52
- return projected?.status === "pending" ? projected : null;
53
- }
54
- export function roleSessionDispatchModeWithConversationSwitch(sessions, events, mailbox, roleName, agentId, effective) {
55
- const ordinary = roleAgentSessionResumeMode(sessions, agentId, effective);
56
- if (ordinary !== "resume") {
57
- if (sessions?.providerBinding !== null && sessions?.providerBinding !== undefined) {
58
- if (freshConversationLaunchAllowed({ sessions, events, mailbox, roleName })) {
59
- return "new";
60
- }
61
- const existing = sessions.sessions[agentId];
62
- if (existing?.nativeSessionId !== undefined
63
- && (existing.status === "stopped" || existing.status === "broken")
64
- && effectiveLaunchSnapshotsCompatibleForTaskSession(existing.effective, effective)) {
65
- // A terminal local Activation does not prove the Provider Conversation
66
- // is gone. Reattach to the same native identity by default.
67
- return "resume";
68
- }
69
- throw new Error(`Fresh Provider Conversation is not yet safe: ${sessions.owner.taskId}/${roleName}.`);
70
- }
71
- return ordinary;
72
- }
73
- if (sessions?.providerBinding !== null && sessions?.providerBinding !== undefined
74
- && freshConversationLaunchAllowed({ sessions, events, mailbox, roleName })) {
75
- return "new";
76
- }
77
- return "resume";
78
- }
79
- export function conversationDetachmentBasis(input) {
80
- const { sessions, mailbox } = input;
81
- const binding = sessions.providerBinding;
82
- const activation = binding === null ? null : currentProviderActivation(binding);
83
- if (input.runMode !== "new" || binding === null || activation === null
84
- || sessions.inFlight?.runId !== input.runId
85
- || mailbox?.processing?.batchId !== sessions.inFlight.receiptId
86
- || mailbox.processing.owner !== "controller"
87
- || mailbox.processing.executionRef?.type !== "run"
88
- || mailbox.processing.executionRef.taskId !== sessions.owner.taskId
89
- || mailbox.processing.executionRef.id !== input.runId
90
- || !actorRequestedSwitchBoundaryReady(sessions, mailbox)
91
- || currentConversationExecutionBlockers(sessions, input.events, input.roleName).length > 0
92
- || pendingConversationSwitch(input.events, input.roleName, sessions) === null) {
93
- return null;
94
- }
95
- return "actor-request";
96
- }
97
- export function conversationReplacementBasis(input) {
98
- const { sessions, mailbox } = input;
99
- const binding = sessions.providerBinding;
100
- if (input.runMode !== "new" || binding === null
101
- || sessions.inFlight?.runId !== input.runId
102
- || mailbox?.processing?.batchId !== sessions.inFlight.receiptId
103
- || mailbox.processing.owner !== "controller"
104
- || mailbox?.processing?.executionRef?.type !== "run"
105
- || mailbox.processing.executionRef.taskId !== sessions.owner.taskId
106
- || mailbox.processing.executionRef.id !== input.runId
107
- || mailbox.inputDelivery !== null
108
- || !conversationIsQuiescent(sessions, mailbox)
109
- || currentConversationExecutionBlockers(sessions, input.events, input.roleName).length > 0) {
110
- return null;
111
- }
112
- if (currentConversationIsExactlyUnrecoverable(sessions, input.events, input.roleName))
113
- return "exact-unrecoverable";
114
- return pendingConversationSwitch(input.events, input.roleName, sessions) === null
115
- ? null
116
- : "actor-request";
117
- }
118
- export function freshConversationLaunchAllowed(input) {
119
- return freshConversationLaunchBlockers(input).length === 0;
120
- }
121
- /** Exact reasons a fresh Conversation cannot currently be admitted. */
122
- export function freshConversationLaunchBlockers(input) {
123
- const { sessions } = input;
124
- if (sessions === null)
125
- return [];
126
- const session = sessions.sessions[sessions.activeAgentId];
127
- if (sessions.providerBinding === null) {
128
- return session === undefined
129
- || session.status === "stopped"
130
- || session.status === "broken"
131
- ? []
132
- : ["native-session-not-terminal"];
133
- }
134
- const request = pendingConversationSwitch(input.events, input.roleName, sessions);
135
- const binding = sessions.providerBinding;
136
- const blockers = [];
137
- if (input.mailbox?.inputDelivery != null)
138
- blockers.push("provider-input-delivery-unsettled");
139
- if (binding.turn !== null
140
- && ["submitting", "accepted", "running", "delivery-unknown"].includes(binding.turn.status))
141
- blockers.push("provider-turn-unsettled");
142
- blockers.push(...currentConversationExecutionBlockers(sessions, input.events, input.roleName));
143
- if (blockers.length > 0)
144
- return blockers;
145
- if (request !== null && actorRequestedSwitchBoundaryReady(sessions, input.mailbox, input.candidateRunId))
146
- return [];
147
- if (currentProviderActivation(binding) !== null)
148
- blockers.push("provider-activation-active");
149
- if (binding.authority.owner !== "none")
150
- blockers.push("provider-writer-authority-owned");
151
- if (blockers.length > 0)
152
- return blockers;
153
- if (currentProviderConversation(binding).recoverability === "unrecoverable") {
154
- return currentConversationIsExactlyUnrecoverable(sessions, input.events, input.roleName) ? [] : ["exact-unrecoverable-evidence-missing"];
155
- }
156
- if (request !== null)
157
- return ["actor-switch-boundary-not-ready"];
158
- return ["current-conversation-recoverable"];
159
- }
160
- function actorRequestedSwitchBoundaryReady(sessions, mailbox, candidateRunId) {
161
- const binding = sessions?.providerBinding;
162
- if (sessions === null || sessions === undefined
163
- || binding === null || binding === undefined
164
- || mailbox?.inputDelivery != null)
165
- return false;
166
- const turnSettled = binding.turn === null
167
- || ["completed", "failed", "cancelled", "rejected"].includes(binding.turn.status);
168
- if (!turnSettled)
169
- return false;
170
- const session = sessions.sessions[sessions.activeAgentId];
171
- if (session?.status === "running")
172
- return false;
173
- const activation = currentProviderActivation(binding);
174
- if (activation === null)
175
- return binding.authority.owner === "none";
176
- if (binding.authority.owner !== "controller"
177
- || binding.authority.holderId !== activation.activationId)
178
- return false;
179
- if (session === undefined)
180
- return false;
181
- if (sessions.inFlight === null) {
182
- if (mailbox?.processing === null)
183
- return true;
184
- return candidateRunId !== undefined
185
- && mailbox?.processing?.owner === "controller"
186
- && mailbox.processing.executionRef?.type === "run"
187
- && mailbox.processing.executionRef.taskId === sessions.owner.taskId
188
- && mailbox.processing.executionRef.id === candidateRunId;
189
- }
190
- return mailbox?.processing?.batchId === sessions.inFlight.receiptId
191
- && mailbox.processing.owner === "controller"
192
- && mailbox.processing.executionRef?.type === "run"
193
- && mailbox.processing.executionRef.taskId === sessions.owner.taskId
194
- && mailbox.processing.executionRef.id === sessions.inFlight.runId;
195
- }
196
- function conversationIsQuiescent(sessions, mailbox) {
197
- const binding = sessions?.providerBinding;
198
- if (binding === null || binding === undefined)
199
- return false;
200
- const turnSettled = binding.turn === null
201
- || ["completed", "failed", "cancelled", "rejected"].includes(binding.turn.status);
202
- return turnSettled
203
- && currentProviderActivation(binding) === null
204
- && binding.authority.owner === "none"
205
- && mailbox?.inputDelivery == null;
206
- }
207
- /**
208
- * A projected recoverability flag is necessary but not sufficient to replace
209
- * a Conversation. Require the exact structured missing-Conversation fact for
210
- * the current Provider identity and latest Activation generation, so an old
211
- * observation cannot authorize a later fresh Conversation. The binding's Run
212
- * id is deliberately excluded: replacement work rebinds that fence before
213
- * launching, while the missing fact necessarily came from the preceding Run's
214
- * exact attempt to resume this same Activation.
215
- */
216
- function currentConversationIsExactlyUnrecoverable(sessions, events, roleName) {
217
- const binding = sessions.providerBinding;
218
- if (binding === null)
219
- return false;
220
- const conversation = currentProviderConversation(binding);
221
- if (conversation.recoverability !== "unrecoverable")
222
- return false;
223
- const activation = [...binding.activations].reverse().find((entry) => (entry.conversationId === conversation.conversationId));
224
- const generationStartedAt = activation?.startedAt ?? conversation.createdAt;
225
- return events.some((event) => {
226
- const observation = runtimeObservationFromTaskEvent(event);
227
- if (observation === null
228
- || observation.kind !== "conversation.observed"
229
- || observation.payload.recoverability !== "unrecoverable"
230
- || (observation.authority !== "provider-structured"
231
- && observation.authority !== "controller"))
232
- return false;
233
- const fence = observation.fence;
234
- if (fence.taskId !== sessions.owner.taskId
235
- || fence.roleName !== roleName
236
- || fence.agentId !== binding.accountScope
237
- || fence.driverId !== binding.providerNamespace
238
- || fence.conversationId !== conversation.conversationId
239
- || (activation !== undefined && fence.activationId !== activation.activationId)) {
240
- return false;
241
- }
242
- return Date.parse(observation.observedAt ?? observation.receivedAt)
243
- >= Date.parse(generationStartedAt);
244
- });
245
- }
246
- /** Current Provider-owned work that may outlive a foreground Turn. */
247
- function currentConversationExecutionBlockers(sessions, events, roleName) {
248
- const binding = sessions.providerBinding;
249
- if (binding === null)
250
- return [];
251
- const conversation = currentProviderConversation(binding);
252
- const activation = [...binding.activations].reverse().find((entry) => (entry.conversationId === conversation.conversationId));
253
- if (activation === undefined)
254
- return ["provider-activation-identity-missing"];
255
- const activeOperation = events.some((event) => {
256
- const observation = runtimeObservationFromTaskEvent(event);
257
- if (observation?.kind !== "operation.started")
258
- return false;
259
- const fence = observation.fence;
260
- return fence.taskId === sessions.owner.taskId
261
- && fence.roleName === roleName
262
- && fence.agentId === binding.accountScope
263
- && fence.driverId === binding.providerNamespace
264
- && (fence.conversationId ?? fence.nativeSessionId) === conversation.conversationId
265
- && (fence.activationId ?? fence.launchId) === activation.activationId;
266
- });
267
- const blockingContinuation = blockingProviderContinuations(events).some((entry) => (entry.taskId === sessions.owner.taskId
268
- && entry.roleName === roleName
269
- && entry.identity.providerNamespace === binding.providerNamespace
270
- && entry.identity.accountScope === binding.accountScope
271
- && entry.identity.conversationId === conversation.conversationId
272
- && entry.identity.activationId === activation.activationId));
273
- return [
274
- ...(activeOperation ? ["provider-operation-active"] : []),
275
- ...(blockingContinuation ? ["provider-continuation-writer-owned"] : [])
276
- ];
277
- }