@zq-silk/yui 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +35 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +610 -201
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +24 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +85 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +60 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +413 -61
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/resourceInventoryRpc.js +85 -0
- package/dist/controller/resourceInventoryWorker.js +50 -0
- package/dist/controller/runtime.js +238 -22
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +549 -42
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/boundedRpc.js +475 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/setup/setupCommand.js +27 -4
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +769 -1
- package/dist/storage/persistenceWorker.js +194 -0
- package/dist/storage/sqliteSchema.js +705 -0
- package/dist/storage/sqliteStore.js +1695 -0
- package/dist/storage/storageVersions.js +9 -2
- package/dist/storage/storeRpc.js +298 -0
- package/dist/storage/taskStore.js +982 -21
- package/dist/storage/upgrade/homeClassification.js +157 -12
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +351 -0
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +713 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +510 -18
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +343 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +2 -1
|
@@ -7,27 +7,98 @@ import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, YUI_M
|
|
|
7
7
|
import { hasRuntimeCleanupObligation, runtimeLifecycleSignalKey, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
8
8
|
import { agentProcessReadinessProbe, ExecutorRegistry } from "../executor/executorRegistry.js";
|
|
9
9
|
import { activeLiveRoleAgentSession, roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
|
|
10
|
-
import { effectiveLaunchSnapshotsCompatible, resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
10
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
11
11
|
import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
|
|
12
12
|
import { FileRoleLaunchPlanner } from "../executor/fileRoleLaunchPlanner.js";
|
|
13
13
|
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
14
|
+
import { SqliteTaskStore } from "../storage/sqliteStore.js";
|
|
15
|
+
import { AsyncTaskStoreClient, resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
|
|
14
16
|
import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
|
|
15
17
|
import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
|
|
16
18
|
import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
|
|
17
19
|
import { FileTaskRuntimeIsolation, TmuxPromptPushAdapter, TmuxSessionHost } from "../runtime/index.js";
|
|
18
20
|
import { startFileTaskController } from "./controller.js";
|
|
19
21
|
import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
|
|
22
|
+
import { openSchedulerTelemetry } from "../telemetry/telemetryWiring.js";
|
|
23
|
+
import { createFileArtifactPort, createLinuxProcessPort, DurableJobSupervisor } from "./jobSupervisor.js";
|
|
24
|
+
import { createDurableJobControl } from "./jobControl.js";
|
|
20
25
|
import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
21
|
-
import { FileRuntimeEventProcessor } from "./runtimeEventProcessor.js";
|
|
26
|
+
import { AsyncRuntimeEventProcessor, FileRuntimeEventProcessor, createAsyncRuntimeObserver } from "./runtimeEventProcessor.js";
|
|
22
27
|
import { RuntimeLaunchCoordinator } from "./runtimeLaunchCoordinator.js";
|
|
23
28
|
import { ephemeralDomainFromEnvironment, recordEphemeralTmuxTarget } from "./domainIdentity.js";
|
|
24
29
|
import { createEphemeralResourceReaper } from "./ephemeralResourceReaper.js";
|
|
25
30
|
import { scanControllerResourceInventory } from "./resourceInventoryLinux.js";
|
|
31
|
+
import { ResourceInventoryClient } from "./resourceInventoryRpc.js";
|
|
32
|
+
import { createResourceAutoGc } from "../resources/autoResourceGc.js";
|
|
26
33
|
import { createRuntimeResourceActivityTracker } from "./resourceInventory.js";
|
|
34
|
+
import { SessionOwnerReconciliation } from "./sessionOwnerReconciliation.js";
|
|
35
|
+
/** Refreshes only the exact Task runtime generation folded by the event transaction. */
|
|
36
|
+
export function refreshAppliedTaskRuntimeDescriptor(store, planner, input) {
|
|
37
|
+
if (input.launchId === undefined)
|
|
38
|
+
return;
|
|
39
|
+
const run = input.runId === undefined
|
|
40
|
+
? null
|
|
41
|
+
: store.getAgentRun(input.taskId, input.runId);
|
|
42
|
+
if (input.runId !== undefined && run === null) {
|
|
43
|
+
throw new Error("Prepared Task runtime generation is not current.");
|
|
44
|
+
}
|
|
45
|
+
// A terminal completion has already settled this exact Run and no later
|
|
46
|
+
// prompt can use its descriptor. Acknowledge the applied provider fact
|
|
47
|
+
// without republishing a dead generation.
|
|
48
|
+
if (run !== null && run.status !== "active")
|
|
49
|
+
return;
|
|
50
|
+
const session = store.getTaskRoleSessionSet(input.taskId, input.roleName)
|
|
51
|
+
?.sessions[input.agentId];
|
|
52
|
+
const effective = run?.effective ?? session?.effective;
|
|
53
|
+
if (effective === undefined
|
|
54
|
+
|| session === undefined
|
|
55
|
+
|| session.agentId !== input.agentId
|
|
56
|
+
|| session.adapterId !== input.adapterId
|
|
57
|
+
|| session.launchId !== input.launchId
|
|
58
|
+
|| session.nativeSessionId !== input.nativeSessionId
|
|
59
|
+
|| (run !== null && (run.roleName !== input.roleName
|
|
60
|
+
|| run.effective.agentId !== input.agentId
|
|
61
|
+
|| run.effective.adapterId !== input.adapterId))) {
|
|
62
|
+
throw new Error("Prepared Task runtime generation is not current.");
|
|
63
|
+
}
|
|
64
|
+
planner.refreshTaskRuntimeDescriptor({
|
|
65
|
+
...input,
|
|
66
|
+
launchId: input.launchId,
|
|
67
|
+
workspace: effective.workspace.root
|
|
68
|
+
});
|
|
69
|
+
}
|
|
27
70
|
/** Production composition root for the lean FileTaskStore + tmux Controller. */
|
|
28
71
|
export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
29
|
-
|
|
30
|
-
|
|
72
|
+
// The Home decides the backend (Issue 01): a layout-7 Home runs SQLite with
|
|
73
|
+
// the persistence worker on by default; YUI_STORE_WORKER=0/false forces the
|
|
74
|
+
// in-process SQLite connection. The non-worker path opens the Home-decided
|
|
75
|
+
// backend through the compatibility opener (SQLite for layout 7, file store
|
|
76
|
+
// with normalization for older layouts).
|
|
77
|
+
const useWorker = resolveStoreWorkerEnabledForHome(home, options.environment ?? process.env);
|
|
78
|
+
const store = options.store
|
|
79
|
+
?? (useWorker
|
|
80
|
+
// Transitional: the scheduler/planner still use a sync store. The worker
|
|
81
|
+
// owns the event-processing hot path; the scheduler migration is the next
|
|
82
|
+
// step (see work-item-5 remaining call sites). Both connections point at
|
|
83
|
+
// the same WAL db and serialize via BEGIN IMMEDIATE + busy_timeout.
|
|
84
|
+
? new SqliteTaskStore(home)
|
|
85
|
+
: openCompatibleFileTaskStore(home));
|
|
86
|
+
// When the worker backend is active, the db-touching observer folds run in
|
|
87
|
+
// the worker (off the main event loop). The client is closed on shutdown.
|
|
88
|
+
const asyncStoreClient = useWorker
|
|
89
|
+
? new AsyncTaskStoreClient(home, {
|
|
90
|
+
environment: options.environment,
|
|
91
|
+
observerModule: new URL("./fileSchedulerStoreAdapter.js", import.meta.url)
|
|
92
|
+
})
|
|
93
|
+
: undefined;
|
|
94
|
+
// When the worker backend is active, the blocking /proc inventory scan runs
|
|
95
|
+
// in the inventory worker (off the main event loop); the scheduler and the
|
|
96
|
+
// ephemeral reaper consume the same inventory shape through this client (§3.3).
|
|
97
|
+
const inventoryClient = useWorker
|
|
98
|
+
? new ResourceInventoryClient()
|
|
99
|
+
: undefined;
|
|
100
|
+
const schedulerStore = options.schedulerStore
|
|
101
|
+
?? new FileSchedulerStoreAdapter(store, openSchedulerTelemetry(home, options.environment ?? process.env));
|
|
31
102
|
const domainIdentity = options.domainIdentity
|
|
32
103
|
?? ephemeralDomainFromEnvironment(options.environment ?? process.env);
|
|
33
104
|
const planner = options.planner ?? new FileRoleLaunchPlanner(home, store, {
|
|
@@ -45,7 +116,27 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
45
116
|
}
|
|
46
117
|
})
|
|
47
118
|
});
|
|
48
|
-
const
|
|
119
|
+
const sessionOwners = new SessionOwnerReconciliation({
|
|
120
|
+
home,
|
|
121
|
+
store,
|
|
122
|
+
environment: options.environment,
|
|
123
|
+
tmux,
|
|
124
|
+
onWarning: options.onError
|
|
125
|
+
});
|
|
126
|
+
const sessionHost = options.sessionHost ?? new TmuxSessionHost(planner, tmux, {
|
|
127
|
+
onHostCreated: ({ binding, pane }) => {
|
|
128
|
+
sessionOwners.recordHostOwner({
|
|
129
|
+
owner: binding.owner,
|
|
130
|
+
agentId: binding.agentId,
|
|
131
|
+
adapterId: binding.adapterId,
|
|
132
|
+
launchId: binding.launchId,
|
|
133
|
+
...(binding.nativeSessionId === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { nativeSessionId: binding.nativeSessionId }),
|
|
136
|
+
...(pane.pid === undefined ? {} : { panePid: pane.pid })
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
49
140
|
const promptPush = options.promptPush
|
|
50
141
|
?? new TmuxPromptPushAdapter(tmux, agentProcessReadinessProbe);
|
|
51
142
|
const runtimeIsolation = options.runtimeIsolation
|
|
@@ -67,7 +158,19 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
67
158
|
: {
|
|
68
159
|
inspectOwners: (owners) => sessionHost.inspectOwners(owners)
|
|
69
160
|
}),
|
|
70
|
-
stopOwner: (owner) =>
|
|
161
|
+
stopOwner: (owner) => {
|
|
162
|
+
// Issue 03: the durable `stopped` transition is gated on physical
|
|
163
|
+
// exit proof. A blocked result keeps the Session non-terminal and
|
|
164
|
+
// preserves owner records for Operator recovery.
|
|
165
|
+
return sessionOwners.terminateOwner(owner).then((result) => {
|
|
166
|
+
if (result.outcome === "stop-blocked") {
|
|
167
|
+
(options.onError ?? (() => undefined))(new Error(`Role runtime cleanup could not prove physical exit: ${result.remaining
|
|
168
|
+
.map(({ record, detail }) => `${record.launchId}: ${detail}`)
|
|
169
|
+
.join("; ")}`));
|
|
170
|
+
}
|
|
171
|
+
return result.outcome === "stop-confirmed";
|
|
172
|
+
});
|
|
173
|
+
},
|
|
71
174
|
...(runtimeIsolation.cleanupTaskLaunch === undefined
|
|
72
175
|
? {}
|
|
73
176
|
: {
|
|
@@ -95,19 +198,32 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
95
198
|
onCleanupRequired: signalRuntimeCleanup,
|
|
96
199
|
runtimeIsolation
|
|
97
200
|
});
|
|
201
|
+
// One inventory scan per scheduler pass. When the worker backend is active
|
|
202
|
+
// the blocking /proc scan runs in the inventory worker; otherwise it runs on
|
|
203
|
+
// the main thread (file backend, unchanged).
|
|
204
|
+
const scanInventory = (panes) => inventoryClient !== undefined
|
|
205
|
+
? inventoryClient.scan({
|
|
206
|
+
currentHome: home,
|
|
207
|
+
scope: "current",
|
|
208
|
+
panes,
|
|
209
|
+
...(options.environment === undefined
|
|
210
|
+
? {}
|
|
211
|
+
: { environment: options.environment })
|
|
212
|
+
})
|
|
213
|
+
: scanControllerResourceInventory({
|
|
214
|
+
currentHome: home,
|
|
215
|
+
scope: "current",
|
|
216
|
+
panes,
|
|
217
|
+
...(options.environment === undefined
|
|
218
|
+
? {}
|
|
219
|
+
: { environment: options.environment })
|
|
220
|
+
});
|
|
98
221
|
const delivery = options.delivery ?? new ExecutorRegistry(planner, tmux, agentProcessReadinessProbe, {
|
|
99
222
|
sessionHost,
|
|
100
223
|
promptPush,
|
|
101
224
|
launchCoordinator,
|
|
102
225
|
roleResourceInventory: async (panes, inputs) => {
|
|
103
|
-
const inventory = await
|
|
104
|
-
currentHome: home,
|
|
105
|
-
scope: "current",
|
|
106
|
-
panes,
|
|
107
|
-
...(options.environment === undefined
|
|
108
|
-
? {}
|
|
109
|
-
: { environment: options.environment })
|
|
110
|
-
});
|
|
226
|
+
const inventory = await scanInventory(panes);
|
|
111
227
|
return inventory.resources.flatMap((resource) => {
|
|
112
228
|
if (resource.kind !== "agent-session")
|
|
113
229
|
return [];
|
|
@@ -182,9 +298,76 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
182
298
|
// recovery bounded to that domain; cross-home cleanup remains an
|
|
183
299
|
// explicit `controller cleanup --all` inventory operation.
|
|
184
300
|
scope: "current",
|
|
185
|
-
environment: options.environment
|
|
301
|
+
environment: options.environment,
|
|
302
|
+
// When the worker backend is active, the reaper's scan runs in the
|
|
303
|
+
// inventory worker too (same cadence, same inventory shape).
|
|
304
|
+
...(inventoryClient === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: {
|
|
307
|
+
scan: () => inventoryClient.scan({
|
|
308
|
+
currentHome: home,
|
|
309
|
+
scope: "current",
|
|
310
|
+
...(options.environment === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { environment: options.environment })
|
|
313
|
+
})
|
|
314
|
+
})
|
|
186
315
|
}));
|
|
316
|
+
// Issue 10: automatic Resource GC. The runner self-skips unless
|
|
317
|
+
// resourcesGcMode=quarantine and resourcesGcAutoQuarantine=true, so wiring
|
|
318
|
+
// it unconditionally costs one config read per full pass when disabled.
|
|
319
|
+
const resourceAutoGc = options.resourceAutoGc
|
|
320
|
+
?? createResourceAutoGc({
|
|
321
|
+
home,
|
|
322
|
+
store,
|
|
323
|
+
environment: options.environment
|
|
324
|
+
});
|
|
187
325
|
const lifecycleDispatcher = createRuntimeLifecycleDispatcher(store, schedulerStore, sessionHost, options.dispatcher, signalRuntimeCleanup, launchCoordinator, planner);
|
|
326
|
+
// f7/rr5: Share one inbox between the supervisor's terminal channel and
|
|
327
|
+
// the runtime event processor. When a Job reaches a terminal state, the
|
|
328
|
+
// supervisor enqueues a durable-job-terminal event; the processor drains
|
|
329
|
+
// it on the next pass, waking the Controller immediately instead of
|
|
330
|
+
// waiting for the poll interval.
|
|
331
|
+
const runtimeEventInbox = new FileRuntimeEventInbox(home);
|
|
332
|
+
const jobSupervisor = new DurableJobSupervisor({
|
|
333
|
+
store: schedulerStore,
|
|
334
|
+
process: createLinuxProcessPort(),
|
|
335
|
+
artifacts: createFileArtifactPort(home),
|
|
336
|
+
// rr6/f1: Bounded supervision wake. The supervisor signals the Controller
|
|
337
|
+
// after spawning a runner (queued→running adoption) and when a runner
|
|
338
|
+
// exits (terminal harvest), so a quick job converges without waiting for
|
|
339
|
+
// the recovery interval. Closes over runningRuntime, which is assigned
|
|
340
|
+
// once startFileTaskController resolves; a wake during shutdown is a
|
|
341
|
+
// no-op. The recovery interval stays the cross-restart fallback.
|
|
342
|
+
wake: (taskId) => {
|
|
343
|
+
try {
|
|
344
|
+
runningRuntime?.signal(`task:${taskId}`);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Controller stopped; the recovery interval remains the fallback.
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
terminalEvents: {
|
|
351
|
+
deliverTerminalEvent(notice) {
|
|
352
|
+
try {
|
|
353
|
+
runtimeEventInbox.enqueueDurableJobTerminal({
|
|
354
|
+
scope: "task",
|
|
355
|
+
taskId: notice.taskId,
|
|
356
|
+
jobId: notice.jobId,
|
|
357
|
+
status: notice.status,
|
|
358
|
+
outcome: notice.outcome
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// Best-effort terminal channel: the terminal transition already
|
|
363
|
+
// committed. A delivery failure must not fail the reconcile pass.
|
|
364
|
+
(options.onError ?? (() => undefined))(error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
onError: options.onError
|
|
369
|
+
});
|
|
370
|
+
const jobControl = createDurableJobControl(store);
|
|
188
371
|
const running = await startFileTaskController(home, schedulerStore, delivery, lifecycleDispatcher, {
|
|
189
372
|
intervalMs: options.intervalMs
|
|
190
373
|
?? reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds),
|
|
@@ -194,7 +377,10 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
194
377
|
now: options.now,
|
|
195
378
|
onError: options.onError,
|
|
196
379
|
lifecycleHost,
|
|
380
|
+
jobSupervisor,
|
|
381
|
+
jobControl,
|
|
197
382
|
...(resourceReaper === undefined ? {} : { resourceReaper }),
|
|
383
|
+
resourceAutoGc,
|
|
198
384
|
onExpiredEphemeralDomain: (domain) => {
|
|
199
385
|
if (domain.yuiHome !== home)
|
|
200
386
|
return;
|
|
@@ -202,11 +388,17 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
202
388
|
},
|
|
203
389
|
workspacePreparer,
|
|
204
390
|
runtimeEventProcessor: options.runtimeEventProcessor
|
|
205
|
-
??
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
391
|
+
?? (useWorker && asyncStoreClient !== undefined
|
|
392
|
+
? new AsyncRuntimeEventProcessor(runtimeEventInbox, createAsyncRuntimeObserver((method, args) => asyncStoreClient.invokeObserver(method, args)), {
|
|
393
|
+
onTaskRuntimeApplied: (input) => {
|
|
394
|
+
refreshAppliedTaskRuntimeDescriptor(store, planner, input);
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
: new FileRuntimeEventProcessor(runtimeEventInbox, schedulerStore, {
|
|
398
|
+
onTaskRuntimeApplied: (input) => {
|
|
399
|
+
refreshAppliedTaskRuntimeDescriptor(store, planner, input);
|
|
400
|
+
}
|
|
401
|
+
})),
|
|
210
402
|
domainIdentity,
|
|
211
403
|
...(options.configuration !== undefined
|
|
212
404
|
? { configuration: options.configuration }
|
|
@@ -220,8 +412,29 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
220
412
|
});
|
|
221
413
|
runningController = running;
|
|
222
414
|
runningRuntime = running.runtime;
|
|
415
|
+
// Issue 03: read-only startup reconciliation. Surfaces durable/physical
|
|
416
|
+
// Session mismatches (including generations whose durable map was cleared)
|
|
417
|
+
// without changing stop or archive behavior. Cleanup stays an explicit
|
|
418
|
+
// Operator action in exact-owner-cleanup mode.
|
|
419
|
+
try {
|
|
420
|
+
const startupReport = sessionOwners.report();
|
|
421
|
+
if (startupReport.summary.livePhysicalRoots > 0) {
|
|
422
|
+
(options.onError ?? (() => undefined))(new Error(`Session reconciliation: ${startupReport.summary.livePhysicalRoots} `
|
|
423
|
+
+ `live physical root(s) across ${startupReport.summary.owners} owner record(s); `
|
|
424
|
+
+ "run `yui session reconcile --report` for details."));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
(options.onError ?? (() => undefined))(error);
|
|
429
|
+
}
|
|
223
430
|
return {
|
|
224
431
|
...running,
|
|
432
|
+
close: async () => {
|
|
433
|
+
await running.close();
|
|
434
|
+
// Release the worker's database connections when the worker backend is active.
|
|
435
|
+
await asyncStoreClient?.close();
|
|
436
|
+
await inventoryClient?.close();
|
|
437
|
+
},
|
|
225
438
|
store,
|
|
226
439
|
schedulerStore,
|
|
227
440
|
planner,
|
|
@@ -325,7 +538,7 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
|
|
|
325
538
|
throw applicationError("INVALID_PARAMS", `Configured Agent does not match Role: ${effective.agentId}.`);
|
|
326
539
|
}
|
|
327
540
|
validateLifecycleEnvironment(request.environment, agent);
|
|
328
|
-
const mode = roleAgentSessionResumeMode(sessions, effective.agentId, effective);
|
|
541
|
+
const mode = roleAgentSessionResumeMode(sessions, effective.agentId, effective, managedWorkspace);
|
|
329
542
|
const session = sessions?.sessions[effective.agentId];
|
|
330
543
|
const owner = request.scope === "task"
|
|
331
544
|
? { scope: "task", taskId: request.taskId, roleName: request.roleName }
|
|
@@ -447,7 +660,10 @@ function assertRuntimeLaunchRequestCurrent(store, request) {
|
|
|
447
660
|
|| session.nativeSessionId !== request.nativeSessionId) {
|
|
448
661
|
throw new Error(`Native session changed: ${request.owner.roleName}.`);
|
|
449
662
|
}
|
|
450
|
-
|
|
663
|
+
const sessionEffectiveCompatible = request.owner.scope === "task"
|
|
664
|
+
? effectiveLaunchSnapshotsCompatibleForTaskMain(session.effective, request.effective, request.managedWorkspace)
|
|
665
|
+
: effectiveLaunchSnapshotsCompatible(session.effective, request.effective);
|
|
666
|
+
if (!sessionEffectiveCompatible) {
|
|
451
667
|
throw new Error(`Native session effective launch changed: ${request.owner.roleName}.`);
|
|
452
668
|
}
|
|
453
669
|
}
|