@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,4 +1,5 @@
1
1
  import { renderTable } from "../output/table.js";
2
+ import { selectAgentEffort, selectAgentModelAndEffort } from "./agentConfigurationPicker.js";
2
3
  const TEXT_COLUMN = { header: "Selection", minWidth: 12, maxWidth: 34 };
3
4
  export async function resolveRoleWizardArguments(commandArgs, ports, io) {
4
5
  const args = [...commandArgs];
@@ -18,6 +19,17 @@ export async function resolveRoleWizardArguments(commandArgs, ports, io) {
18
19
  }
19
20
  return { kind: "unchanged", args };
20
21
  }
22
+ export async function resolveGlobalRoleAgentConfigurationArguments(roleName, agentId, ports, io) {
23
+ const args = ["role", "update", roleName];
24
+ if (!io.interactive || io.json)
25
+ return { kind: "unchanged", args };
26
+ const role = asRole(await ports.call("role.show", { name: roleName }));
27
+ const binding = role?.agentBindings[agentId];
28
+ if (role === undefined || binding === undefined) {
29
+ return { kind: "cancelled", args };
30
+ }
31
+ return updateAgentBindingSettings(args, role, binding, ports, io);
32
+ }
21
33
  function isGlobalRoleAdd(args) {
22
34
  return args[0] === "role" && args[1] === "add";
23
35
  }
@@ -146,7 +158,7 @@ async function configureRoleCreation(args, agent, ports, io, taskRole) {
146
158
  return { kind: "cancelled", args };
147
159
  return section === "role"
148
160
  ? configureNewRoleField(args, agent, ports, io, taskRole)
149
- : configureNewAgentField(args, agent, io);
161
+ : configureNewAgentField(args, agent, ports, io);
150
162
  }
151
163
  async function configureNewRoleField(args, agent, ports, io, taskRole) {
152
164
  const field = await choose("Role settings", [
@@ -175,16 +187,49 @@ async function configureNewRoleField(args, agent, ports, io, taskRole) {
175
187
  void agent;
176
188
  return setProfileField(args, field, io);
177
189
  }
178
- async function configureNewAgentField(args, agent, io) {
179
- const fields = agentFields({
190
+ async function configureNewAgentField(args, agent, ports, io) {
191
+ const binding = {
180
192
  agentId: agent.id,
181
193
  adapterId: agent.adapterId,
182
- config: { adapterId: agent.adapterId }
183
- });
194
+ config: {
195
+ adapterId: agent.adapterId,
196
+ permission: { strategy: "bypass" }
197
+ }
198
+ };
199
+ const resolved = await loadAgentCatalog(ports, binding);
200
+ const fields = agentFields(binding, resolved?.catalog);
184
201
  const field = await choose(`Agent settings: ${agent.id}`, fields.map((candidate) => ({ value: candidate.value, cells: [candidate.label, "CLI default"] })), [TEXT_COLUMN, { header: "Current", minWidth: 12, maxWidth: 24 }], io, undefined, "field");
185
202
  const selected = fields.find((candidate) => candidate.value === field);
186
203
  if (selected === undefined)
187
204
  return { kind: "cancelled", args };
205
+ if (selected.value === "model") {
206
+ if (resolved === undefined)
207
+ return { kind: "cancelled", args };
208
+ const selection = await selectAgentModelAndEffort(resolved, io, {});
209
+ return selection.kind === "cancelled"
210
+ ? { kind: "cancelled", args }
211
+ : { kind: "resolved", args: appendModelEffortPatch(args, selection, false) };
212
+ }
213
+ if (selected.value === "effort") {
214
+ if (resolved === undefined)
215
+ return { kind: "cancelled", args };
216
+ const selection = await selectAgentEffort(resolved, io, {});
217
+ return selection.kind === "cancelled"
218
+ ? { kind: "cancelled", args }
219
+ : {
220
+ kind: "resolved",
221
+ args: selection.effort === undefined ? args : [...args, "--effort", selection.effort]
222
+ };
223
+ }
224
+ if (selected.value === "permission-strategy") {
225
+ const strategy = await promptAgentFieldValue(selected, io);
226
+ if (strategy === undefined || strategy.length === 0)
227
+ return { kind: "cancelled", args };
228
+ const permissionArgs = await configuredPermissionArgs(binding, resolved?.catalog, strategy, io);
229
+ return permissionArgs === undefined
230
+ ? { kind: "cancelled", args }
231
+ : { kind: "resolved", args: [...args, ...permissionArgs] };
232
+ }
188
233
  const value = await promptAgentFieldValue(selected, io);
189
234
  return value === undefined
190
235
  ? { kind: "cancelled", args }
@@ -211,7 +256,7 @@ async function updateRole(args, role, ports, io, taskRole) {
211
256
  return { kind: "cancelled", args };
212
257
  return section === "role"
213
258
  ? updateRoleSettings(args, role, ports, io, taskRole)
214
- : updateAgentSettings(args, role, io);
259
+ : updateAgentSettings(args, role, ports, io);
215
260
  }
216
261
  async function settingsSection(title, io, taskRole) {
217
262
  return choose(title, [
@@ -281,7 +326,7 @@ async function setProfileField(args, field, io) {
281
326
  args: [...args, ...values.flatMap((entry) => [options.set, entry])]
282
327
  };
283
328
  }
284
- async function updateAgentSettings(args, role, io) {
329
+ async function updateAgentSettings(args, role, ports, io) {
285
330
  const bindings = Object.values(role.agentBindings);
286
331
  const selectedId = await choose("Select Role Agent binding", bindings.map((binding) => ({
287
332
  value: binding.agentId,
@@ -296,7 +341,11 @@ async function updateAgentSettings(args, role, io) {
296
341
  const binding = role.agentBindings[selectedId];
297
342
  if (binding === undefined)
298
343
  return { kind: "cancelled", args };
299
- const fields = agentFields(binding);
344
+ return updateAgentBindingSettings(args, role, binding, ports, io);
345
+ }
346
+ async function updateAgentBindingSettings(args, role, binding, ports, io) {
347
+ const resolved = await loadAgentCatalog(ports, binding, role.workspace);
348
+ const fields = agentFields(binding, resolved?.catalog);
300
349
  const field = await choose(`Agent settings: ${binding.agentId}`, [
301
350
  ...fields.map((candidate) => ({
302
351
  value: candidate.value,
@@ -315,6 +364,51 @@ async function updateAgentSettings(args, role, io) {
315
364
  const selectedField = fields.find((candidate) => candidate.value === field);
316
365
  if (selectedField === undefined)
317
366
  return { kind: "cancelled", args };
367
+ if (selectedField.value === "model") {
368
+ if (resolved === undefined)
369
+ return { kind: "cancelled", args };
370
+ const selection = await selectAgentModelAndEffort(resolved, io, {
371
+ currentModel: stringField(binding.config, "model"),
372
+ currentEffort: stringField(binding.config, "effort")
373
+ });
374
+ return selection.kind === "cancelled"
375
+ ? { kind: "cancelled", args }
376
+ : {
377
+ kind: "resolved",
378
+ args: appendModelEffortPatch([...args, "--agent", binding.agentId], selection, true)
379
+ };
380
+ }
381
+ if (selectedField.value === "effort") {
382
+ if (resolved === undefined)
383
+ return { kind: "cancelled", args };
384
+ const selection = await selectAgentEffort(resolved, io, {
385
+ model: stringField(binding.config, "model"),
386
+ currentEffort: stringField(binding.config, "effort")
387
+ });
388
+ if (selection.kind === "cancelled")
389
+ return { kind: "cancelled", args };
390
+ return {
391
+ kind: "resolved",
392
+ args: [
393
+ ...args,
394
+ "--agent", binding.agentId,
395
+ ...(selection.effort === undefined
396
+ ? ["--clear-effort"] : ["--effort", selection.effort])
397
+ ]
398
+ };
399
+ }
400
+ if (selectedField.value === "permission-strategy") {
401
+ const strategy = await promptAgentFieldValue(selectedField, io);
402
+ if (strategy === undefined || strategy.length === 0)
403
+ return { kind: "cancelled", args };
404
+ const permissionArgs = await configuredPermissionArgs(binding, resolved?.catalog, strategy, io);
405
+ if (permissionArgs === undefined)
406
+ return { kind: "cancelled", args };
407
+ return {
408
+ kind: "resolved",
409
+ args: [...args, "--agent", binding.agentId, ...permissionArgs]
410
+ };
411
+ }
318
412
  const action = await choose(`Update ${selectedField.label}`, [
319
413
  { value: "keep", cells: ["Keep current", selectedField.current] },
320
414
  { value: "clear", cells: ["Use CLI default", "Remove override"] },
@@ -325,7 +419,7 @@ async function updateAgentSettings(args, role, io) {
325
419
  if (action === "clear") {
326
420
  return {
327
421
  kind: "resolved",
328
- args: [...args, "--agent", binding.agentId, selectedField.clear]
422
+ args: [...args, "--agent", binding.agentId, ...selectedField.clear]
329
423
  };
330
424
  }
331
425
  const value = await promptAgentFieldValue(selectedField, io);
@@ -336,31 +430,101 @@ async function updateAgentSettings(args, role, io) {
336
430
  args: [...args, "--agent", binding.agentId, selectedField.set, value]
337
431
  };
338
432
  }
339
- function agentFields(binding) {
433
+ function agentFields(binding, catalog) {
340
434
  const config = binding.config;
341
435
  const permission = entity(config.permission) ?? {};
342
436
  return [
343
- agentField("model", "Model", config.model, "--model", "--clear-model"),
344
- agentField("effort", "Effort", config.effort, "--effort", "--clear-effort"),
345
- ...(binding.adapterId === "codex" ? [
346
- agentField("sandbox", "Sandbox", permission.sandbox, "--sandbox", "--clear-sandbox", [
437
+ agentField("model", "Model", config.model, "--model", ["--clear-model"]),
438
+ agentField("effort", "Effort", config.effort, "--effort", ["--clear-effort"]),
439
+ agentField("permission-strategy", "Permission strategy", permission.strategy, "--permission-strategy", ["--permission-strategy", "default"], catalogChoices(catalog, "permission.strategy", ["default", "bypass", "configured"])),
440
+ ...(binding.adapterId === "codex" && permission.strategy === "configured" ? [
441
+ agentField("sandbox", "Sandbox", permission.sandbox, "--sandbox", ["--permission-strategy", "default"], catalogChoices(catalog, "permission.sandbox", [
347
442
  "read-only", "workspace-write", "danger-full-access"
348
- ]),
349
- agentField("approval", "Approval", permission.approval, "--approval", "--clear-approval", [
443
+ ])),
444
+ agentField("approval", "Approval", permission.approval, "--approval", ["--permission-strategy", "default"], catalogChoices(catalog, "permission.approval", [
350
445
  "untrusted", "on-request", "never"
351
- ]),
352
- agentField("search", "Web search", config.search, "--search", "--clear-search", [
353
- "true"
446
+ ]))
447
+ ] : []),
448
+ ...(binding.adapterId === "codex" ? [
449
+ agentField("search", "Web search", config.search, "--search", ["--clear-search"], [
450
+ ...catalogChoices(catalog, "search", ["true"])
354
451
  ])
355
452
  ] : []),
356
- ...(binding.adapterId === "claude" ? [
357
- agentField("permission-mode", "Permission mode", permission.mode, "--permission-mode", "--clear-permission-mode")
453
+ ...(binding.adapterId === "claude" && permission.strategy === "configured" ? [
454
+ agentField("permission-mode", "Permission mode", permission.mode, "--permission-mode", ["--permission-strategy", "default"], catalogChoices(catalog, "permission.mode"))
358
455
  ] : [])
359
456
  ];
360
457
  }
458
+ async function loadAgentCatalog(ports, binding, cwd) {
459
+ const value = await ports.call("agent.capabilities", {
460
+ agentId: binding.agentId,
461
+ config: binding.config,
462
+ ...(cwd === undefined ? {} : { cwd })
463
+ });
464
+ const input = entity(value);
465
+ return input !== undefined
466
+ && (input.source === "live" || input.source === "cache" || input.source === "fallback")
467
+ && entity(input.catalog) !== undefined
468
+ ? value
469
+ : undefined;
470
+ }
471
+ function catalogChoices(catalog, key, fallback = []) {
472
+ const field = catalog?.fields.find((candidate) => candidate.key === key);
473
+ return field === undefined ? [...fallback] : field.choices.map(({ value }) => value);
474
+ }
475
+ function appendModelEffortPatch(args, selection, update) {
476
+ return [
477
+ ...args,
478
+ ...(selection.model === undefined
479
+ ? update ? ["--clear-model"] : []
480
+ : ["--model", selection.model]),
481
+ ...(selection.effort === undefined
482
+ ? update ? ["--clear-effort"] : []
483
+ : ["--effort", selection.effort])
484
+ ];
485
+ }
361
486
  function agentField(value, label, current, set, clear, choices) {
362
487
  return { value, label, current: display(current), set, clear, ...(choices === undefined ? {} : { choices }) };
363
488
  }
489
+ async function configuredPermissionArgs(binding, catalog, strategy, io) {
490
+ if (strategy !== "configured")
491
+ return ["--permission-strategy", strategy];
492
+ if (binding.adapterId === "codex") {
493
+ const field = await choose("Native permission option", [
494
+ { value: "sandbox", cells: ["Sandbox"] },
495
+ { value: "approval", cells: ["Approval"] }
496
+ ], [TEXT_COLUMN], io, "sandbox", "option");
497
+ if (field === undefined)
498
+ return undefined;
499
+ const option = field === "sandbox" ? "--sandbox" : "--approval";
500
+ const value = await promptAgentFieldValue(agentField(field, field === "sandbox" ? "Sandbox" : "Approval", undefined, option, ["--permission-strategy", "default"], field === "sandbox"
501
+ ? catalogChoices(catalog, "permission.sandbox", [
502
+ "read-only", "workspace-write", "danger-full-access"
503
+ ])
504
+ : catalogChoices(catalog, "permission.approval", [
505
+ "untrusted", "on-request", "never"
506
+ ])), io);
507
+ return value === undefined
508
+ ? undefined
509
+ : ["--permission-strategy", "configured", option, value];
510
+ }
511
+ const field = await choose("Native permission option", [
512
+ { value: "mode", cells: ["Permission mode"] },
513
+ { value: "allowed", cells: ["Allowed tool"] },
514
+ { value: "disallowed", cells: ["Disallowed tool"] }
515
+ ], [TEXT_COLUMN], io, "mode", "option");
516
+ if (field === undefined)
517
+ return undefined;
518
+ const option = field === "mode"
519
+ ? "--permission-mode"
520
+ : field === "allowed" ? "--allowed-tool" : "--disallowed-tool";
521
+ const value = field === "mode"
522
+ ? await promptAgentFieldValue(agentField("permission-mode", "Permission mode", undefined, option, ["--permission-strategy", "default"], catalogChoices(catalog, "permission.mode")), io)
523
+ : (await io.question(`${field === "allowed" ? "Allowed" : "Disallowed"} tool: `))?.trim();
524
+ return value === undefined || value.length === 0
525
+ ? undefined
526
+ : ["--permission-strategy", "configured", option, value];
527
+ }
364
528
  async function promptAgentFieldValue(field, io) {
365
529
  return field.choices === undefined
366
530
  ? (await io.question(`${field.label}: `))?.trim()
@@ -1,22 +1,65 @@
1
- import { spawnSync } from "node:child_process";
2
- import { runtimeError } from "../errors/cliError.js";
1
+ import { resolveYuiHome } from "../storage/taskStore.js";
2
+ import { createUpdatePorts } from "./updatePorts.js";
3
+ import { runUpdate } from "./updateOrchestrator.js";
3
4
  /**
4
- * Update the published global package. This intentionally remains an exact,
5
- * shell-free npm invocation. In particular, yui-dev updates the published
6
- * Yui installation; it never rewrites the current checkout.
5
+ * Run `yui update` as a side-by-side, recoverable orchestration.
6
+ *
7
+ * The new package is staged beside the live install and used to run a read-only
8
+ * preflight against the target Home. Current/compatible-old storage promotes the
9
+ * binary through the exact Controller handoff without a Home switch; only a
10
+ * migration-required verdict enters the offline backup/switch path. On failure
11
+ * it reports the precise phase and recovery boundary. See
12
+ * {@link runUpdate} for the exact, narrowed rollback boundary (this release does
13
+ * not introduce a versioned binary pointer, so it does not claim binary+Home
14
+ * dual-resource atomicity).
15
+ *
16
+ * Returns a process exit code: 0 on success or already-current, 5 on abort.
7
17
  */
8
- export function runUpdateCommand(spawn = spawnSync) {
9
- const result = spawn("npm", ["install", "--global", "@zq-silk/yui@latest"], {
10
- cwd: process.cwd(),
11
- env: process.env,
12
- shell: false,
13
- stdio: "inherit"
14
- });
15
- if (result.error !== undefined) {
16
- throw runtimeError(`Failed to start npm: ${result.error.message}`);
17
- }
18
- if (result.status === null) {
19
- throw runtimeError(`npm update terminated${result.signal === null ? "" : ` by ${result.signal}`}.`);
20
- }
21
- return result.status;
18
+ export function runUpdateCommand(environment = process.env, spawn, write = (text) => process.stdout.write(text), ports) {
19
+ const home = resolveYuiHome(environment);
20
+ const resolvedPorts = ports ?? createUpdatePorts(environment, spawn);
21
+ const result = runUpdate(resolvedPorts, { home });
22
+ write(`${renderUpdateResult(result)}\n`);
23
+ // Both an abort and an ambiguous result are non-success exits; ambiguous is
24
+ // distinct (manual verification required), so it gets its own exit code.
25
+ if (result.outcome === "aborted")
26
+ return 5;
27
+ if (result.outcome === "ambiguous")
28
+ return 6;
29
+ return 0;
30
+ }
31
+ /** Render an {@link UpdateResult} as concise, CLI-style text. */
32
+ export function renderUpdateResult(result) {
33
+ const rendered = (() => {
34
+ switch (result.outcome) {
35
+ case "already-current":
36
+ return "Yui is already up to date; nothing to install.";
37
+ case "updated":
38
+ return result.storageBackupPath === undefined
39
+ ? result.path === "compatible-fast"
40
+ ? `Updated Yui to ${result.version} via the compatible fast path; the Home was not migrated.`
41
+ : `Updated Yui to ${result.version}; storage was already current.`
42
+ : `Updated Yui to ${result.version}. Storage was migrated; original Home backed up at `
43
+ + `${result.storageBackupPath}.`;
44
+ case "aborted":
45
+ return [
46
+ `Update aborted during ${result.phase}: ${result.message}`,
47
+ result.recoverable
48
+ ? "The current install and Home remain usable."
49
+ : result.phase === "activate-binary" && result.storageBackupPath === undefined
50
+ ? "The Home was not migrated, but binary health is unknown; do not assume the current install is usable."
51
+ : "Manual recovery is required (see below).",
52
+ `Action: ${result.action}`
53
+ ].join("\n");
54
+ case "ambiguous":
55
+ return [
56
+ `Update result is UNKNOWN after ${result.phase}: ${result.message}`,
57
+ "Manual verification is required — do NOT assume the update succeeded or was a no-op.",
58
+ `Action: ${result.action}`
59
+ ].join("\n");
60
+ }
61
+ })();
62
+ return result.cleanupWarning === undefined
63
+ ? rendered
64
+ : `${rendered}\nWarning: ${result.cleanupWarning}`;
22
65
  }