@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
@@ -0,0 +1,290 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
5
+ import { parse } from "smol-toml";
6
+ /**
7
+ * Inspects the local, file-backed Codex configuration layers supported by Yui
8
+ * that can conflict with launch-owned settings. Remote and platform-managed
9
+ * layers are outside this compatibility boundary. Project files are considered
10
+ * only when the directory, project root, or project root is trusted.
11
+ */
12
+ export function inspectCodexDeveloperInstructions(input) {
13
+ return inspectCodexConfigKeys(input, ["developer_instructions"])
14
+ .developerInstructions;
15
+ }
16
+ export function inspectCodexLaunchConfig(input) {
17
+ return inspectCodexConfigKeys(input, ["developer_instructions", "notify"]);
18
+ }
19
+ function inspectCodexConfigKeys(input, keys) {
20
+ const environment = input.environment ?? process.env;
21
+ const home = codexHome(environment);
22
+ const systemPath = checkedAbsolutePath(input.systemConfigPath ?? "/etc/codex/config.toml", "Codex system config path");
23
+ const managedPath = checkedAbsolutePath(input.managedConfigPath ?? "/etc/codex/managed_config.toml", "Codex managed config path");
24
+ const userPath = join(home, "config.toml");
25
+ const profilePath = input.profile === undefined
26
+ ? undefined
27
+ : profileConfigPath(home, input.profile);
28
+ const discoveryPaths = [
29
+ systemPath,
30
+ userPath,
31
+ ...(profilePath === undefined ? [] : [profilePath])
32
+ ];
33
+ const basePaths = [...discoveryPaths, managedPath];
34
+ const contentsByPath = new Map(basePaths.map((path) => [path, readOptionalConfig(path)]));
35
+ // Managed policy is applied after project configuration by Codex and cannot
36
+ // decide whether project configuration is discoverable.
37
+ const discovery = effectiveProjectDiscovery(discoveryPaths.flatMap((path) => {
38
+ const contents = contentsByPath.get(path);
39
+ if (contents === null || contents === undefined)
40
+ return [];
41
+ try {
42
+ return [inspectProjectDiscovery(contents)];
43
+ }
44
+ catch (error) {
45
+ throw unreliableInspection(path, error);
46
+ }
47
+ }));
48
+ const projectPaths = projectConfigPaths(input.workspace, input.trustWorkspace === true
49
+ ? withExactWorkspaceTrust(discovery, input.workspace)
50
+ : discovery);
51
+ const candidates = [
52
+ ...discoveryPaths.map((path) => ({ path, keys })),
53
+ ...projectPaths.map((path) => ({
54
+ path,
55
+ keys: keys.filter((key) => key === "developer_instructions")
56
+ })),
57
+ { path: managedPath, keys }
58
+ ];
59
+ let developerInstructions = { status: "absent" };
60
+ let notify = { status: "absent" };
61
+ for (const candidate of candidates) {
62
+ const contents = contentsByPath.has(candidate.path)
63
+ ? contentsByPath.get(candidate.path)
64
+ : readOptionalConfig(candidate.path);
65
+ if (contents === null)
66
+ continue;
67
+ let configured;
68
+ try {
69
+ configured = inspectConfigContents(contents, candidate.keys);
70
+ }
71
+ catch (error) {
72
+ throw unreliableInspection(candidate.path, error);
73
+ }
74
+ if (developerInstructions.status === "absent"
75
+ && configured.has("developer_instructions")) {
76
+ developerInstructions = { status: "configured", source: candidate.path };
77
+ }
78
+ if (notify.status === "absent" && configured.has("notify")) {
79
+ notify = { status: "configured", source: candidate.path };
80
+ }
81
+ }
82
+ return { developerInstructions, notify };
83
+ }
84
+ function withExactWorkspaceTrust(discovery, workspace) {
85
+ return {
86
+ ...discovery,
87
+ trust: new Map([...discovery.trust, [resolve(workspace), "trusted"]])
88
+ };
89
+ }
90
+ function effectiveProjectDiscovery(layers) {
91
+ let rootMarkers;
92
+ const trust = new Map();
93
+ for (const layer of layers) {
94
+ if (layer.rootMarkers !== undefined)
95
+ rootMarkers = layer.rootMarkers;
96
+ for (const [path, value] of layer.trust)
97
+ trust.set(path, value);
98
+ }
99
+ return { rootMarkers, trust };
100
+ }
101
+ function projectConfigPaths(workspace, discovery) {
102
+ const start = resolve(workspace);
103
+ const projectRoot = findProjectRoot(start, discovery.rootMarkers ?? [".git"]);
104
+ let repositoryRoot;
105
+ let repositoryRootResolved = false;
106
+ const getRepositoryRoot = () => {
107
+ if (!repositoryRootResolved) {
108
+ repositoryRoot = resolveRepositoryRootForTrust(start);
109
+ repositoryRootResolved = true;
110
+ }
111
+ return repositoryRoot;
112
+ };
113
+ const paths = [];
114
+ let current = start;
115
+ while (true) {
116
+ const configPath = join(current, ".codex", "config.toml");
117
+ if (existsSync(configPath)
118
+ &&
119
+ trustForDirectory(current, projectRoot, getRepositoryRoot, discovery.trust)
120
+ === "trusted") {
121
+ paths.push(configPath);
122
+ }
123
+ if (samePath(current, projectRoot))
124
+ break;
125
+ const parent = dirname(current);
126
+ if (parent === current)
127
+ break;
128
+ current = parent;
129
+ }
130
+ return paths.reverse();
131
+ }
132
+ function findProjectRoot(start, markers) {
133
+ // Native Codex treats an explicitly empty marker list as "current directory".
134
+ if (markers.length === 0)
135
+ return start;
136
+ let current = start;
137
+ while (true) {
138
+ if (markers.some((marker) => existsSync(isAbsolute(marker) ? marker : `${current}${sep}${marker}`))) {
139
+ return current;
140
+ }
141
+ const parent = dirname(current);
142
+ if (parent === current)
143
+ return start;
144
+ current = parent;
145
+ }
146
+ }
147
+ function trustForDirectory(directory, projectRoot, repositoryRoot, configured) {
148
+ for (const candidate of [directory, projectRoot]) {
149
+ const value = exactTrust(candidate, configured);
150
+ if (value !== undefined)
151
+ return value;
152
+ }
153
+ for (const candidate of [repositoryRoot()]) {
154
+ if (candidate === undefined)
155
+ continue;
156
+ const value = exactTrust(candidate, configured);
157
+ if (value !== undefined)
158
+ return value;
159
+ }
160
+ return undefined;
161
+ }
162
+ function exactTrust(directory, configured) {
163
+ const lexical = resolve(directory);
164
+ const canonical = canonicalPath(lexical);
165
+ for (const [path, value] of configured) {
166
+ const configuredLexical = resolve(path);
167
+ if (samePath(configuredLexical, lexical)
168
+ || samePath(canonicalPath(configuredLexical), canonical)) {
169
+ return value;
170
+ }
171
+ }
172
+ return undefined;
173
+ }
174
+ function resolveRepositoryRootForTrust(workspace) {
175
+ const result = spawnSync("git", ["-C", workspace, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", timeout: 1_000, windowsHide: true });
176
+ if (result.status !== 0 || result.error !== undefined)
177
+ return undefined;
178
+ const commonDirectory = result.stdout.trim();
179
+ if (!isAbsolute(commonDirectory) || commonDirectory.includes("\0"))
180
+ return undefined;
181
+ const normalized = resolve(commonDirectory);
182
+ return basename(normalized) === ".git" ? dirname(normalized) : undefined;
183
+ }
184
+ function inspectProjectDiscovery(contents) {
185
+ const table = parseConfig(contents);
186
+ const rootMarkers = parseRootMarkers(table.project_root_markers);
187
+ const trust = new Map();
188
+ const projects = table.projects;
189
+ if (projects !== undefined) {
190
+ if (!isTable(projects)) {
191
+ throw new Error("projects must be a TOML table");
192
+ }
193
+ for (const [path, project] of Object.entries(projects)) {
194
+ if (!isAbsolute(path) || path.includes("\0")) {
195
+ throw new Error("project trust path is not absolute");
196
+ }
197
+ if (!isTable(project)) {
198
+ throw new Error("project trust entry must be a TOML table");
199
+ }
200
+ const level = project.trust_level;
201
+ if (level === "trusted" || level === "untrusted")
202
+ trust.set(path, level);
203
+ else if (level !== undefined) {
204
+ throw new Error("project trust_level must be trusted or untrusted");
205
+ }
206
+ }
207
+ }
208
+ return {
209
+ ...(rootMarkers === undefined ? {} : { rootMarkers }),
210
+ trust
211
+ };
212
+ }
213
+ function parseRootMarkers(value) {
214
+ if (value === undefined)
215
+ return undefined;
216
+ if (!Array.isArray(value) || value.some((marker) => typeof marker !== "string")) {
217
+ throw new Error("project_root_markers must be an array of strings");
218
+ }
219
+ if (value.some((marker) => marker.includes("\0"))) {
220
+ throw new Error("project_root_markers cannot contain NUL bytes");
221
+ }
222
+ return value;
223
+ }
224
+ function inspectConfigContents(contents, keys) {
225
+ const table = parseConfig(contents);
226
+ return new Set(keys.filter((key) => Object.hasOwn(table, key)));
227
+ }
228
+ function parseConfig(contents) {
229
+ try {
230
+ return parse(contents);
231
+ }
232
+ catch (error) {
233
+ throw new Error(`ambiguous or invalid TOML: ${error instanceof Error ? error.message : String(error)}`);
234
+ }
235
+ }
236
+ function isTable(value) {
237
+ return typeof value === "object" && value !== null && !Array.isArray(value);
238
+ }
239
+ function checkedAbsolutePath(path, label) {
240
+ if (path.includes("\0") || resolve(path) !== path) {
241
+ throw new Error(`${label} must be an absolute normalized path.`);
242
+ }
243
+ return path;
244
+ }
245
+ function codexHome(environment) {
246
+ const configured = environment.CODEX_HOME?.trim();
247
+ const launchHome = environment.HOME?.trim();
248
+ if (configured?.includes("\0"))
249
+ throw new Error("CODEX_HOME cannot contain NUL bytes.");
250
+ if (launchHome?.includes("\0"))
251
+ throw new Error("HOME cannot contain NUL bytes.");
252
+ return resolve(configured === undefined || configured.length === 0
253
+ ? join(launchHome === undefined || launchHome.length === 0 ? homedir() : launchHome, ".codex")
254
+ : configured);
255
+ }
256
+ function profileConfigPath(home, profile) {
257
+ const name = profile.trim();
258
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(name)) {
259
+ throw new Error(`Codex profile must be a plain name before its config can be inspected: ${profile}.`);
260
+ }
261
+ return join(home, `${name}.config.toml`);
262
+ }
263
+ function readOptionalConfig(path) {
264
+ try {
265
+ return readFileSync(path, "utf8");
266
+ }
267
+ catch (error) {
268
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
269
+ return null;
270
+ throw new Error(`Codex config could not be inspected: ${path}: `
271
+ + `${error instanceof Error ? error.message : String(error)}`);
272
+ }
273
+ }
274
+ function unreliableInspection(path, error) {
275
+ return new Error(`Codex launch config could not be inspected reliably: ${path}: `
276
+ + `${error instanceof Error ? error.message : String(error)}`);
277
+ }
278
+ function canonicalPath(path) {
279
+ try {
280
+ return realpathSync.native(path);
281
+ }
282
+ catch {
283
+ return resolve(path);
284
+ }
285
+ }
286
+ function samePath(left, right) {
287
+ return process.platform === "win32"
288
+ ? left.toLowerCase() === right.toLowerCase()
289
+ : left === right;
290
+ }
@@ -0,0 +1,340 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { resolveAgentAdapter } from "./agentAdapter.js";
3
+ export function resolveEffectiveLaunch(input) {
4
+ validateDesiredRole(input.role);
5
+ const binding = input.role.agentBindings[input.role.activeAgentId];
6
+ const workspace = snapshotWorkspace(input.role.workspace, input.workspace);
7
+ const writeProjectIds = effectiveWriteProjects(input, workspace);
8
+ const config = resolveAgentAdapter(binding.adapterId).canonicalizeConfig(clone(binding.config));
9
+ return snapshotFromConfig({
10
+ sourceDesiredRevision: input.role.launchRevision,
11
+ agentId: binding.agentId,
12
+ config,
13
+ profileAccess: input.role.defaultAccess,
14
+ writeProjectIds,
15
+ workspace,
16
+ context: snapshotContext(input.role),
17
+ ...(input.purpose === "review"
18
+ ? {
19
+ reviewRoundId: identity(input.reviewRoundId ?? "", "ReviewRound id"),
20
+ reviewBaseCommit: commit(input.reviewBaseCommit ?? "", "Review base commit")
21
+ }
22
+ : {})
23
+ });
24
+ }
25
+ export function effectiveLaunchConfig(snapshot) {
26
+ validateEffectiveLaunchSnapshot(snapshot);
27
+ return effectiveLaunchConfigUnchecked(snapshot);
28
+ }
29
+ function codexConfigFromSnapshot(snapshot) {
30
+ return {
31
+ adapterId: "codex",
32
+ ...(snapshot.model === undefined ? {} : { model: snapshot.model }),
33
+ ...(snapshot.effort === undefined ? {} : { effort: snapshot.effort }),
34
+ permission: clone(snapshot.permission),
35
+ ...(snapshot.additionalDirectories === undefined
36
+ ? {}
37
+ : { additionalDirectories: [...snapshot.additionalDirectories] }),
38
+ ...(snapshot.advanced === undefined
39
+ ? {}
40
+ : { advanced: clone(snapshot.advanced) }),
41
+ ...(snapshot.search ? { search: true } : {}),
42
+ ...(snapshot.profile === undefined ? {} : { profile: snapshot.profile })
43
+ };
44
+ }
45
+ function claudeConfigFromSnapshot(snapshot) {
46
+ return {
47
+ adapterId: "claude",
48
+ ...(snapshot.model === undefined ? {} : { model: snapshot.model }),
49
+ ...(snapshot.effort === undefined ? {} : { effort: snapshot.effort }),
50
+ permission: clone(snapshot.permission),
51
+ ...(snapshot.additionalDirectories === undefined
52
+ ? {}
53
+ : { additionalDirectories: [...snapshot.additionalDirectories] }),
54
+ ...(snapshot.advanced === undefined
55
+ ? {}
56
+ : { advanced: clone(snapshot.advanced) }),
57
+ ...(snapshot.settingsFile === undefined
58
+ ? {}
59
+ : { settingsFile: snapshot.settingsFile }),
60
+ ...(snapshot.settingsSources === undefined
61
+ ? {}
62
+ : { settingsSources: [...snapshot.settingsSources] })
63
+ };
64
+ }
65
+ export function effectiveLaunchSnapshotsCompatible(existing, desired) {
66
+ validateEffectiveLaunchSnapshot(existing);
67
+ validateEffectiveLaunchSnapshot(desired);
68
+ const withoutDesiredRevision = (snapshot) => {
69
+ const { sourceDesiredRevision: _sourceDesiredRevision, ...actual } = snapshot;
70
+ return actual;
71
+ };
72
+ return isDeepStrictEqual(withoutDesiredRevision(existing), withoutDesiredRevision(desired));
73
+ }
74
+ export function validateEffectiveLaunchSnapshot(snapshot) {
75
+ if (snapshot.schemaVersion !== 2) {
76
+ throw new Error("Effective launch snapshot must use schemaVersion 2.");
77
+ }
78
+ positiveInteger(snapshot.sourceDesiredRevision, "Source desired revision");
79
+ identity(snapshot.agentId, "Effective Agent id");
80
+ if (snapshot.profileAccess !== "read" && snapshot.profileAccess !== "write") {
81
+ throw new Error(`Effective launch Profile access is invalid: ${String(snapshot.profileAccess)}.`);
82
+ }
83
+ if (typeof snapshot.search !== "boolean") {
84
+ throw new Error("Effective launch search flag must be boolean.");
85
+ }
86
+ if (snapshot.adapterId === "claude" && snapshot.search) {
87
+ throw new Error("Claude effective launch cannot enable Codex search.");
88
+ }
89
+ const writeProjectIds = uniqueIdentities(snapshot.writeProjectIds, "Effective writable Project");
90
+ if (!isDeepStrictEqual(writeProjectIds, snapshot.writeProjectIds)) {
91
+ throw new Error("Effective writable Projects must be unique and sorted.");
92
+ }
93
+ if (snapshot.permission === undefined) {
94
+ throw new Error("Effective launch requires an explicit permission strategy.");
95
+ }
96
+ if ((snapshot.reviewRoundId === undefined) !== (snapshot.reviewBaseCommit === undefined)) {
97
+ throw new Error("Effective Review base is incomplete.");
98
+ }
99
+ if (snapshot.reviewRoundId !== undefined && snapshot.reviewBaseCommit !== undefined) {
100
+ identity(snapshot.reviewRoundId, "Effective ReviewRound id");
101
+ commit(snapshot.reviewBaseCommit, "Effective review base commit");
102
+ }
103
+ validateWorkspace(snapshot.workspace);
104
+ cloneContext(snapshot.context);
105
+ const config = effectiveLaunchConfigUnchecked(snapshot);
106
+ resolveAgentAdapter(snapshot.adapterId).canonicalizeConfig(config);
107
+ return snapshot;
108
+ }
109
+ export function effectiveRoleForLaunch(role, snapshot) {
110
+ validateEffectiveLaunchSnapshot(snapshot);
111
+ const config = effectiveLaunchConfig(snapshot);
112
+ const binding = {
113
+ agentId: snapshot.agentId,
114
+ adapterId: snapshot.adapterId,
115
+ config
116
+ };
117
+ const result = {
118
+ ...role,
119
+ ...cloneContext(snapshot.context),
120
+ activeAgentId: snapshot.agentId,
121
+ agentBindings: { ...role.agentBindings, [snapshot.agentId]: binding },
122
+ launchRevision: snapshot.sourceDesiredRevision,
123
+ defaultAccess: snapshot.profileAccess,
124
+ workspace: snapshot.workspace.root
125
+ };
126
+ clearMissingContext(result, snapshot.context);
127
+ return result;
128
+ }
129
+ function snapshotFromConfig(input) {
130
+ const config = clone(input.config);
131
+ if (config.permission === undefined) {
132
+ throw new Error("Effective launch requires an explicit permission strategy.");
133
+ }
134
+ const review = input.reviewRoundId === undefined
135
+ ? {}
136
+ : {
137
+ reviewRoundId: identity(input.reviewRoundId, "ReviewRound id"),
138
+ reviewBaseCommit: commit(input.reviewBaseCommit ?? "", "Review base commit")
139
+ };
140
+ const common = {
141
+ schemaVersion: 2,
142
+ sourceDesiredRevision: positiveInteger(input.sourceDesiredRevision, "Source desired revision"),
143
+ agentId: identity(input.agentId, "Effective Agent id"),
144
+ profileAccess: input.profileAccess,
145
+ ...(config.model === undefined ? {} : { model: config.model }),
146
+ ...(config.effort === undefined ? {} : { effort: config.effort }),
147
+ search: config.adapterId === "codex" && config.search === true,
148
+ ...(config.additionalDirectories === undefined
149
+ ? {}
150
+ : { additionalDirectories: [...config.additionalDirectories] }),
151
+ ...(config.advanced === undefined ? {} : { advanced: clone(config.advanced) }),
152
+ writeProjectIds: [...input.writeProjectIds],
153
+ workspace: cloneWorkspace(input.workspace),
154
+ context: cloneContext(input.context),
155
+ ...review
156
+ };
157
+ const snapshot = config.adapterId === "codex"
158
+ ? {
159
+ ...common,
160
+ adapterId: "codex",
161
+ permission: clone(config.permission),
162
+ ...(config.profile === undefined ? {} : { profile: config.profile })
163
+ }
164
+ : {
165
+ ...common,
166
+ adapterId: "claude",
167
+ permission: clone(config.permission),
168
+ ...(config.settingsFile === undefined ? {} : { settingsFile: config.settingsFile }),
169
+ ...(config.settingsSources === undefined
170
+ ? {}
171
+ : { settingsSources: [...config.settingsSources] })
172
+ };
173
+ return validateEffectiveLaunchSnapshot(snapshot);
174
+ }
175
+ function effectiveLaunchConfigUnchecked(snapshot) {
176
+ return snapshot.adapterId === "codex"
177
+ ? codexConfigFromSnapshot(snapshot)
178
+ : claudeConfigFromSnapshot(snapshot);
179
+ }
180
+ function effectiveWriteProjects(input, workspace) {
181
+ if (input.purpose === "review") {
182
+ if (!("taskId" in input.role)) {
183
+ throw new Error("Review launch requires a Task Role.");
184
+ }
185
+ if (input.reviewRoundId === undefined || input.reviewBaseCommit === undefined) {
186
+ throw new Error("Review launch requires exact ReviewRound provenance.");
187
+ }
188
+ commit(input.reviewBaseCommit, "Review base commit");
189
+ const reviewWorkspace = input.workspace;
190
+ if (reviewWorkspace === undefined
191
+ || (reviewWorkspace.owner.type !== "review-round"
192
+ && (reviewWorkspace.owner.type !== "execution-lane"
193
+ || reviewWorkspace.owner.purpose !== "review"))) {
194
+ throw new Error("Review launch requires a ReviewRound-owned workspace.");
195
+ }
196
+ if (reviewWorkspace.owner.reviewRoundId !== input.reviewRoundId) {
197
+ throw new Error(`ReviewRound workspace owner does not match ${input.reviewRoundId}.`);
198
+ }
199
+ if (reviewWorkspace.entries.length === 0
200
+ || reviewWorkspace.entries.some(({ access }) => access !== "write")) {
201
+ throw new Error("Every ReviewRound workspace Project must be an isolated writable entry.");
202
+ }
203
+ return uniqueIdentities(reviewWorkspace.entries.map(({ projectId }) => projectId), "Review workspace Project");
204
+ }
205
+ const workspaceWrite = uniqueIdentities(workspace.entries
206
+ .filter(({ access }) => access === "write")
207
+ .map(({ projectId }) => projectId), "Workspace writable Project");
208
+ if (!("taskId" in input.role))
209
+ return [];
210
+ // A managed workspace describes what exists, not who is authorized to
211
+ // mutate it. Only an explicit WorkItem write scope can grant Task writes.
212
+ if (input.workItemWriteProjectIds === undefined)
213
+ return [];
214
+ const requested = uniqueIdentities(input.workItemWriteProjectIds, "WorkItem writable Project");
215
+ if (requested.length === 0)
216
+ return [];
217
+ if (!isDeepStrictEqual(requested, workspaceWrite)) {
218
+ throw new Error("WorkItem write scope does not match the managed workspace write scope.");
219
+ }
220
+ return requested;
221
+ }
222
+ function snapshotWorkspace(roleWorkspace, workspace) {
223
+ if (workspace === undefined)
224
+ return { root: text(roleWorkspace, "Role workspace"), entries: [] };
225
+ return {
226
+ root: text(workspace.root, "Effective workspace root"),
227
+ entries: workspace.entries.map((entry) => ({ ...entry }))
228
+ };
229
+ }
230
+ function snapshotContext(role) {
231
+ return cloneContext(role);
232
+ }
233
+ function cloneContext(context) {
234
+ return {
235
+ ...(context.description === undefined ? {} : { description: text(context.description, "Role description") }),
236
+ ...(context.responsibilities === undefined
237
+ ? {}
238
+ : { responsibilities: context.responsibilities.map((value) => text(value, "Role responsibility")) }),
239
+ ...(context.constraints === undefined
240
+ ? {}
241
+ : { constraints: context.constraints.map((value) => text(value, "Role constraint")) }),
242
+ ...(context.expectedOutput === undefined
243
+ ? {}
244
+ : { expectedOutput: text(context.expectedOutput, "Role expected output") }),
245
+ ...(context.systemPrompt === undefined
246
+ ? {}
247
+ : { systemPrompt: text(context.systemPrompt, "Role system prompt") }),
248
+ ...(context.skills === undefined
249
+ ? {}
250
+ : { skills: context.skills.map((value) => identity(value, "Role Skill id")) })
251
+ };
252
+ }
253
+ function clearMissingContext(role, context) {
254
+ for (const key of [
255
+ "description",
256
+ "responsibilities",
257
+ "constraints",
258
+ "expectedOutput",
259
+ "systemPrompt",
260
+ "skills"
261
+ ]) {
262
+ if (!Object.hasOwn(context, key))
263
+ delete role[key];
264
+ }
265
+ }
266
+ function cloneWorkspace(workspace) {
267
+ validateWorkspace(workspace);
268
+ return {
269
+ root: workspace.root,
270
+ entries: workspace.entries.map((entry) => ({ ...entry }))
271
+ };
272
+ }
273
+ function validateWorkspace(workspace) {
274
+ text(workspace.root, "Effective workspace root");
275
+ if (!Array.isArray(workspace.entries))
276
+ throw new Error("Effective workspace entries are invalid.");
277
+ uniqueIdentities(workspace.entries.map(({ projectId }) => projectId), "Effective workspace Project");
278
+ for (const entry of workspace.entries) {
279
+ identity(entry.projectId, "Effective workspace Project id");
280
+ identity(entry.directory, "Effective workspace Project directory");
281
+ if (entry.access !== "read" && entry.access !== "write") {
282
+ throw new Error("Effective workspace Project access is invalid.");
283
+ }
284
+ text(entry.path, "Effective workspace Project path");
285
+ text(entry.branch, "Effective workspace Project branch");
286
+ text(entry.baseRef, "Effective workspace Project base ref");
287
+ if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.baseCommit)) {
288
+ throw new Error("Effective workspace base commit is invalid.");
289
+ }
290
+ }
291
+ }
292
+ function validateDesiredRole(role) {
293
+ positiveInteger(role.launchRevision, "Role desired revision");
294
+ if (role.defaultAccess !== "read" && role.defaultAccess !== "write") {
295
+ throw new Error("Role default access is invalid.");
296
+ }
297
+ const binding = role.agentBindings[role.activeAgentId];
298
+ if (binding === undefined)
299
+ throw new Error("Role active Agent binding is missing.");
300
+ if (binding.adapterId !== "codex" && binding.adapterId !== "claude") {
301
+ throw new Error(`Role Agent adapter is unsupported: ${binding.adapterId}.`);
302
+ }
303
+ }
304
+ function uniqueIdentities(values, label) {
305
+ if (!Array.isArray(values))
306
+ throw new Error(`${label} list is invalid.`);
307
+ const result = [...new Set(values.map((value) => identity(value, label)))].sort();
308
+ if (result.length !== values.length)
309
+ throw new Error(`${label} list contains duplicates.`);
310
+ return result;
311
+ }
312
+ function positiveInteger(value, label) {
313
+ if (!Number.isSafeInteger(value) || value < 1)
314
+ throw new Error(`${label} must be positive.`);
315
+ return value;
316
+ }
317
+ function identity(value, label) {
318
+ const result = text(value, label);
319
+ if (["__proto__", "prototype", "constructor", ".", ".."].includes(result)
320
+ || /[/\\\0]/u.test(result)) {
321
+ throw new Error(`${label} is invalid.`);
322
+ }
323
+ return result;
324
+ }
325
+ function commit(value, label) {
326
+ const result = text(value, label).toLowerCase();
327
+ if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(result)) {
328
+ throw new Error(`${label} is invalid.`);
329
+ }
330
+ return result;
331
+ }
332
+ function text(value, label) {
333
+ if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
334
+ throw new Error(`${label} is required.`);
335
+ }
336
+ return value.trim();
337
+ }
338
+ function clone(value) {
339
+ return JSON.parse(JSON.stringify(value));
340
+ }