@zq-silk/yui 2.21.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/cli/commandCatalog.js +87 -27
- package/dist/cli/dynamicCompletion.js +4 -1
- package/dist/cli/helpRenderer.js +12 -1
- package/dist/cli/interactionPolicy.js +2 -0
- package/dist/cli/invocationRouter.js +5 -5
- package/dist/cli/roleWizard.js +5 -1
- package/dist/cli/updateCommand.js +20 -3
- package/dist/cli/updateOrchestrator.js +13 -0
- package/dist/cli/updatePorts.js +27 -0
- package/dist/commands/globalRoleCommands.js +33 -8
- package/dist/commands/inboxCommands.js +39 -12
- package/dist/commands/operatorCommands.js +2 -5
- package/dist/commands/releaseCommands.js +16 -2
- package/dist/commands/roleConfiguration.js +25 -2
- package/dist/commands/sessionCommands.js +5 -2
- package/dist/commands/taskCommands.js +217 -64
- package/dist/commands/taskContextCommand.js +26 -4
- package/dist/commands/taskFactCommands.js +15 -2
- package/dist/commands/taskIntegrationCommands.js +3 -3
- package/dist/commands/taskRoleRuntimeStatus.js +19 -5
- package/dist/commands/textInput.js +2 -2
- package/dist/context/contextFamilies.js +10 -0
- package/dist/context/currentSkills.js +37 -4
- package/dist/context/runContextPack.js +44 -5
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +21 -6
- package/dist/context/sessionSkillNotice.js +74 -0
- package/dist/context/taskContext.js +100 -16
- package/dist/controlPlaneCli.js +17 -7
- package/dist/controller/agentRuntimeObserver.js +57 -12
- package/dist/controller/clientRuntime.js +66 -18
- package/dist/controller/controller.js +19 -2
- package/dist/controller/diagnosticJournal.js +10 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +74 -24
- package/dist/controller/hostLifecycle.js +153 -0
- package/dist/controller/hostLifecycleChecks.js +27 -0
- package/dist/controller/hostUpgrade.js +344 -0
- package/dist/controller/messageHealth.js +4 -3
- package/dist/controller/providerRetryAdmission.js +15 -1
- package/dist/controller/providerRetryDelivery.js +18 -2
- package/dist/controller/runtime.js +82 -21
- package/dist/controller/runtimeEventInbox.js +107 -13
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/controller/sessionNotify.js +5 -46
- package/dist/controller/taskRunRecovery.js +153 -0
- package/dist/core/controllerClient.js +16 -2
- package/dist/core/controllerServer.js +14 -5
- package/dist/core/controllerTelemetry.js +51 -5
- package/dist/errors/cliFailure.js +14 -5
- package/dist/execution/codexThreadNaming.js +54 -43
- package/dist/executor/agentAdapter.js +32 -4
- package/dist/executor/agentConfigurationFields.js +2 -0
- package/dist/executor/fileRoleLaunchPlanner.js +18 -3
- package/dist/integration/gitIntegrationService.js +3 -2
- package/dist/integration/integrationSourceApplication.js +12 -1
- package/dist/interaction/notificationPresentation.js +11 -16
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/messageContinuation.js +3 -1
- package/dist/output/boundedRead.js +36 -7
- package/dist/output/rolePresentation.js +3 -1
- package/dist/runtime/agentEndpoint.js +11 -1
- package/dist/runtime/agentEndpointIdentity.js +2 -0
- package/dist/runtime/agentHost.js +174 -40
- package/dist/runtime/agentHostImplementation.js +3 -1
- package/dist/runtime/codexAppServerRuntime.js +113 -24
- package/dist/runtime/codexInteractiveHost.js +102 -11
- package/dist/runtime/codexNativeAccess.js +1 -1
- package/dist/runtime/contextRecovery.js +15 -0
- package/dist/runtime/hostSessionLifecycle.js +83 -0
- package/dist/runtime/hostUpgradeGate.js +43 -0
- package/dist/runtime/lifecycleChecks.js +47 -0
- package/dist/runtime/lifecycleReservation.js +9 -1
- package/dist/runtime/nativeTitleReceipt.js +31 -0
- package/dist/runtime/nativeTitleSync.js +192 -0
- package/dist/runtime/providerRetry.js +9 -6
- package/dist/runtime/providerRuntimeIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +65 -35
- package/dist/scheduler/wakeupQueue.js +10 -4
- package/dist/storage/sqliteSchema.js +29 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/tmux/tmuxManager.js +4 -2
- package/dist/web/webSettings.js +2 -0
- package/docs/authorization.md +18 -3
- package/docs/cli-information-contract.md +228 -6
- package/docs/cli-information-contract.zh-CN.md +95 -5
- package/docs/managed-turn-and-session-runtime.md +177 -21
- package/docs/managed-turn-and-session-runtime.zh-CN.md +126 -14
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +65 -0
- package/docs/release-workflow.zh-CN.md +48 -0
- package/docs/roles-and-configuration.md +28 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/storage-baseline.md +13 -1
- package/docs/storage-baseline.zh-CN.md +5 -1
- package/docs/task-delivery.md +7 -0
- package/docs/task-delivery.zh-CN.md +5 -0
- package/docs/testing/review-evaluation.md +7 -7
- package/docs/testing/review-evaluation.zh-CN.md +7 -7
- package/docs/testing/verification-levels.md +45 -2
- package/docs/testing/verification-levels.zh-CN.md +21 -2
- package/i18n/README.zh-CN.md +6 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +98 -193
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/artifacts.md +41 -0
- package/skills/yui-leader/references/assignment-execution.md +140 -0
- package/skills/yui-leader/references/completion.md +79 -0
- package/skills/yui-leader/references/delivery-scope.md +26 -0
- package/skills/yui-leader/references/execution.md +16 -487
- package/skills/yui-leader/references/implementation.md +81 -0
- package/skills/yui-leader/references/planning.md +7 -45
- package/skills/yui-leader/references/review.md +90 -0
- package/skills/yui-leader/references/task-progress.md +23 -0
- package/skills/yui-leader/references/work-allocation.md +118 -0
- package/skills/yui-operator/SKILL.md +77 -333
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-operator/references/body-transport.md +62 -0
- package/skills/yui-operator/references/configuration.md +47 -0
- package/skills/yui-operator/references/lifecycle.md +41 -0
- package/skills/yui-operator/references/progress.md +39 -0
- package/skills/yui-operator/references/routing.md +57 -0
- package/skills/yui-operator/references/submission.md +56 -0
- package/skills/yui-operator/references/task-delivery.md +1 -1
- package/skills/yui-operator/references/web-lifecycle.md +19 -0
- package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
- package/skills/yui-runtime/SKILL.md +182 -531
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/archive.md +51 -0
- package/skills/yui-runtime/references/command-discovery.md +41 -0
- package/skills/yui-runtime/references/completed-followup.md +65 -0
- package/skills/yui-runtime/references/context-ordering.md +3 -34
- package/skills/yui-runtime/references/context-reads.md +61 -0
- package/skills/yui-runtime/references/dispatched-run.md +61 -0
- package/skills/yui-runtime/references/execution-failure.md +105 -0
- package/skills/yui-runtime/references/global-context.md +18 -0
- package/skills/yui-runtime/references/global-input-controls.md +44 -0
- package/skills/yui-runtime/references/host-custody.md +30 -0
- package/skills/yui-runtime/references/inbox-recovery.md +77 -0
- package/skills/yui-runtime/references/incremental-context.md +40 -0
- package/skills/yui-runtime/references/publication.md +4 -111
- package/skills/yui-runtime/references/recovery.md +32 -192
- package/skills/yui-runtime/references/session-identity.md +18 -0
- package/skills/yui-runtime/references/session-replacement.md +45 -0
- package/skills/yui-runtime/references/uncertain-execution.md +29 -0
|
@@ -43,7 +43,7 @@ import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
|
43
43
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
44
44
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
45
45
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
46
|
-
import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
46
|
+
import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, providerTurnIsActive, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
47
47
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
48
48
|
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeUsageEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
49
49
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
@@ -106,11 +106,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
106
106
|
return errors;
|
|
107
107
|
}
|
|
108
108
|
observeAgentHostObservation(event, now = new Date()) {
|
|
109
|
+
let preparation;
|
|
110
|
+
if (isRunTerminalObservation(event.observation)) {
|
|
111
|
+
preparation = {};
|
|
112
|
+
try {
|
|
113
|
+
preparation = this.prepareRuntimeTerminalObservation(resolveAgentHostObservation(this.store, event), now);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (!(error instanceof AgentHostObservationDeferred || error instanceof RuntimeHookRunFenceError))
|
|
117
|
+
throw error;
|
|
118
|
+
// Re-resolve under the committing fence below; preparation grants no
|
|
119
|
+
// authority and must not settle a deferred/obsolete Host envelope.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
109
122
|
// Match canonical ingress: archive and Host fact settlement share the
|
|
110
123
|
// same write boundary, including the original transport envelope.
|
|
111
|
-
return this.store.transaction(() => this.foldAgentHostObservation(event, now));
|
|
124
|
+
return this.store.transaction(() => this.foldAgentHostObservation(event, now, preparation));
|
|
112
125
|
}
|
|
113
|
-
foldAgentHostObservation(event, now) {
|
|
126
|
+
foldAgentHostObservation(event, now, preparation) {
|
|
114
127
|
const raw = createRuntimeObservation(event.observation);
|
|
115
128
|
const task = raw.fence.taskId === undefined ? null : this.store.getTask(raw.fence.taskId);
|
|
116
129
|
if (raw.fence.taskId !== undefined && task === null)
|
|
@@ -154,7 +167,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
154
167
|
// Rejection is durable diagnostic evidence, never business acceptance.
|
|
155
168
|
return this.recordObsoleteCanonicalObservation(event.observation, error.message, now);
|
|
156
169
|
}
|
|
157
|
-
return this.
|
|
170
|
+
return this.foldRuntimeObservation(input, now, preparation);
|
|
158
171
|
}
|
|
159
172
|
/**
|
|
160
173
|
* Sole provider-independent ingress for structured runtime state. Driver
|
|
@@ -163,12 +176,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
163
176
|
* transitions, and retains the canonical observation for status projection.
|
|
164
177
|
*/
|
|
165
178
|
observeRuntimeObservation(raw, now = new Date()) {
|
|
179
|
+
const preparation = this.prepareRuntimeTerminalObservation(raw, now);
|
|
166
180
|
// Archive and observation settlement share the same write boundary.
|
|
167
181
|
// An ingress read outside this transaction must never authorize clearing
|
|
168
182
|
// a claim after another CLI has committed archive.
|
|
169
|
-
return this.store.transaction(() => this.foldRuntimeObservation(raw, now));
|
|
183
|
+
return this.store.transaction(() => this.foldRuntimeObservation(raw, now, preparation));
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
prepareRuntimeTerminalObservation(raw, now) {
|
|
186
|
+
const input = createRuntimeObservation(raw);
|
|
187
|
+
if (!isRunTerminalObservation(input))
|
|
188
|
+
return undefined;
|
|
189
|
+
if (input.fence.taskId === undefined || Date.parse(input.receivedAt) > now.getTime())
|
|
190
|
+
return {};
|
|
191
|
+
const adapterId = this.adapterForRuntimeObservation(input);
|
|
192
|
+
if (adapterId === null)
|
|
193
|
+
return {};
|
|
194
|
+
return this.prepareRuntimeTerminal({
|
|
195
|
+
taskId: input.fence.taskId, roleName: input.fence.roleName,
|
|
196
|
+
agentId: input.fence.agentId, adapterId, conversationId: input.fence.conversationId,
|
|
197
|
+
nativeSessionId: input.fence.nativeSessionId, nativeTurnId: input.fence.nativeTurnId,
|
|
198
|
+
attemptId: input.fence.receiptId, runId: input.fence.runId
|
|
199
|
+
}, input.kind === "turn.completed" && input.payload.resultTransportDiagnostic === undefined
|
|
200
|
+
&& transportAgentResult(input.payload.output).status === "completed");
|
|
201
|
+
}
|
|
202
|
+
foldRuntimeObservation(raw, now, preparation) {
|
|
172
203
|
const input = createRuntimeObservation(raw);
|
|
173
204
|
const taskId = input.fence.taskId;
|
|
174
205
|
if (taskId === undefined)
|
|
@@ -263,13 +294,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
263
294
|
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
264
295
|
break;
|
|
265
296
|
case "turn.completed":
|
|
266
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now);
|
|
297
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now, preparation);
|
|
267
298
|
break;
|
|
268
299
|
case "turn.failed": {
|
|
269
300
|
// A Provider Turn is an activation boundary, not a Task outcome.
|
|
270
301
|
// Keep the AgentRun and Session identity available for Agent-directed
|
|
271
302
|
// restore + submit, even when a provider marks its AgentRun terminal.
|
|
272
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now);
|
|
303
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now, preparation);
|
|
273
304
|
break;
|
|
274
305
|
}
|
|
275
306
|
case "operation.started":
|
|
@@ -286,7 +317,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
286
317
|
outcome = this.validateCanonicalRunObservation(input, now);
|
|
287
318
|
break;
|
|
288
319
|
case "turn.cancelled": {
|
|
289
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now);
|
|
320
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now, preparation);
|
|
290
321
|
break;
|
|
291
322
|
}
|
|
292
323
|
case "conversation.observed":
|
|
@@ -617,7 +648,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
617
648
|
return "applied";
|
|
618
649
|
});
|
|
619
650
|
}
|
|
620
|
-
foldProviderTurnBoundary(input, adapterId, providerStatus, now) {
|
|
651
|
+
foldProviderTurnBoundary(input, adapterId, providerStatus, now, preparation) {
|
|
621
652
|
const outcome = providerStatus === "completed"
|
|
622
653
|
? input.payload.resultTransportDiagnostic === undefined
|
|
623
654
|
? transportAgentResult(input.payload.output)
|
|
@@ -656,7 +687,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
656
687
|
const classification = this.classifyRuntimeRunTerminal(completed);
|
|
657
688
|
if (classification !== "apply")
|
|
658
689
|
return classification;
|
|
659
|
-
const result = this.
|
|
690
|
+
const result = this.foldRuntimeRunTerminal(completed, now, preparation ?? {});
|
|
660
691
|
return result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
661
692
|
}
|
|
662
693
|
validateCanonicalSessionObservation(input, now) {
|
|
@@ -1692,6 +1723,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1692
1723
|
const disposition = runtimeCleanupDisposition(mailbox);
|
|
1693
1724
|
if (mailbox === null || disposition === null)
|
|
1694
1725
|
return false;
|
|
1726
|
+
if (disposition === "upgrade-host")
|
|
1727
|
+
return false;
|
|
1695
1728
|
if (mailbox.processing !== null) {
|
|
1696
1729
|
if (disposition !== "replace-session" && !mailbox.processing.batch.reasons.every(isRuntimeCleanupReason)) {
|
|
1697
1730
|
return false;
|
|
@@ -1774,8 +1807,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1774
1807
|
listDormantRuntimeOwners() {
|
|
1775
1808
|
return this.listRuntimeSessionCandidates().flatMap((candidate) => {
|
|
1776
1809
|
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(candidate.owner)))
|
|
1777
|
-
|| (candidate.owner
|
|
1778
|
-
&& this.store.getActiveRun(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
|
|
1810
|
+
|| runtimeOwnerHasActiveWork(this.store, candidate.owner)) {
|
|
1779
1811
|
return [];
|
|
1780
1812
|
}
|
|
1781
1813
|
return [{
|
|
@@ -2282,17 +2314,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
2282
2314
|
classifyRuntimeRunTerminal(input) {
|
|
2283
2315
|
return resolveTerminalExecution(this.store, input) === null ? "obsolete" : "apply";
|
|
2284
2316
|
}
|
|
2317
|
+
prepareRuntimeTerminal(input, completed) {
|
|
2318
|
+
const run = resolveTerminalExecution(this.store, input)?.run;
|
|
2319
|
+
const workspace = run?.status === "active"
|
|
2320
|
+
&& run.executionGroupId !== undefined
|
|
2321
|
+
&& run.executionLaneId !== undefined
|
|
2322
|
+
&& run.workspace !== undefined
|
|
2323
|
+
&& completed
|
|
2324
|
+
? this.#observeRead("git-workspace-snapshot", run.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, run.workspace))
|
|
2325
|
+
: undefined;
|
|
2326
|
+
return { run, workspace };
|
|
2327
|
+
}
|
|
2285
2328
|
observeRuntimeRunTerminal(input, now = new Date()) {
|
|
2329
|
+
return this.foldRuntimeRunTerminal(input, now, this.prepareRuntimeTerminal(input, input.outcome.status === "completed"));
|
|
2330
|
+
}
|
|
2331
|
+
foldRuntimeRunTerminal(input, now, preparation) {
|
|
2286
2332
|
// Resource inspection is bounded but external to SQLite. Revalidate the
|
|
2287
2333
|
// immutable AgentRun workspace after acquiring the write transaction.
|
|
2288
|
-
const preparedRun =
|
|
2289
|
-
const workspace = preparedRun?.status === "active"
|
|
2290
|
-
&& preparedRun.executionGroupId !== undefined
|
|
2291
|
-
&& preparedRun.executionLaneId !== undefined
|
|
2292
|
-
&& preparedRun.workspace !== undefined
|
|
2293
|
-
&& input.outcome.status === "completed"
|
|
2294
|
-
? this.#observeRead("git-workspace-snapshot", preparedRun.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace))
|
|
2295
|
-
: undefined;
|
|
2334
|
+
const { run: preparedRun, workspace } = preparation;
|
|
2296
2335
|
return this.store.transaction((store) => {
|
|
2297
2336
|
const task = store.getTask(input.taskId);
|
|
2298
2337
|
if (task === null)
|
|
@@ -2304,7 +2343,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
2304
2343
|
if (resolved === null)
|
|
2305
2344
|
throw new Error("Runtime terminal has no exact execution binding.");
|
|
2306
2345
|
const { run: observedRun, current: recordedProviderTurn, attemptId } = resolved;
|
|
2307
|
-
if (
|
|
2346
|
+
if (preparedRun?.id !== observedRun?.id
|
|
2347
|
+
|| !isDeepStrictEqual(preparedRun?.workspace, observedRun?.workspace)) {
|
|
2308
2348
|
throw new Error("Runtime terminal workspace changed during result preparation.");
|
|
2309
2349
|
}
|
|
2310
2350
|
if (workspace !== undefined && observedRun?.workspace !== undefined
|
|
@@ -3132,8 +3172,7 @@ function sameRuntimeOwner(left, right) {
|
|
|
3132
3172
|
}
|
|
3133
3173
|
function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = false) {
|
|
3134
3174
|
const { owner } = candidate;
|
|
3135
|
-
if (!allowActiveRun && owner
|
|
3136
|
-
&& store.getActiveRun(owner.taskId, owner.roleName) !== null) {
|
|
3175
|
+
if (!allowActiveRun && runtimeOwnerHasActiveWork(store, owner)) {
|
|
3137
3176
|
return false;
|
|
3138
3177
|
}
|
|
3139
3178
|
const sessions = runtimeOwnerSessionSet(store, owner);
|
|
@@ -3143,6 +3182,17 @@ function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = fal
|
|
|
3143
3182
|
return active !== undefined && active.agentId === candidate.agentId && active.adapterId === candidate.adapterId
|
|
3144
3183
|
&& active.nativeSessionId === candidate.nativeSessionId && (allowActiveRun || active.updatedAt === candidate.sessionUpdatedAt);
|
|
3145
3184
|
}
|
|
3185
|
+
function runtimeOwnerHasActiveWork(store, owner) {
|
|
3186
|
+
if (owner.scope === "task")
|
|
3187
|
+
return store.getActiveRun(owner.taskId, owner.roleName) !== null;
|
|
3188
|
+
// Global Turns do not have Task AgentRun records. Use their existing native
|
|
3189
|
+
// delivery binding, including submitting and uncertain acceptance.
|
|
3190
|
+
const sessions = store.getGlobalRoleSessionSet(owner.roleName);
|
|
3191
|
+
const binding = sessions?.providerBinding;
|
|
3192
|
+
return binding != null && (providerTurnIsActive(binding.run)
|
|
3193
|
+
|| currentProviderConversation(binding).conversationId
|
|
3194
|
+
!== sessions?.sessions[sessions.activeAgentId]?.nativeSessionId);
|
|
3195
|
+
}
|
|
3146
3196
|
function endRuntimeOwnerSession(store, owner, now) {
|
|
3147
3197
|
if (owner.scope === "task") {
|
|
3148
3198
|
const sessions = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createRuntimeBinding } from "../runtime/runtimeBinding.js";
|
|
2
|
+
import { normalizeRuntimeOwner } from "../runtime/runtimeOwner.js";
|
|
3
|
+
import { RuntimeHostContentionError } from "../runtime/ports.js";
|
|
4
|
+
const states = ["unavailable", "stopped", "starting", "running", "stopping"];
|
|
5
|
+
export const CONTROLLER_HOST_TRANSITIONS = {
|
|
6
|
+
launch: { from: ["unavailable", "stopped", "running"], to: "starting" },
|
|
7
|
+
launched: { from: ["starting"], to: "running" },
|
|
8
|
+
stop: { from: states, to: "stopping" },
|
|
9
|
+
"exit-sent": { from: ["stopping"], to: "stopping" },
|
|
10
|
+
stopped: { from: ["stopping"], to: "stopped" },
|
|
11
|
+
failed: { from: ["starting", "stopping"], to: "unavailable" },
|
|
12
|
+
observed: { from: states, to: (current, observed) => current === "stopping" && (observed.state === "running" || observed.state === "starting")
|
|
13
|
+
? "stopping" : observed.state }
|
|
14
|
+
};
|
|
15
|
+
/** All physical effects still require their existing durable caller authority.
|
|
16
|
+
* This process-local FSM gates competing effects at the shared Host port.
|
|
17
|
+
* Recovery observes current custody; it never invents a launch or replays one.
|
|
18
|
+
* Observation revisions reject late reads, not external process identities:
|
|
19
|
+
* exact OS/native identities remain owned by SessionOwner/Host fences. */
|
|
20
|
+
export function createControllerHostLifecycle(raw, hooks = {}) {
|
|
21
|
+
const entries = new Map();
|
|
22
|
+
let nextOperation = 0;
|
|
23
|
+
const entryFor = (input) => {
|
|
24
|
+
const owner = normalizeRuntimeOwner(input);
|
|
25
|
+
const key = JSON.stringify(owner);
|
|
26
|
+
let entry = entries.get(key);
|
|
27
|
+
if (!entry) {
|
|
28
|
+
entry = { owner: Object.freeze(owner), state: "unavailable", revision: 0 };
|
|
29
|
+
entries.set(key, entry);
|
|
30
|
+
}
|
|
31
|
+
return entry;
|
|
32
|
+
};
|
|
33
|
+
const send = (entry, event, observed, binding) => {
|
|
34
|
+
const rule = CONTROLLER_HOST_TRANSITIONS[event];
|
|
35
|
+
if (!rule.from.includes(entry.state))
|
|
36
|
+
throw new RuntimeHostContentionError("previous-process", `Host lifecycle cannot ${event} from ${entry.state}.`);
|
|
37
|
+
const to = typeof rule.to === "function" ? rule.to(entry.state, observed) : rule.to;
|
|
38
|
+
const nativeSessionId = to === "stopped" ? undefined
|
|
39
|
+
: binding?.nativeSessionId ?? observed?.nativeSessionId ?? entry.nativeSessionId;
|
|
40
|
+
const bindingId = to === "stopped" ? undefined
|
|
41
|
+
: observed?.nativeSessionId && observed.nativeSessionId !== entry.nativeSessionId
|
|
42
|
+
? undefined : binding?.id ?? entry.bindingId;
|
|
43
|
+
if (event === "observed" && to === entry.state && nativeSessionId === entry.nativeSessionId)
|
|
44
|
+
return;
|
|
45
|
+
const transition = Object.freeze({ owner: entry.owner, from: entry.state, to, event,
|
|
46
|
+
revision: entry.revision + 1, operationId: entry.operationId, nativeSessionId, bindingId });
|
|
47
|
+
hooks.before?.notify(transition);
|
|
48
|
+
entry.state = to;
|
|
49
|
+
entry.revision = transition.revision;
|
|
50
|
+
entry.nativeSessionId = nativeSessionId;
|
|
51
|
+
entry.bindingId = bindingId;
|
|
52
|
+
hooks.after?.notify(transition);
|
|
53
|
+
};
|
|
54
|
+
const observe = (entry, revision, inspection) => {
|
|
55
|
+
if (entry.revision === revision && entry.operationId === undefined)
|
|
56
|
+
send(entry, "observed", inspection);
|
|
57
|
+
return inspection;
|
|
58
|
+
};
|
|
59
|
+
const effect = async (owner, event, run) => {
|
|
60
|
+
const entry = entryFor(owner);
|
|
61
|
+
if (entry.operationId !== undefined)
|
|
62
|
+
throw new RuntimeHostContentionError("previous-process", `Host lifecycle already has an in-flight operation for ${owner.roleName}.`);
|
|
63
|
+
// Check state before reserving the operation; a rejected edge owns nothing.
|
|
64
|
+
if (!CONTROLLER_HOST_TRANSITIONS[event].from.includes(entry.state))
|
|
65
|
+
throw new RuntimeHostContentionError("previous-process", `Host lifecycle cannot ${event} from ${entry.state}.`);
|
|
66
|
+
entry.operationId = ++nextOperation;
|
|
67
|
+
send(entry, event);
|
|
68
|
+
try {
|
|
69
|
+
return await run(entry);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
send(entry, "failed");
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
entry.operationId = undefined;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const inspectOwner = async (owner) => {
|
|
80
|
+
const entry = entryFor(owner), revision = entry.revision;
|
|
81
|
+
return observe(entry, revision, await raw.inspectOwner(owner));
|
|
82
|
+
};
|
|
83
|
+
const launch = async (request, before) => {
|
|
84
|
+
await inspectOwner(request.owner);
|
|
85
|
+
return effect(request.owner, "launch", async (entry) => {
|
|
86
|
+
const binding = createRuntimeBinding(request.mode === "new"
|
|
87
|
+
? await raw.start(request, before) : await raw.restore(request, before));
|
|
88
|
+
if (JSON.stringify(normalizeRuntimeOwner(binding.owner)) !== JSON.stringify(entry.owner)
|
|
89
|
+
|| binding.agentId !== request.agentId || binding.adapterId !== request.adapterId
|
|
90
|
+
|| request.mode === "resume" && binding.nativeSessionId !== request.nativeSessionId) {
|
|
91
|
+
throw new Error("Host lifecycle launch handshake has mismatched owner/native identity.");
|
|
92
|
+
}
|
|
93
|
+
send(entry, "launched", undefined, binding);
|
|
94
|
+
return binding;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
const port = {
|
|
98
|
+
start: launch, restore: launch, inspectOwner,
|
|
99
|
+
inspect: async (binding) => {
|
|
100
|
+
const entry = entryFor(binding.owner), revision = entry.revision;
|
|
101
|
+
const inspection = await raw.inspect(binding);
|
|
102
|
+
if (entry.bindingId && entry.bindingId !== binding.id
|
|
103
|
+
|| entry.nativeSessionId && binding.nativeSessionId !== entry.nativeSessionId)
|
|
104
|
+
return inspection;
|
|
105
|
+
return observe(entry, revision, inspection);
|
|
106
|
+
},
|
|
107
|
+
...(raw.inspectOwners === undefined ? {} : {
|
|
108
|
+
inspectOwners: async (owners) => {
|
|
109
|
+
const revisions = new Map(owners.map(owner => {
|
|
110
|
+
const entry = entryFor(owner);
|
|
111
|
+
return [entry, entry.revision];
|
|
112
|
+
}));
|
|
113
|
+
const results = await raw.inspectOwners(owners);
|
|
114
|
+
for (const { owner, inspection } of results) {
|
|
115
|
+
const entry = entryFor(owner), revision = revisions.get(entry);
|
|
116
|
+
if (revision !== undefined)
|
|
117
|
+
observe(entry, revision, inspection);
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
}),
|
|
122
|
+
stop: async (binding) => {
|
|
123
|
+
const current = entryFor(binding.owner);
|
|
124
|
+
if (current.nativeSessionId && binding.nativeSessionId !== current.nativeSessionId
|
|
125
|
+
|| current.bindingId && binding.id !== current.bindingId) {
|
|
126
|
+
throw new Error("Host lifecycle stop carries a stale binding identity.");
|
|
127
|
+
}
|
|
128
|
+
return effect(binding.owner, "stop", async (entry) => {
|
|
129
|
+
await raw.stop(binding);
|
|
130
|
+
const observed = await raw.inspect(binding);
|
|
131
|
+
if (observed.state !== "stopped")
|
|
132
|
+
throw new Error("Host lifecycle stop has no physical exit proof.");
|
|
133
|
+
send(entry, "stopped");
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
stopOwner: owner => effect(owner, "stop", async (entry) => {
|
|
137
|
+
const stopped = await (hooks.stopOwner ? hooks.stopOwner(owner) : raw.stopOwner(owner));
|
|
138
|
+
send(entry, stopped ? "stopped" : "failed");
|
|
139
|
+
return stopped;
|
|
140
|
+
})
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
port,
|
|
144
|
+
snapshot: (owner) => Object.freeze({ ...entryFor(owner) }),
|
|
145
|
+
// Graceful exit acknowledgement is not exit proof. A later physical
|
|
146
|
+
// observation moves stopping -> stopped; no timer or automatic replay.
|
|
147
|
+
requestExit: (owner, run) => effect(owner, "stop", async (entry) => {
|
|
148
|
+
const result = await run();
|
|
149
|
+
send(entry, "exit-sent");
|
|
150
|
+
return result;
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createNonBlockingChecks } from "../runtime/lifecycleChecks.js";
|
|
2
|
+
/** Checks produce a recommendation; only the separately fenced coordinator
|
|
3
|
+
* can admit/execute it. Hints contain no target identity or restart authority. */
|
|
4
|
+
export function createHostLifecycleChecks(coordinator, onError = () => { }) {
|
|
5
|
+
const checks = createNonBlockingChecks([{
|
|
6
|
+
id: "host-version",
|
|
7
|
+
run: async (_hint, signal) => {
|
|
8
|
+
const observations = await coordinator.status();
|
|
9
|
+
if (signal.aborted)
|
|
10
|
+
return;
|
|
11
|
+
const recommended = coordinator.hasPending() || observations.some(entry => entry.status === "waiting" || entry.status === "unknown" || entry.status === "failed");
|
|
12
|
+
if (!recommended)
|
|
13
|
+
return;
|
|
14
|
+
// This await belongs exclusively to this optional check lane. Neither
|
|
15
|
+
// Host transitions nor Controller event folding wait for this operation.
|
|
16
|
+
const results = await coordinator.request();
|
|
17
|
+
for (const entry of results) {
|
|
18
|
+
if (entry.status === "failed" || entry.status === "unknown") {
|
|
19
|
+
onError(new Error(`Host upgrade ${entry.owner.roleName}/${entry.nativeSessionId}: ${entry.detail}`));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}], onError);
|
|
24
|
+
const close = checks.close;
|
|
25
|
+
checks.close = () => { close(); coordinator.stop(); };
|
|
26
|
+
return checks;
|
|
27
|
+
}
|