@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
@@ -59,7 +59,8 @@ function validateEnvironmentBinding(binding) {
59
59
  throw new Error("Agent environment binding must be an object.");
60
60
  }
61
61
  if (binding.source !== "process" || typeof binding.required !== "boolean"
62
- || !isEnvironmentName(binding.target) || !isEnvironmentName(binding.sourceName)) {
62
+ || !isEnvironmentName(binding.target) || !isEnvironmentName(binding.sourceName)
63
+ || binding.target.startsWith("YUI_")) {
63
64
  throw new Error("Agent environment binding is invalid.");
64
65
  }
65
66
  return {
@@ -14,7 +14,9 @@ const OWNED_ARGUMENTS_BY_ADAPTER = {
14
14
  "--setting-sources", "--worktree", "-w", "--tmux", "--print", "-p",
15
15
  "--agents", "--bg", "--background", "--dangerously-skip-permissions",
16
16
  "--allow-dangerously-skip-permissions", "--no-session-persistence", "--from-pr",
17
- "--fallback-model", "--tools"
17
+ "--fallback-model", "--tools", "--system-prompt", "--system-prompt-file",
18
+ "--append-system-prompt", "--append-system-prompt-file", "--plugin-dir",
19
+ "--name", "-n"
18
20
  ]
19
21
  };
20
22
  export function ownedArgumentsForAdapter(adapterId) {
@@ -0,0 +1,106 @@
1
+ import { resolveAgentEnvironment } from "./agent.js";
2
+ import { homedir, tmpdir } from "node:os";
3
+ import { dirname } from "node:path";
4
+ import { usableInteractiveTerminal } from "../output/terminal.js";
5
+ export { YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "./managedRuntimeEnvironment.js";
6
+ /**
7
+ * Non-secret process context needed by native Agent CLIs after tmux starts
8
+ * them with an empty environment. Keep this list explicit: arbitrary parent
9
+ * variables can contain credentials belonging to a different Agent.
10
+ */
11
+ export const AGENT_OPERATIONAL_ENVIRONMENT_NAMES = [
12
+ "PATH",
13
+ "HOME",
14
+ "USER",
15
+ "LOGNAME",
16
+ "SHELL",
17
+ "TMPDIR",
18
+ "TMP",
19
+ "TEMP",
20
+ "TMUX_TMPDIR",
21
+ "TERM",
22
+ "COLORTERM",
23
+ "LANG",
24
+ "LANGUAGE",
25
+ "LC_ALL",
26
+ "LC_ADDRESS",
27
+ "LC_COLLATE",
28
+ "LC_CTYPE",
29
+ "LC_IDENTIFICATION",
30
+ "LC_MEASUREMENT",
31
+ "LC_MESSAGES",
32
+ "LC_MONETARY",
33
+ "LC_NAME",
34
+ "LC_NUMERIC",
35
+ "LC_PAPER",
36
+ "LC_TELEPHONE",
37
+ "LC_TIME",
38
+ "TZ",
39
+ "XDG_CONFIG_HOME",
40
+ "XDG_CACHE_HOME",
41
+ "XDG_DATA_HOME",
42
+ "XDG_STATE_HOME",
43
+ "XDG_RUNTIME_DIR",
44
+ "HTTP_PROXY",
45
+ "HTTPS_PROXY",
46
+ "ALL_PROXY",
47
+ "NO_PROXY",
48
+ "http_proxy",
49
+ "https_proxy",
50
+ "all_proxy",
51
+ "no_proxy",
52
+ "SSL_CERT_FILE",
53
+ "SSL_CERT_DIR",
54
+ "NODE_EXTRA_CA_CERTS",
55
+ "CURL_CA_BUNDLE",
56
+ "REQUESTS_CA_BUNDLE",
57
+ "SSH_AUTH_SOCK"
58
+ ];
59
+ export const NATIVE_AGENT_ENVIRONMENT_NAMES = [
60
+ "CODEX_HOME",
61
+ "CLAUDE_CONFIG_DIR"
62
+ ];
63
+ export function nativeAgentEnvironmentNames(adapterId) {
64
+ switch (adapterId) {
65
+ case "codex": return ["CODEX_HOME"];
66
+ case "claude": return ["CLAUDE_CONFIG_DIR"];
67
+ }
68
+ }
69
+ export function selectEnvironment(source, names) {
70
+ const selected = {};
71
+ for (const name of names) {
72
+ const value = source[name];
73
+ if (value !== undefined)
74
+ selected[name] = value;
75
+ }
76
+ return selected;
77
+ }
78
+ function selectNonEmptyEnvironment(source, names) {
79
+ const selected = {};
80
+ for (const name of names) {
81
+ const value = source[name];
82
+ if (value !== undefined && value.length > 0)
83
+ selected[name] = value;
84
+ }
85
+ return selected;
86
+ }
87
+ export function operationalAgentEnvironment(adapterId, source) {
88
+ return {
89
+ ...selectNonEmptyEnvironment(source, [
90
+ ...AGENT_OPERATIONAL_ENVIRONMENT_NAMES,
91
+ ...nativeAgentEnvironmentNames(adapterId)
92
+ ]),
93
+ PATH: source.PATH
94
+ || `${dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`,
95
+ HOME: source.HOME || homedir(),
96
+ TERM: usableInteractiveTerminal(source.TERM),
97
+ TMPDIR: source.TMPDIR || tmpdir()
98
+ };
99
+ }
100
+ export function configuredAgentLaunchEnvironment(agent, source) {
101
+ const { SSH_AUTH_SOCK: _sshAgent, ...operational } = operationalAgentEnvironment(agent.adapterId, source);
102
+ return {
103
+ ...operational,
104
+ ...resolveAgentEnvironment(agent, source)
105
+ };
106
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Environment values owned by Yui's managed runtime.
3
+ *
4
+ * These values carry control-plane, Task, workspace, launch, or exact-action
5
+ * identity. They are never user Agent environment bindings, and an ordinary
6
+ * repository test must not inherit them from the managed Session that launched
7
+ * the test command.
8
+ */
9
+ export const YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES = Object.freeze([
10
+ "YUI_CLI_NAME",
11
+ "YUI_HOME",
12
+ "YUI_SESSION_SCOPE",
13
+ "YUI_TASK_ID",
14
+ "YUI_ROLE",
15
+ "YUI_AGENT_ID",
16
+ "YUI_ADAPTER_ID",
17
+ "YUI_WORKSPACE",
18
+ "YUI_RUN_ID",
19
+ "YUI_LAUNCH_ID",
20
+ "YUI_NATIVE_SESSION_ROOT",
21
+ "YUI_NATIVE_SESSION_ID",
22
+ "YUI_CONTROL_PLANE_DESCRIPTOR",
23
+ "YUI_TASK_RUNTIME_DESCRIPTOR",
24
+ "YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR",
25
+ "YUI_TASK_RUNTIME_SERVICE_NAMESPACE",
26
+ "YUI_SESSION_TITLE",
27
+ "YUI_AGENT_COMMAND",
28
+ "YUI_AGENT_BASE_ARGS",
29
+ "YUI_WRITABLE_PROJECT_IDS",
30
+ "YUI_CONTEXT_PROJECT_IDS",
31
+ "YUI_WORKSPACE_PROJECTS",
32
+ "YUI_LEADER_ACTION_RUN_ID",
33
+ "YUI_LEADER_ACTION_RECEIPT_ID"
34
+ ]);
@@ -1,8 +1,9 @@
1
1
  export function createTaskBrief(input, now) {
2
2
  return {
3
- schemaVersion: 1,
3
+ schemaVersion: 2,
4
4
  objective: requireText(input.objective, "Task objective"),
5
5
  boundaries: normalizeBoundaries(input.boundaries),
6
+ technicalApproach: optionalText(input.technicalApproach, "Task technical approach"),
6
7
  currentFocus: requireText(input.currentFocus, "Current focus"),
7
8
  leaderSummary: requireText(input.leaderSummary, "Leader summary"),
8
9
  updatedAt: now.toISOString(),
@@ -13,6 +14,7 @@ export function updateTaskBrief(brief, patch, updatedBy, now) {
13
14
  return createTaskBrief({
14
15
  objective: patch.objective ?? brief.objective,
15
16
  boundaries: patch.boundaries ?? brief.boundaries,
17
+ technicalApproach: patch.technicalApproach ?? brief.technicalApproach,
16
18
  currentFocus: patch.currentFocus ?? brief.currentFocus,
17
19
  leaderSummary: patch.leaderSummary ?? brief.leaderSummary,
18
20
  updatedBy
@@ -35,3 +37,11 @@ function requireText(value, label) {
35
37
  throw new Error(`${label} is required.`);
36
38
  return normalized;
37
39
  }
40
+ function optionalText(value, label) {
41
+ if (value === undefined)
42
+ return "";
43
+ if (typeof value !== "string" || value.includes("\0")) {
44
+ throw new Error(`${label} is invalid.`);
45
+ }
46
+ return value.trim();
47
+ }
@@ -0,0 +1,287 @@
1
+ import { configurationField, defaultModel, modelChoice } from "../executor/agentConfigurationCatalog.js";
2
+ import { renderTable } from "../output/table.js";
3
+ const DEFAULT = "\0yui:cli-default";
4
+ const CUSTOM = "\0yui:custom";
5
+ const OMIT = "\0yui:omit";
6
+ const COLUMNS = [
7
+ { header: "Value", minWidth: 12, maxWidth: 34 },
8
+ { header: "Details", minWidth: 16, maxWidth: 52 }
9
+ ];
10
+ export async function selectAgentModelAndEffort(resolved, io, current) {
11
+ renderResolutionNotice(resolved, io);
12
+ const model = await selectModel(resolved, io, current.currentModel);
13
+ if (model === null)
14
+ return { kind: "cancelled" };
15
+ const record = modelChoice(resolved.catalog, model);
16
+ const currentEffort = model === current.currentModel
17
+ ? current.currentEffort
18
+ : undefined;
19
+ const effort = await selectEffortValue(resolved, io, model, record, currentEffort);
20
+ return effort.cancelled
21
+ ? { kind: "cancelled" }
22
+ : { kind: "selected", model, effort: effort.value };
23
+ }
24
+ export async function selectAgentEffort(resolved, io, input) {
25
+ renderResolutionNotice(resolved, io);
26
+ const effort = await selectEffortValue(resolved, io, input.model, modelChoice(resolved.catalog, input.model), input.currentEffort);
27
+ return effort.cancelled
28
+ ? { kind: "cancelled" }
29
+ : { kind: "selected", effort: effort.value };
30
+ }
31
+ /** Select provider-native permission settings in the adapter's canonical shape. */
32
+ export async function selectAgentPermission(resolved, io, current) {
33
+ const strategyField = configurationField(resolved.catalog, "permission.strategy");
34
+ const strategyChoices = uniqueChoices(strategyField?.choices ?? [
35
+ { value: "default", label: "default" },
36
+ { value: "bypass", label: "bypass" },
37
+ { value: "configured", label: "configured" }
38
+ ], current.strategy);
39
+ const strategy = await choose("Select permission strategy", strategyChoices.map(({ value, label, description }) => ({
40
+ value,
41
+ label,
42
+ detail: description ?? value
43
+ })), io, current.strategy, "permission strategy");
44
+ if (strategy === undefined)
45
+ return { kind: "cancelled" };
46
+ if (strategy === "default" || strategy === "bypass") {
47
+ return { kind: "selected", permission: { strategy } };
48
+ }
49
+ if (resolved.catalog.adapterId === "codex") {
50
+ const codex = current.strategy === "configured"
51
+ ? current
52
+ : undefined;
53
+ const sandbox = await selectPermissionField(resolved, io, "permission.sandbox", codex?.sandbox, codex === undefined ? "workspace-write" : OMIT);
54
+ if (sandbox.kind === "cancelled")
55
+ return sandbox;
56
+ const approval = await selectPermissionField(resolved, io, "permission.approval", codex?.approval, codex === undefined ? "on-request" : OMIT);
57
+ if (approval.kind === "cancelled")
58
+ return approval;
59
+ const permission = {
60
+ ...(codex ?? { strategy: "configured" })
61
+ };
62
+ if (sandbox.value === undefined)
63
+ delete permission.sandbox;
64
+ else
65
+ permission.sandbox = sandbox.value;
66
+ if (approval.value === undefined)
67
+ delete permission.approval;
68
+ else
69
+ permission.approval = approval.value;
70
+ return {
71
+ kind: "selected",
72
+ permission: permission
73
+ };
74
+ }
75
+ const claude = current.strategy === "configured"
76
+ ? current
77
+ : undefined;
78
+ const mode = await selectPermissionField(resolved, io, "permission.mode", claude?.mode, claude === undefined ? undefined : OMIT);
79
+ if (mode.kind === "cancelled")
80
+ return mode;
81
+ const permission = {
82
+ ...(claude ?? { strategy: "configured" })
83
+ };
84
+ if (mode.value === undefined)
85
+ delete permission.mode;
86
+ else
87
+ permission.mode = mode.value;
88
+ return {
89
+ kind: "selected",
90
+ permission: permission
91
+ };
92
+ }
93
+ export function renderAgentConfigurationResolutionNotice(resolved) {
94
+ const lines = [];
95
+ if (resolved.source === "cache") {
96
+ lines.push(`! Runtime capability request failed (${resolved.failure?.message ?? "unknown failure"}). `
97
+ + `Showing cached options from ${resolved.fetchedAt ?? "an earlier request"}; they may be stale.`);
98
+ }
99
+ else if (resolved.source === "fallback") {
100
+ lines.push(`! Runtime capability request failed (${resolved.failure?.message ?? "unknown failure"}). `
101
+ + "No matching cache is available; only fallback and custom values can be offered.");
102
+ }
103
+ lines.push(...resolved.catalog.warnings.map((warning) => `! Agent catalog warning: ${warning}`));
104
+ return lines.length === 0 ? "" : `${lines.join("\n")}\n`;
105
+ }
106
+ async function selectModel(resolved, io, current) {
107
+ const catalogDefault = defaultModel(resolved.catalog);
108
+ const choices = [
109
+ {
110
+ value: DEFAULT,
111
+ label: "CLI default",
112
+ detail: catalogDefault === undefined
113
+ ? "Follow the Agent CLI default"
114
+ : `${catalogDefault.label}${catalogDefault.resolvedModel === undefined
115
+ ? "" : ` (${catalogDefault.resolvedModel})`}`,
116
+ aliases: ["default"]
117
+ },
118
+ ...resolved.catalog.models.map((model) => ({
119
+ value: model.value,
120
+ label: model.label,
121
+ detail: [
122
+ model.description ?? model.value,
123
+ model.isDefault ? "Reported CLI default model" : undefined
124
+ ].filter((part) => part !== undefined).join("; ")
125
+ })),
126
+ ...(current === undefined || resolved.catalog.models.some((model) => model.value === current)
127
+ ? []
128
+ : [{ value: current, label: current, detail: "Current value; not reported by this catalog" }]),
129
+ {
130
+ value: CUSTOM,
131
+ label: "Custom…",
132
+ detail: "Enter another model value",
133
+ aliases: ["custom"]
134
+ }
135
+ ];
136
+ const selected = await choose("Select model", choices, io, current === undefined ? DEFAULT : current, "model");
137
+ if (selected === undefined)
138
+ return null;
139
+ if (selected === DEFAULT)
140
+ return undefined;
141
+ if (selected !== CUSTOM)
142
+ return selected;
143
+ const custom = (await io.question("Custom model: "))?.trim();
144
+ return custom === undefined || custom.length === 0 ? null : custom;
145
+ }
146
+ async function selectEffortValue(resolved, io, model, record, current) {
147
+ const customModel = record === undefined && model !== undefined;
148
+ const efforts = customModel
149
+ ? observedEfforts(resolved.catalog.models)
150
+ : record?.efforts ?? [];
151
+ if (!customModel && record !== undefined && efforts.length === 0) {
152
+ io.write(`○ ${record.label} does not report configurable reasoning effort; using CLI default.\n`);
153
+ return { cancelled: false };
154
+ }
155
+ const choices = [
156
+ {
157
+ value: DEFAULT,
158
+ label: "CLI default",
159
+ detail: record?.defaultEffort === undefined
160
+ ? "Follow the selected model default"
161
+ : record.defaultEffort,
162
+ aliases: ["default"]
163
+ },
164
+ ...efforts.map((effort) => ({
165
+ value: effort.value,
166
+ label: effort.label,
167
+ detail: customModel
168
+ ? "Observed for another model; compatibility is unverified"
169
+ : effort.description ?? "Supported by selected model"
170
+ })),
171
+ ...(current === undefined || efforts.some((effort) => effort.value === current)
172
+ ? []
173
+ : [{ value: current, label: current, detail: "Current value; not reported for selected model" }]),
174
+ ...(!customModel && record !== undefined && record.efforts.length === 0
175
+ ? []
176
+ : [{
177
+ value: CUSTOM,
178
+ label: "Custom…",
179
+ detail: "Enter another effort value",
180
+ aliases: ["custom"]
181
+ }])
182
+ ];
183
+ const selected = await choose("Select reasoning effort", choices, io, current === undefined ? DEFAULT : current, "effort");
184
+ if (selected === undefined)
185
+ return { cancelled: true };
186
+ if (selected === DEFAULT)
187
+ return { cancelled: false };
188
+ if (selected !== CUSTOM)
189
+ return { cancelled: false, value: selected };
190
+ const custom = (await io.question("Custom reasoning effort: "))?.trim();
191
+ return custom === undefined || custom.length === 0
192
+ ? { cancelled: true }
193
+ : { cancelled: false, value: custom };
194
+ }
195
+ function observedEfforts(models) {
196
+ const observed = new Map();
197
+ for (const effort of models.flatMap((model) => model.efforts)) {
198
+ if (!observed.has(effort.value))
199
+ observed.set(effort.value, effort);
200
+ }
201
+ return [...observed.values()];
202
+ }
203
+ async function selectPermissionField(resolved, io, key, current, defaultValue) {
204
+ const field = configurationField(resolved.catalog, key);
205
+ const fallback = key === "permission.sandbox"
206
+ ? ["read-only", "workspace-write", "danger-full-access"]
207
+ : key === "permission.approval"
208
+ ? ["untrusted", "on-request", "never"]
209
+ : ["acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"];
210
+ const choices = uniqueChoices(field === undefined
211
+ ? fallback.map((value) => ({ value, label: value }))
212
+ : field.choices, current);
213
+ const includeOmit = defaultValue === OMIT || current !== undefined;
214
+ const pickerChoices = [
215
+ ...(includeOmit
216
+ ? [{ value: OMIT, label: "Omit", detail: "Do not pass this provider option" }]
217
+ : []),
218
+ ...choices.map(({ value, label, description }) => ({
219
+ value,
220
+ label,
221
+ detail: description ?? value
222
+ })),
223
+ ...(field?.allowCustom === true
224
+ ? [{ value: CUSTOM, label: "Custom…", detail: "Enter another provider value" }]
225
+ : [])
226
+ ];
227
+ const selected = await choose(`Select ${key}`, pickerChoices, io, current ?? defaultValue ?? pickerChoices[0]?.value ?? OMIT, key);
228
+ if (selected === undefined)
229
+ return { kind: "cancelled" };
230
+ if (selected === OMIT)
231
+ return { kind: "selected", value: undefined };
232
+ if (selected === CUSTOM) {
233
+ const custom = (await io.question(`Custom ${key}: `))?.trim();
234
+ return custom === undefined || custom.length === 0
235
+ ? { kind: "cancelled" }
236
+ : { kind: "selected", value: custom };
237
+ }
238
+ return { kind: "selected", value: selected };
239
+ }
240
+ function uniqueChoices(choices, current) {
241
+ const seen = new Set();
242
+ const result = [];
243
+ for (const choice of choices) {
244
+ if (seen.has(choice.value))
245
+ continue;
246
+ seen.add(choice.value);
247
+ result.push(choice);
248
+ }
249
+ if (current !== undefined && !seen.has(current)) {
250
+ result.push({
251
+ value: current,
252
+ label: current,
253
+ description: "Current value; not reported by this catalog"
254
+ });
255
+ }
256
+ return result;
257
+ }
258
+ function renderResolutionNotice(resolved, io) {
259
+ const notice = renderAgentConfigurationResolutionNotice(resolved);
260
+ if (notice.length > 0)
261
+ io.write(notice);
262
+ }
263
+ async function choose(title, choices, io, defaultValue, label) {
264
+ io.write(`${renderTable(title, [{ header: "#", minWidth: 1, maxWidth: 4 }, ...COLUMNS], choices.map((choice, index) => [
265
+ String(index + 1),
266
+ choice.label,
267
+ choice.detail
268
+ ]), io.width)}\n\n`);
269
+ const defaultLabel = choices.find((choice) => choice.value === defaultValue)?.label
270
+ ?? defaultValue;
271
+ const answer = (await io.question(`Choose ${label} [1-${choices.length}/value, q; default ${defaultLabel}]: `))?.trim();
272
+ if (answer === undefined || answer.toLowerCase() === "q" || answer.toLowerCase() === "quit") {
273
+ return undefined;
274
+ }
275
+ if (answer.length === 0) {
276
+ return choices.some((choice) => choice.value === defaultValue)
277
+ ? defaultValue
278
+ : choices[0]?.value;
279
+ }
280
+ const numeric = Number(answer);
281
+ if (Number.isSafeInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
282
+ return choices[numeric - 1]?.value;
283
+ }
284
+ return choices.find((choice) => choice.value === answer)?.value
285
+ ?? choices.find((choice) => choice.aliases?.includes(answer) === true)?.value
286
+ ?? (choices.some((choice) => choice.value === CUSTOM) ? answer : undefined);
287
+ }