@zq-silk/yui 0.2.0 → 0.4.2

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 (208) hide show
  1. package/ARCHITECTURE.md +603 -133
  2. package/README.md +806 -31
  3. package/dist/agent/agent.js +2 -1
  4. package/dist/agent/argumentPolicy.js +3 -1
  5. package/dist/agent/launchEnvironment.js +106 -0
  6. package/dist/agent/managedRuntimeEnvironment.js +34 -0
  7. package/dist/brief/taskBrief.js +11 -1
  8. package/dist/cli/agentConfigurationPicker.js +287 -0
  9. package/dist/cli/commandCatalog.js +488 -60
  10. package/dist/cli/completion.js +146 -22
  11. package/dist/cli/helpRenderer.js +3 -1
  12. package/dist/cli/interactionCandidates.js +53 -15
  13. package/dist/cli/interactionPolicy.js +267 -30
  14. package/dist/cli/interactiveSelection.js +6 -2
  15. package/dist/cli/invocationRouter.js +5 -1
  16. package/dist/cli/operatorWizard.js +87 -0
  17. package/dist/cli/roleOptionCatalog.js +1 -0
  18. package/dist/cli/roleWizard.js +185 -21
  19. package/dist/cli/updateCommand.js +62 -19
  20. package/dist/cli/updateOrchestrator.js +539 -0
  21. package/dist/cli/updatePorts.js +1119 -0
  22. package/dist/cli/upgradeCommand.js +112 -0
  23. package/dist/cli.js +1420 -86
  24. package/dist/commands/agentCommands.js +146 -3
  25. package/dist/commands/configCommands.js +126 -0
  26. package/dist/commands/controllerCommands.js +365 -0
  27. package/dist/commands/globalRoleCommands.js +168 -126
  28. package/dist/commands/jobCommands.js +18 -8
  29. package/dist/commands/operatorCommands.js +159 -9
  30. package/dist/commands/profileCommands.js +203 -0
  31. package/dist/commands/projectCommands.js +650 -0
  32. package/dist/commands/roleConfiguration.js +85 -24
  33. package/dist/commands/roleRuntimeGuard.js +12 -0
  34. package/dist/commands/roleSkillValidation.js +47 -0
  35. package/dist/commands/taskActor.js +127 -0
  36. package/dist/commands/taskCommands.js +4201 -313
  37. package/dist/commands/taskCompletionGate.js +131 -0
  38. package/dist/commands/taskContextCommand.js +244 -30
  39. package/dist/commands/taskInputCommands.js +177 -59
  40. package/dist/commands/taskIntegrationCommands.js +303 -0
  41. package/dist/commands/taskOverviewCommand.js +363 -0
  42. package/dist/commands/taskRoleRuntimeStatus.js +125 -19
  43. package/dist/commands/textInput.js +15 -0
  44. package/dist/completion/completionInstaller.js +26 -22
  45. package/dist/config/yuiConfig.js +4 -3
  46. package/dist/context/dispatchContext.js +90 -38
  47. package/dist/context/roleSessionContext.js +119 -0
  48. package/dist/controller/claudeLifecycleHook.js +203 -0
  49. package/dist/controller/clientRuntime.js +408 -56
  50. package/dist/controller/codexLifecycleHook.js +108 -0
  51. package/dist/controller/controller.js +1089 -32
  52. package/dist/controller/domainIdentity.js +505 -0
  53. package/dist/controller/ephemeralResourceReaper.js +131 -0
  54. package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
  55. package/dist/controller/providerHookRunFence.js +127 -0
  56. package/dist/controller/resourceCleanupLinux.js +286 -0
  57. package/dist/controller/resourceInventory.js +531 -0
  58. package/dist/controller/resourceInventoryLinux.js +610 -0
  59. package/dist/controller/runtime.js +629 -10
  60. package/dist/controller/runtimeEventInbox.js +564 -0
  61. package/dist/controller/runtimeEventProcessor.js +248 -0
  62. package/dist/controller/runtimeLaunchCoordinator.js +477 -0
  63. package/dist/controller/sessionNotify.js +121 -78
  64. package/dist/coordination/deadlineScheduler.js +15 -0
  65. package/dist/coordination/mailboxScheduler.js +108 -0
  66. package/dist/coordination/workMailbox.js +329 -0
  67. package/dist/coordination/workMailboxQueue.js +86 -0
  68. package/dist/core/controllerClient.js +19 -5
  69. package/dist/core/controllerEndpoint.js +37 -0
  70. package/dist/core/controllerServer.js +218 -10
  71. package/dist/core/protocol.js +6 -2
  72. package/dist/decision/decision.js +2 -1
  73. package/dist/doctor/doctor.js +681 -32
  74. package/dist/domain/validation.js +53 -0
  75. package/dist/errors/cliError.js +5 -3
  76. package/dist/event/taskEvent.js +7 -3
  77. package/dist/execution/codexThreadNaming.js +160 -0
  78. package/dist/execution/executionGroup.js +579 -0
  79. package/dist/executor/agentAdapter.js +255 -40
  80. package/dist/executor/agentConfigurationCatalog.js +326 -0
  81. package/dist/executor/agentConfigurationProbe.js +506 -0
  82. package/dist/executor/agentExecutor.js +625 -10
  83. package/dist/executor/codexConfigConflict.js +290 -0
  84. package/dist/executor/effectiveLaunch.js +340 -0
  85. package/dist/executor/executorRegistry.js +238 -36
  86. package/dist/executor/fileRoleLaunchPlanner.js +550 -40
  87. package/dist/executor/turnCompletion.js +126 -0
  88. package/dist/input/inputRequest.js +30 -9
  89. package/dist/integration/changeSet.js +36 -0
  90. package/dist/integration/checkResult.js +24 -0
  91. package/dist/integration/gitIntegrationService.js +695 -0
  92. package/dist/integration/integrationAttempt.js +142 -0
  93. package/dist/interaction/operatorPresentation.js +96 -0
  94. package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
  95. package/dist/lifecycle/exactRunTerminalization.js +572 -0
  96. package/dist/lifecycle/providerLifecycleMapping.js +190 -0
  97. package/dist/lifecycle/taskRoleSessionReset.js +124 -0
  98. package/dist/message/message.js +23 -7
  99. package/dist/milestone/milestone.js +2 -1
  100. package/dist/operator/operatorSessionHistory.js +124 -0
  101. package/dist/output/agentConfigurationPresentation.js +43 -0
  102. package/dist/output/rolePresentation.js +34 -10
  103. package/dist/output/terminal.js +8 -0
  104. package/dist/output/timePresentation.js +55 -0
  105. package/dist/profile/agentProfile.js +128 -0
  106. package/dist/repository/gitWorkspace.js +578 -24
  107. package/dist/repository/project.js +213 -0
  108. package/dist/repository/taskWorkspaceCoordinator.js +392 -0
  109. package/dist/repository/taskWorkspacePreparer.js +1688 -191
  110. package/dist/review/reviewConfig.js +11 -0
  111. package/dist/review/reviewRound.js +399 -0
  112. package/dist/review/taskFinalReviewContract.js +90 -0
  113. package/dist/role/role.js +124 -23
  114. package/dist/run/agentRun.js +155 -12
  115. package/dist/run/runIdentity.js +82 -0
  116. package/dist/runtime/exactControlPlane.js +472 -0
  117. package/dist/runtime/index.js +8 -0
  118. package/dist/runtime/lifecycleReservation.js +38 -0
  119. package/dist/runtime/ports.js +11 -0
  120. package/dist/runtime/preallocatedNativeSession.js +13 -0
  121. package/dist/runtime/promptEnvelope.js +30 -0
  122. package/dist/runtime/runtimeBinding.js +31 -0
  123. package/dist/runtime/runtimeOwner.js +14 -0
  124. package/dist/runtime/sessionLaunchRequest.js +62 -0
  125. package/dist/runtime/sessionTitle.js +54 -0
  126. package/dist/runtime/taskRuntimeIsolation.js +643 -0
  127. package/dist/runtime/tmuxAdapters.js +315 -0
  128. package/dist/runtime/turnCompletion.js +3 -0
  129. package/dist/runtime/validation.js +23 -0
  130. package/dist/scheduler/activeRoleRunDelivery.js +342 -32
  131. package/dist/scheduler/activeTaskProgress.js +63 -0
  132. package/dist/scheduler/leaderFailure.js +2 -1
  133. package/dist/scheduler/leaderWakeupProcessor.js +307 -66
  134. package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
  135. package/dist/scheduler/operatorNotification.js +44 -2
  136. package/dist/scheduler/ports.js +28 -1
  137. package/dist/scheduler/roleRunLiveness.js +131 -25
  138. package/dist/scheduler/roleRunStall.js +951 -0
  139. package/dist/scheduler/taskExecutionProjection.js +544 -0
  140. package/dist/scheduler/wakeupQueue.js +3 -0
  141. package/dist/setup/setupCommand.js +302 -52
  142. package/dist/storage/compatibleTaskStore.js +102 -0
  143. package/dist/storage/migration/baseline.js +78 -0
  144. package/dist/storage/migration/classifier.js +51 -0
  145. package/dist/storage/migration/compatibleCodec.js +53 -0
  146. package/dist/storage/migration/engine.js +147 -0
  147. package/dist/storage/migration/index.js +33 -0
  148. package/dist/storage/migration/planner.js +154 -0
  149. package/dist/storage/migration/productionRegistry.js +486 -0
  150. package/dist/storage/migration/registry.js +169 -0
  151. package/dist/storage/migration/report.js +54 -0
  152. package/dist/storage/migration/types.js +31 -0
  153. package/dist/storage/storageSchema.js +147 -123
  154. package/dist/storage/storageVersions.js +11 -0
  155. package/dist/storage/taskStore.js +1793 -197
  156. package/dist/storage/upgrade/homeClassification.js +156 -0
  157. package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
  158. package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
  159. package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
  160. package/dist/storage/upgrade/recordVersionScan.js +176 -0
  161. package/dist/storage/upgrade/recordVersions.js +159 -0
  162. package/dist/storage/upgrade/switchProgress.js +80 -0
  163. package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
  164. package/dist/storage/upgrade/upgradeReceipt.js +161 -0
  165. package/dist/storage/upgradeCoordination.js +186 -0
  166. package/dist/storage/upgradeFence.js +366 -0
  167. package/dist/task/task.js +132 -26
  168. package/dist/task/taskRecordReference.js +66 -0
  169. package/dist/tmux/commandExecutor.js +75 -2
  170. package/dist/tmux/tmuxManager.js +747 -49
  171. package/dist/version.js +23 -0
  172. package/dist/web/assets/assetManifest.js +62 -0
  173. package/dist/web/assets/client/app.js +631 -0
  174. package/dist/web/assets/client/components.js +605 -0
  175. package/dist/web/assets/client/dom.js +14 -0
  176. package/dist/web/assets/client/format.js +28 -0
  177. package/dist/web/assets/client/i18n.js +494 -0
  178. package/dist/web/assets/client/markdown.js +114 -0
  179. package/dist/web/assets/client/theme.js +32 -0
  180. package/dist/web/assets/client/view.js +458 -0
  181. package/dist/web/assets/fontData.js +12 -0
  182. package/dist/web/assets/fonts.js +12 -0
  183. package/dist/web/assets/shell.js +114 -0
  184. package/dist/web/assets/styles/cards.js +135 -0
  185. package/dist/web/assets/styles/layout.js +47 -0
  186. package/dist/web/assets/styles/markdown.js +29 -0
  187. package/dist/web/assets/styles/responsive.js +39 -0
  188. package/dist/web/assets/styles/tokens.js +101 -0
  189. package/dist/web/assets/styles/widgets.js +147 -0
  190. package/dist/web/tmuxWebTerminal.js +158 -0
  191. package/dist/web/webServer.js +463 -0
  192. package/dist/web/webSnapshot.js +148 -0
  193. package/dist/workItem/workItem.js +642 -23
  194. package/dist/workspace/gitChangeSetCapture.js +86 -0
  195. package/dist/workspace/workItemChangeSetManager.js +445 -0
  196. package/dist/worktree/managedWorkspace.js +202 -0
  197. package/docs/task-local-identity.md +62 -0
  198. package/i18n/README.zh-CN.md +406 -31
  199. package/package.json +10 -2
  200. package/skills/yui-leader/SKILL.md +601 -39
  201. package/skills/yui-operator/SKILL.md +255 -34
  202. package/skills/yui-reviewer/SKILL.md +57 -0
  203. package/skills/yui-worker/SKILL.md +214 -17
  204. package/dist/commands/repositoryCommands.js +0 -86
  205. package/dist/operator/operatorContext.js +0 -66
  206. package/dist/repository/repository.js +0 -55
  207. package/dist/scheduler/archivedTaskRuntime.js +0 -12
  208. package/dist/worktree/roleWorkspace.js +0 -62
@@ -1,31 +1,650 @@
1
1
  import { reconciliationIntervalMilliseconds } from "../config/yuiConfig.js";
2
- import { ExecutorRegistry } from "../executor/executorRegistry.js";
2
+ import { createHash } from "node:crypto";
3
+ import { resolve } from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { controllerSocketPath } from "../core/controllerEndpoint.js";
6
+ import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
7
+ import { hasRuntimeCleanupObligation, runtimeLifecycleSignalKey, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
8
+ import { agentProcessReadinessProbe, ExecutorRegistry } from "../executor/executorRegistry.js";
9
+ import { activeLiveRoleAgentSession, roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
10
+ import { effectiveLaunchSnapshotsCompatible, resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
11
+ import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
3
12
  import { FileRoleLaunchPlanner } from "../executor/fileRoleLaunchPlanner.js";
4
- import { FileTaskStore } from "../storage/taskStore.js";
13
+ import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
5
14
  import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
6
15
  import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
7
- import { TmuxManager } from "../tmux/tmuxManager.js";
16
+ import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
17
+ import { FileTaskRuntimeIsolation, TmuxPromptPushAdapter, TmuxSessionHost } from "../runtime/index.js";
8
18
  import { startFileTaskController } from "./controller.js";
9
19
  import { FileSchedulerStoreAdapter } from "./fileSchedulerStoreAdapter.js";
10
- import { createSessionNotifyDispatcher } from "./sessionNotify.js";
20
+ import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
21
+ import { FileRuntimeEventProcessor } from "./runtimeEventProcessor.js";
22
+ import { RuntimeLaunchCoordinator } from "./runtimeLaunchCoordinator.js";
23
+ import { ephemeralDomainFromEnvironment, recordEphemeralTmuxTarget } from "./domainIdentity.js";
24
+ import { createEphemeralResourceReaper } from "./ephemeralResourceReaper.js";
25
+ import { scanControllerResourceInventory } from "./resourceInventoryLinux.js";
26
+ import { createRuntimeResourceActivityTracker } from "./resourceInventory.js";
11
27
  /** Production composition root for the lean FileTaskStore + tmux Controller. */
12
28
  export async function startFileTaskControllerRuntime(home, options = {}) {
13
- const store = options.store ?? new FileTaskStore(home);
29
+ const store = options.store ?? openCompatibleFileTaskStore(home);
14
30
  const schedulerStore = options.schedulerStore ?? new FileSchedulerStoreAdapter(store);
31
+ const domainIdentity = options.domainIdentity
32
+ ?? ephemeralDomainFromEnvironment(options.environment ?? process.env);
15
33
  const planner = options.planner ?? new FileRoleLaunchPlanner(home, store, {
16
34
  environment: options.environment
17
35
  });
18
- const tmux = options.tmux ?? new TmuxManager(options.environment?.YUI_TMUX_BIN ?? process.env.YUI_TMUX_BIN ?? "tmux", new NodeCommandExecutor(), { yuiHome: home });
19
- const delivery = options.delivery ?? new ExecutorRegistry(planner, tmux);
36
+ const tmux = options.tmux ?? new TmuxManager(options.environment?.YUI_TMUX_BIN ?? process.env.YUI_TMUX_BIN ?? "tmux", new NodeCommandExecutor(), {
37
+ yuiHome: home,
38
+ ...(domainIdentity === undefined
39
+ ? {}
40
+ : {
41
+ onRoleTargetRecorded: (target) => {
42
+ if (!recordEphemeralTmuxTarget(home, domainIdentity.token, target)) {
43
+ throw new Error(`Ephemeral tmux target fence could not be recorded: ${target}.`);
44
+ }
45
+ }
46
+ })
47
+ });
48
+ const sessionHost = options.sessionHost ?? new TmuxSessionHost(planner, tmux);
49
+ const promptPush = options.promptPush
50
+ ?? new TmuxPromptPushAdapter(tmux, agentProcessReadinessProbe);
51
+ const runtimeIsolation = options.runtimeIsolation
52
+ ?? new FileTaskRuntimeIsolation({
53
+ // A sibling of the exact control Home keeps provider data/cache/tmp out
54
+ // of both the shared control plane and every managed Git workspace.
55
+ runtimeRoot: `${resolve(home)}.task-runtimes`,
56
+ controlPlane: {
57
+ yuiHome: home,
58
+ controllerSocketPath: controllerSocketPath(home),
59
+ tmuxNamespace: yuiTmuxServerName(home),
60
+ globalInstallPaths: [process.execPath]
61
+ }
62
+ });
63
+ const lifecycleHost = {
64
+ inspectOwner: (owner) => (sessionHost.inspectOwner(owner)),
65
+ ...(sessionHost.inspectOwners === undefined
66
+ ? {}
67
+ : {
68
+ inspectOwners: (owners) => sessionHost.inspectOwners(owners)
69
+ }),
70
+ stopOwner: (owner) => (sessionHost.stopOwner(owner)),
71
+ ...(runtimeIsolation.cleanupTaskLaunch === undefined
72
+ ? {}
73
+ : {
74
+ cleanupTaskLaunch: (input) => runtimeIsolation.cleanupTaskLaunch(input)
75
+ })
76
+ };
77
+ const resourceActivity = createRuntimeResourceActivityTracker();
78
+ let runningRuntime;
79
+ let runningController;
80
+ const signalRuntimeCleanup = (target) => {
81
+ runningRuntime?.signal(runtimeLifecycleSignalKey(target.kind === "role-runtime"
82
+ ? {
83
+ scope: "task",
84
+ taskId: target.taskId,
85
+ roleName: target.roleName
86
+ }
87
+ : { scope: "global", roleName: target.roleName }));
88
+ };
89
+ const launchCoordinator = new RuntimeLaunchCoordinator(schedulerStore, sessionHost, {
90
+ ...(options.now === undefined ? {} : { now: options.now }),
91
+ assertCurrent: (request) => {
92
+ assertRuntimeLaunchRequestCurrent(store, request);
93
+ },
94
+ launchFingerprint: (request) => (runtimeLaunchFingerprint(store, request)),
95
+ onCleanupRequired: signalRuntimeCleanup,
96
+ runtimeIsolation
97
+ });
98
+ const delivery = options.delivery ?? new ExecutorRegistry(planner, tmux, agentProcessReadinessProbe, {
99
+ sessionHost,
100
+ promptPush,
101
+ launchCoordinator,
102
+ roleResourceInventory: async (panes, inputs) => {
103
+ const inventory = await scanControllerResourceInventory({
104
+ currentHome: home,
105
+ scope: "current",
106
+ panes,
107
+ ...(options.environment === undefined
108
+ ? {}
109
+ : { environment: options.environment })
110
+ });
111
+ return inventory.resources.flatMap((resource) => {
112
+ if (resource.kind !== "agent-session")
113
+ return [];
114
+ const owner = resource.owner;
115
+ if (owner.kind !== "task-role")
116
+ return [];
117
+ const active = resource.state === "running" || resource.state === "current";
118
+ const input = inputs.find((candidate) => (candidate.taskId === owner.taskId
119
+ && candidate.roleName === owner.roleName));
120
+ if (input === undefined)
121
+ return [];
122
+ const identity = owner.runId === undefined
123
+ || owner.adapterId === undefined
124
+ || (owner.nativeSessionId === undefined && owner.launchId === undefined)
125
+ || (owner.nativeSessionId !== undefined && owner.nativeSessionId.trim().length === 0)
126
+ || (owner.launchId !== undefined && owner.launchId.trim().length === 0)
127
+ ? undefined
128
+ : {
129
+ taskId: owner.taskId,
130
+ roleName: owner.roleName,
131
+ runId: owner.runId,
132
+ agentId: owner.agentId,
133
+ adapterId: owner.adapterId,
134
+ ...(owner.nativeSessionId === undefined
135
+ ? {}
136
+ : { nativeSessionId: owner.nativeSessionId }),
137
+ ...(owner.launchId === undefined ? {} : { launchId: owner.launchId })
138
+ };
139
+ const changed = !active || input.runId === undefined
140
+ ? false
141
+ : resourceActivity({
142
+ taskId: input.taskId,
143
+ roleName: input.roleName,
144
+ runId: input.runId,
145
+ agentId: input.agentId,
146
+ adapterId: input.adapterId,
147
+ ...(input.nativeSessionId === undefined
148
+ ? {}
149
+ : { nativeSessionId: input.nativeSessionId }),
150
+ ...(input.launchId === undefined ? {} : { launchId: input.launchId })
151
+ }, resource);
152
+ return [{
153
+ taskId: owner.taskId,
154
+ roleName: owner.roleName,
155
+ resource: {
156
+ observedAt: inventory.observedAt,
157
+ active,
158
+ changed: active && changed,
159
+ ...(identity === undefined ? {} : { identity }),
160
+ ...(input.progressAt === undefined ? {} : { progressAt: input.progressAt }),
161
+ cpuTimeMs: resource.cpuTimeMs,
162
+ ...(resource.ioReadBytes === undefined
163
+ ? {}
164
+ : { ioReadBytes: resource.ioReadBytes }),
165
+ ...(resource.ioWriteBytes === undefined
166
+ ? {}
167
+ : { ioWriteBytes: resource.ioWriteBytes }),
168
+ rssBytes: resource.rssBytes
169
+ }
170
+ }];
171
+ });
172
+ }
173
+ });
20
174
  const workspacePreparer = options.workspacePreparer
21
175
  ?? new FileTaskWorkspacePreparer(home, store);
22
- const dispatcher = createSessionNotifyDispatcher(schedulerStore, options.dispatcher);
23
- const running = await startFileTaskController(home, schedulerStore, delivery, dispatcher, {
176
+ const resourceReaper = options.resourceReaper
177
+ ?? (domainIdentity === undefined
178
+ ? undefined
179
+ : createEphemeralResourceReaper({
180
+ currentHome: home,
181
+ // The detached Controller owns one YUI_HOME. Keep automatic
182
+ // recovery bounded to that domain; cross-home cleanup remains an
183
+ // explicit `controller cleanup --all` inventory operation.
184
+ scope: "current",
185
+ environment: options.environment
186
+ }));
187
+ const lifecycleDispatcher = createRuntimeLifecycleDispatcher(store, schedulerStore, sessionHost, options.dispatcher, signalRuntimeCleanup, launchCoordinator, planner);
188
+ const running = await startFileTaskController(home, schedulerStore, delivery, lifecycleDispatcher, {
24
189
  intervalMs: options.intervalMs
25
190
  ?? reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds),
191
+ signalWindowMs: options.signalWindowMs,
192
+ deliveryRetryMs: options.deliveryRetryMs,
193
+ deliveryRetryLimit: options.deliveryRetryLimit,
26
194
  now: options.now,
27
195
  onError: options.onError,
196
+ lifecycleHost,
197
+ ...(resourceReaper === undefined ? {} : { resourceReaper }),
198
+ onExpiredEphemeralDomain: (domain) => {
199
+ if (domain.yuiHome !== home)
200
+ return;
201
+ void runningController?.close().catch(options.onError ?? (() => undefined));
202
+ },
203
+ workspacePreparer,
204
+ runtimeEventProcessor: options.runtimeEventProcessor
205
+ ?? new FileRuntimeEventProcessor(new FileRuntimeEventInbox(home), schedulerStore, {
206
+ onTaskRuntimeApplied: (input) => {
207
+ planner.refreshTaskRuntimeDescriptor(input);
208
+ }
209
+ }),
210
+ domainIdentity,
211
+ ...(options.configuration !== undefined
212
+ ? { configuration: options.configuration }
213
+ : options.intervalMs === undefined
214
+ ? {
215
+ configuration: {
216
+ reconciliationIntervalMs: () => reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds)
217
+ }
218
+ }
219
+ : {})
220
+ });
221
+ runningController = running;
222
+ runningRuntime = running.runtime;
223
+ return {
224
+ ...running,
225
+ store,
226
+ schedulerStore,
227
+ planner,
228
+ tmux,
229
+ delivery,
230
+ sessionHost,
231
+ promptPush,
232
+ runtimeIsolation,
28
233
  workspacePreparer
234
+ };
235
+ }
236
+ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionHost, fallback, onCleanupRequired, sharedLaunchCoordinator, environmentRefresher) {
237
+ const launchCoordinator = sharedLaunchCoordinator
238
+ ?? new RuntimeLaunchCoordinator(schedulerStore, sessionHost, {
239
+ assertCurrent: (request) => {
240
+ assertRuntimeLaunchRequestCurrent(store, request);
241
+ },
242
+ launchFingerprint: (request) => (runtimeLaunchFingerprint(store, request)),
243
+ onCleanupRequired
244
+ });
245
+ const lifecycleTails = new Map();
246
+ return async (method, params) => {
247
+ if (method === "runtime.replace-agent-environment") {
248
+ if (environmentRefresher === undefined) {
249
+ throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
250
+ }
251
+ const refresh = parseEnvironmentRefresh(params);
252
+ validateEnvironmentRefreshSources(store, refresh);
253
+ environmentRefresher.refreshAgentEnvironment(refresh);
254
+ return {
255
+ replaced: true,
256
+ count: Object.keys(refresh.sources).length
257
+ + Object.keys(refresh.nativeSources).length
258
+ };
259
+ }
260
+ if (method !== "runtime.ensure-role-session") {
261
+ if (fallback !== undefined)
262
+ return fallback(method, params);
263
+ throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
264
+ }
265
+ const request = parseEnsureRoleSession(params);
266
+ const lifecycleKey = request.scope === "task"
267
+ ? `task\0${request.taskId}\0${request.roleName}`
268
+ : `global\0${request.roleName}`;
269
+ const previous = lifecycleTails.get(lifecycleKey) ?? Promise.resolve();
270
+ let release;
271
+ const current = new Promise((resolve) => { release = resolve; });
272
+ const tail = previous.then(() => current);
273
+ lifecycleTails.set(lifecycleKey, tail);
274
+ await previous;
275
+ try {
276
+ const task = request.scope === "task"
277
+ ? store.getTask(request.taskId)
278
+ : null;
279
+ if (request.scope === "task" && task?.status !== "active") {
280
+ throw applicationError("INVALID_PARAMS", task === null
281
+ ? `Task not found: ${request.taskId}.`
282
+ : `Task is not active: ${request.taskId}.`);
283
+ }
284
+ if (request.scope === "task" && task !== null) {
285
+ const taskWorkspace = store.getTaskWorkspace(task.id);
286
+ if (!isTaskOwnedWorkspace(taskWorkspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })))) {
287
+ throw new Error(`Task workspace is not ready: ${task.id}.`);
288
+ }
289
+ }
290
+ if (request.scope === "task"
291
+ && hasPendingRuntimeCleanup(store, request)) {
292
+ throw new Error(`Runtime cleanup is still pending: ${request.taskId}/${request.roleName}.`);
293
+ }
294
+ if (request.scope === "global"
295
+ && hasPendingRuntimeCleanup(store, request)) {
296
+ throw new Error(`Runtime cleanup is still pending: ${request.roleName}.`);
297
+ }
298
+ const role = request.scope === "task"
299
+ ? store.getRole(request.taskId, request.roleName)
300
+ : store.getGlobalRole(request.roleName);
301
+ if (role === null) {
302
+ throw applicationError("INVALID_PARAMS", request.scope === "task"
303
+ ? `Role not found: ${request.taskId}/${request.roleName}.`
304
+ : `Global Role not found: ${request.roleName}.`);
305
+ }
306
+ const activeRun = request.scope === "task"
307
+ ? store.getActiveAgentRun(request.taskId, request.roleName)
308
+ : null;
309
+ const managedWorkspace = request.scope === "task"
310
+ ? activeRun?.workspace
311
+ ?? currentDesiredManagedWorkspace(store, request.taskId, request.roleName)
312
+ : undefined;
313
+ const sessions = request.scope === "task"
314
+ ? store.getTaskRoleSessionSet(request.taskId, request.roleName)
315
+ : store.getGlobalRoleSessionSet(request.roleName);
316
+ const effective = activeRun?.effective
317
+ ?? activeLiveRoleAgentSession(sessions)?.effective
318
+ ?? resolveRuntimeDesiredEffective(store, request, role);
319
+ const binding = role.agentBindings[effective.agentId];
320
+ const agent = store.getConfiguredAgent(effective.agentId);
321
+ if (binding === undefined || binding.adapterId !== effective.adapterId) {
322
+ throw applicationError("INVALID_PARAMS", `Role binding does not match effective launch: ${effective.agentId}.`);
323
+ }
324
+ if (agent === null || agent.adapterId !== binding.adapterId) {
325
+ throw applicationError("INVALID_PARAMS", `Configured Agent does not match Role: ${effective.agentId}.`);
326
+ }
327
+ validateLifecycleEnvironment(request.environment, agent);
328
+ const mode = roleAgentSessionResumeMode(sessions, effective.agentId, effective);
329
+ const session = sessions?.sessions[effective.agentId];
330
+ const owner = request.scope === "task"
331
+ ? { scope: "task", taskId: request.taskId, roleName: request.roleName }
332
+ : { scope: "global", roleName: request.roleName };
333
+ const common = {
334
+ owner,
335
+ agentId: effective.agentId,
336
+ adapterId: binding.adapterId,
337
+ effective,
338
+ workspace: effective.workspace.root,
339
+ ...(managedWorkspace === undefined ? {} : { managedWorkspace }),
340
+ ...(activeRun === null ? {} : { runId: activeRun.id }),
341
+ ...(request.environment === undefined
342
+ ? {}
343
+ : { environment: request.environment })
344
+ };
345
+ const launchRequest = mode === "new"
346
+ ? { ...common, mode: "new" }
347
+ : {
348
+ ...common,
349
+ mode: "resume",
350
+ nativeSessionId: session.nativeSessionId
351
+ };
352
+ const assertCurrent = () => assertRuntimeLaunchRequestCurrent(store, launchRequest);
353
+ const runtimeBinding = await launchCoordinator.prepare(launchRequest, "immediate", assertCurrent);
354
+ return {
355
+ ensured: true,
356
+ sessionStarted: runtimeBinding.hostCreated === true,
357
+ scope: request.scope,
358
+ roleName: request.roleName,
359
+ ...(request.scope === "task" ? { taskId: request.taskId } : {})
360
+ };
361
+ }
362
+ finally {
363
+ release();
364
+ if (lifecycleTails.get(lifecycleKey) === tail) {
365
+ lifecycleTails.delete(lifecycleKey);
366
+ }
367
+ }
368
+ };
369
+ }
370
+ function resolveRuntimeDesiredEffective(store, request, role) {
371
+ if (request.scope === "global") {
372
+ return resolveEffectiveLaunch({
373
+ role: role,
374
+ purpose: "execution"
375
+ });
376
+ }
377
+ const taskRole = role;
378
+ const item = store.listWorkItems(request.taskId).find((candidate) => (candidate.assignee === request.roleName
379
+ && !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
380
+ const workspace = (item === null
381
+ ? store.getTaskWorkspace(request.taskId)
382
+ : store.getWorkItemWorkspace(request.taskId, item.id))
383
+ ?? store.getTaskWorkspace(request.taskId)
384
+ ?? undefined;
385
+ return resolveEffectiveLaunch({
386
+ role: taskRole,
387
+ purpose: "execution",
388
+ ...(workspace === undefined ? {} : { workspace }),
389
+ ...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
29
390
  });
30
- return { ...running, store, schedulerStore, planner, tmux, delivery, workspacePreparer };
391
+ }
392
+ function assertRuntimeLaunchRequestCurrent(store, request) {
393
+ let activeRun = null;
394
+ if (request.owner.scope === "task") {
395
+ const task = store.getTask(request.owner.taskId);
396
+ if (task === null || task.status !== "active") {
397
+ throw new Error(`Task is no longer active: ${request.owner.taskId}.`);
398
+ }
399
+ activeRun = store.getActiveAgentRun(request.owner.taskId, request.owner.roleName);
400
+ if (request.runId !== undefined
401
+ && activeRun?.id !== request.runId) {
402
+ throw new Error(`Role Run is no longer current: ${request.runId}.`);
403
+ }
404
+ }
405
+ const role = request.owner.scope === "task"
406
+ ? store.getRole(request.owner.taskId, request.owner.roleName)
407
+ : store.getGlobalRole(request.owner.roleName);
408
+ if (role === null) {
409
+ throw new Error(`Role no longer exists: ${request.owner.roleName}.`);
410
+ }
411
+ const sessions = request.owner.scope === "task"
412
+ ? store.getTaskRoleSessionSet(request.owner.taskId, request.owner.roleName)
413
+ : store.getGlobalRoleSessionSet(request.owner.roleName);
414
+ const expectedEffective = request.owner.scope === "task" && request.runId !== undefined
415
+ ? activeRun?.effective
416
+ : activeLiveRoleAgentSession(sessions)?.effective
417
+ ?? currentDesiredEffective(store, request, role);
418
+ if (expectedEffective === undefined
419
+ || !isDeepStrictEqual(expectedEffective, request.effective)
420
+ || request.effective.agentId !== request.agentId
421
+ || request.effective.adapterId !== request.adapterId
422
+ || request.effective.workspace.root !== request.workspace) {
423
+ throw new Error(`Role launch state changed: ${request.owner.roleName}.`);
424
+ }
425
+ if (request.owner.scope === "task" && request.managedWorkspace !== undefined) {
426
+ const expectedWorkspace = activeRun?.workspace
427
+ ?? currentDesiredManagedWorkspace(store, request.owner.taskId, request.owner.roleName);
428
+ if (!isDeepStrictEqual(expectedWorkspace, request.managedWorkspace)) {
429
+ throw new Error(`Managed workspace launch state changed: ${request.owner.roleName}.`);
430
+ }
431
+ }
432
+ const binding = role.agentBindings[request.agentId];
433
+ if (binding === undefined || binding.adapterId !== request.adapterId) {
434
+ throw new Error(`Role effective binding changed: ${request.owner.roleName}.`);
435
+ }
436
+ const agent = store.getConfiguredAgent(request.agentId);
437
+ if (agent === null || agent.adapterId !== request.adapterId) {
438
+ throw new Error(`Agent launch state changed: ${request.agentId}.`);
439
+ }
440
+ if (request.mode === "resume") {
441
+ const session = request.owner.scope === "task"
442
+ ? store.getTaskRoleSessionSet(request.owner.taskId, request.owner.roleName)
443
+ ?.sessions[request.agentId]
444
+ : store.getGlobalRoleSessionSet(request.owner.roleName)
445
+ ?.sessions[request.agentId];
446
+ if (session === null || session === undefined
447
+ || session.nativeSessionId !== request.nativeSessionId) {
448
+ throw new Error(`Native session changed: ${request.owner.roleName}.`);
449
+ }
450
+ if (!effectiveLaunchSnapshotsCompatible(session.effective, request.effective)) {
451
+ throw new Error(`Native session effective launch changed: ${request.owner.roleName}.`);
452
+ }
453
+ }
454
+ }
455
+ function currentDesiredEffective(store, request, role) {
456
+ if (request.owner.scope === "global") {
457
+ return resolveEffectiveLaunch({ role: role, purpose: "execution" });
458
+ }
459
+ const item = store.listWorkItems(request.owner.taskId).find((candidate) => (candidate.assignee === request.owner.roleName
460
+ && !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
461
+ const workspace = (item === null
462
+ ? store.getTaskWorkspace(request.owner.taskId)
463
+ : store.getWorkItemWorkspace(request.owner.taskId, item.id))
464
+ ?? store.getTaskWorkspace(request.owner.taskId)
465
+ ?? undefined;
466
+ return resolveEffectiveLaunch({
467
+ role: role,
468
+ purpose: "execution",
469
+ ...(workspace === undefined ? {} : { workspace }),
470
+ ...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
471
+ });
472
+ }
473
+ function runtimeLaunchFingerprint(store, request) {
474
+ const agent = store.getConfiguredAgent(request.effective.agentId);
475
+ if (agent === null) {
476
+ throw new Error(`Agent no longer exists: ${request.effective.agentId}.`);
477
+ }
478
+ return createHash("sha256").update(JSON.stringify([
479
+ request.owner,
480
+ request.effective,
481
+ request.managedWorkspace,
482
+ request.runtimePolicy,
483
+ agent
484
+ ])).digest("hex");
485
+ }
486
+ function currentDesiredManagedWorkspace(store, taskId, roleName) {
487
+ const item = store.listWorkItems(taskId).find((candidate) => (candidate.assignee === roleName
488
+ && !["completed", "failed", "retired"].includes(candidate.status))) ?? null;
489
+ return (item === null
490
+ ? store.getTaskWorkspace(taskId)
491
+ : store.getWorkItemWorkspace(taskId, item.id))
492
+ ?? store.getTaskWorkspace(taskId)
493
+ ?? undefined;
494
+ }
495
+ function hasPendingRuntimeCleanup(store, request) {
496
+ const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(request));
497
+ return hasRuntimeCleanupObligation(mailbox);
498
+ }
499
+ function parseEnsureRoleSession(params) {
500
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
501
+ throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
502
+ }
503
+ const value = params;
504
+ const roleName = requiredParam(value.roleName);
505
+ const environment = parseLifecycleEnvironment(value.environment);
506
+ const expectedFields = environment === undefined ? 0 : 1;
507
+ if (value.scope === "global" && Object.keys(value).length === 2 + expectedFields) {
508
+ return {
509
+ scope: "global",
510
+ roleName,
511
+ ...(environment === undefined ? {} : { environment })
512
+ };
513
+ }
514
+ if (value.scope === "task" && Object.keys(value).length === 3 + expectedFields) {
515
+ return {
516
+ scope: "task",
517
+ taskId: requiredParam(value.taskId),
518
+ roleName,
519
+ ...(environment === undefined ? {} : { environment })
520
+ };
521
+ }
522
+ throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
523
+ }
524
+ function parseLifecycleEnvironment(value) {
525
+ if (value === undefined)
526
+ return undefined;
527
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
528
+ throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
529
+ }
530
+ const entries = Object.entries(value);
531
+ if (entries.length > 256) {
532
+ throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
533
+ }
534
+ const environment = {};
535
+ for (const [name, item] of entries) {
536
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
537
+ || typeof item !== "string"
538
+ || item.includes("\0")
539
+ || MANAGED_RUNTIME_ENVIRONMENT.has(name)) {
540
+ throw applicationError("INVALID_PARAMS", "Runtime session environment is invalid.");
541
+ }
542
+ environment[name] = item;
543
+ }
544
+ return environment;
545
+ }
546
+ function validateLifecycleEnvironment(environment, agent) {
547
+ if (environment === undefined)
548
+ return;
549
+ const declared = new Map(agent.environment.map((binding) => [
550
+ binding.sourceName,
551
+ binding
552
+ ]));
553
+ const allowed = new Set([
554
+ ...declared.keys(),
555
+ ...AGENT_OPERATIONAL_ENVIRONMENT_NAMES,
556
+ ...nativeAgentEnvironmentNames(agent.adapterId)
557
+ ]);
558
+ for (const name of Object.keys(environment)) {
559
+ if (!allowed.has(name)) {
560
+ throw applicationError("INVALID_PARAMS", `Runtime session environment source is not declared: ${name}.`);
561
+ }
562
+ }
563
+ const missing = agent.environment.find((binding) => (binding.required
564
+ && !MANAGED_RUNTIME_ENVIRONMENT.has(binding.sourceName)
565
+ && environment[binding.sourceName] === undefined));
566
+ if (missing !== undefined) {
567
+ throw applicationError("INVALID_PARAMS", `Required Agent environment is missing: ${missing.sourceName}.`);
568
+ }
569
+ }
570
+ const MANAGED_RUNTIME_ENVIRONMENT = new Set(YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES);
571
+ function parseEnvironmentRefresh(params) {
572
+ if (typeof params !== "object"
573
+ || params === null
574
+ || Array.isArray(params)
575
+ || Object.keys(params).length !== 4) {
576
+ throw applicationError("INVALID_PARAMS", "Runtime Agent environment refresh params are invalid.");
577
+ }
578
+ const values = params;
579
+ return {
580
+ sources: parseEnvironmentValues(values.sources, "sources"),
581
+ sourceNames: parseEnvironmentNames(values.sourceNames, "source names"),
582
+ nativeSources: parseEnvironmentValues(values.nativeSources, "native sources"),
583
+ nativeNames: parseEnvironmentNames(values.nativeNames, "native names")
584
+ };
585
+ }
586
+ function parseEnvironmentValues(value, label) {
587
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
588
+ throw applicationError("INVALID_PARAMS", `Runtime Agent environment ${label} are invalid.`);
589
+ }
590
+ const entries = Object.entries(value);
591
+ if (entries.length > 256) {
592
+ throw applicationError("INVALID_PARAMS", "Runtime Agent environment refresh has too many sources.");
593
+ }
594
+ const sources = {};
595
+ for (const [name, item] of entries) {
596
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
597
+ || typeof item !== "string"
598
+ || item.includes("\0")
599
+ || MANAGED_RUNTIME_ENVIRONMENT.has(name)) {
600
+ throw applicationError("INVALID_PARAMS", `Runtime Agent environment source is invalid: ${name}.`);
601
+ }
602
+ sources[name] = item;
603
+ }
604
+ return sources;
605
+ }
606
+ function parseEnvironmentNames(value, label) {
607
+ if (!Array.isArray(value)
608
+ || value.length > 256
609
+ || value.some((name) => (typeof name !== "string"
610
+ || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
611
+ || MANAGED_RUNTIME_ENVIRONMENT.has(name)))) {
612
+ throw applicationError("INVALID_PARAMS", `Runtime Agent environment ${label} are invalid.`);
613
+ }
614
+ return [...new Set(value)];
615
+ }
616
+ function validateEnvironmentRefreshSources(store, refresh) {
617
+ const declared = new Set(store.listConfiguredAgents().flatMap((agent) => (agent.environment.map((binding) => binding.sourceName))));
618
+ for (const name of Object.keys(refresh.sources)) {
619
+ if (!declared.has(name)) {
620
+ throw applicationError("INVALID_PARAMS", `Runtime Agent environment source is not declared: ${name}.`);
621
+ }
622
+ }
623
+ if (Object.keys(refresh.sources).some((name) => !refresh.sourceNames.includes(name))) {
624
+ throw applicationError("INVALID_PARAMS", "Runtime Agent environment source scope is invalid.");
625
+ }
626
+ const native = new Set(store.listConfiguredAgents().flatMap((agent) => (nativeAgentEnvironmentNames(agent.adapterId))));
627
+ for (const name of Object.keys(refresh.nativeSources)) {
628
+ if (!native.has(name) || !refresh.nativeNames.includes(name)) {
629
+ throw applicationError("INVALID_PARAMS", `Runtime native Agent environment source is not declared: ${name}.`);
630
+ }
631
+ }
632
+ const allowedNative = new Set(["CODEX_HOME", "CLAUDE_CONFIG_DIR"]);
633
+ if (refresh.nativeNames.some((name) => !allowedNative.has(name))) {
634
+ throw applicationError("INVALID_PARAMS", "Runtime native Agent environment scope is invalid.");
635
+ }
636
+ }
637
+ function requiredParam(value) {
638
+ if (typeof value !== "string"
639
+ || value.length === 0
640
+ || value.trim() !== value
641
+ || value.includes("\0")) {
642
+ throw applicationError("INVALID_PARAMS", "Runtime session params are invalid.");
643
+ }
644
+ return value;
645
+ }
646
+ function applicationError(code, message) {
647
+ const error = Object.assign(new Error(message), { code });
648
+ error.name = "CoreApplicationError";
649
+ return error;
31
650
  }