@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
@@ -3,11 +3,19 @@ import { realpathSync, statSync } from "node:fs";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
5
5
  import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgentBaseArguments } from "../agent/argumentPolicy.js";
6
+ import { writeTextFileAtomically } from "../storage/durableFile.js";
7
+ import { inspectCodexDeveloperInstructions } from "./codexConfigConflict.js";
8
+ import { discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
9
+ import { preInputReadinessCapability } from "../lifecycle/providerLifecycleMapping.js";
6
10
  const SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
7
11
  const APPROVALS = ["untrusted", "on-request", "never"];
8
12
  const PROBE_TIMEOUT_MS = 2_000;
9
13
  const PROBE_MAX_BYTES = 1024 * 1024;
14
+ const CODEX_TESTED_THROUGH_VERSION = "0.145.0";
10
15
  class BaseAdapter {
16
+ launchContextArgs(_input) {
17
+ return [];
18
+ }
11
19
  validateConfig(input) {
12
20
  if (input.agent.adapterId !== this.id || input.config.adapterId !== this.id) {
13
21
  throw new Error(`Agent adapter identity mismatch: expected ${this.id}.`);
@@ -19,7 +27,12 @@ class BaseAdapter {
19
27
  this.validateConfig(input);
20
28
  const config = this.canonicalizeConfig(input.config);
21
29
  return {
22
- argv: [...input.agent.baseArgs, ...this.structuredArgs(config), ...(config.advanced?.rawArgs ?? [])],
30
+ argv: [
31
+ ...input.agent.baseArgs,
32
+ ...this.structuredArgs(config),
33
+ ...this.launchContextArgs(input),
34
+ ...(config.advanced?.rawArgs ?? [])
35
+ ],
23
36
  sessionStrategy: this.capabilities.nativeSessionDiscovery === "runtime"
24
37
  ? "runtime-discovery"
25
38
  : "preallocated"
@@ -40,7 +53,15 @@ class CodexAdapter extends BaseAdapter {
40
53
  id = "codex";
41
54
  label = "Codex";
42
55
  supportedVersion = "0.144.1";
43
- capabilities = { recover: true, interrupt: true, nativeSessionDiscovery: "runtime" };
56
+ capabilities = {
57
+ recover: true,
58
+ interrupt: true,
59
+ nativeSessionDiscovery: "runtime",
60
+ preInputReadiness: preInputReadinessCapability("codex")
61
+ };
62
+ discoverConfiguration(input) {
63
+ return discoverCodexConfiguration(input);
64
+ }
44
65
  validateStructured(config) {
45
66
  exact(config, ["adapterId", "model", "effort", "permission", "search", "profile",
46
67
  "additionalDirectories", "advanced"], "Codex Agent config");
@@ -53,33 +74,85 @@ class CodexAdapter extends BaseAdapter {
53
74
  throw new Error("Codex search must be boolean.");
54
75
  }
55
76
  validatePaths(config.additionalDirectories, "Codex additional directory");
56
- if (config.permission !== undefined) {
57
- exact(config.permission, ["sandbox", "approval"], "Codex permission config");
58
- if (config.permission.sandbox !== undefined && !SANDBOXES.includes(config.permission.sandbox)) {
77
+ if (config.permission === undefined) {
78
+ throw new Error("Codex permission strategy is required.");
79
+ }
80
+ if (config.permission.strategy === "configured") {
81
+ exact(config.permission, ["strategy", "sandbox", "approval"], "Codex permission config");
82
+ if (config.permission.sandbox !== undefined
83
+ && !SANDBOXES.includes(config.permission.sandbox)) {
59
84
  throw new Error("Codex sandbox is invalid.");
60
85
  }
61
- if (config.permission.approval !== undefined && !APPROVALS.includes(config.permission.approval)) {
86
+ if (config.permission.approval !== undefined
87
+ && !APPROVALS.includes(config.permission.approval)) {
62
88
  throw new Error("Codex approval is invalid.");
63
89
  }
90
+ requireConfiguredPermissionOption(config.permission, "Codex");
91
+ }
92
+ else {
93
+ exact(config.permission, ["strategy"], "Codex permission config");
94
+ validateSimplePermissionStrategy(config.permission.strategy, "Codex permission strategy");
64
95
  }
65
96
  advanced(this.id, config.advanced);
66
97
  }
67
98
  structuredArgs(config) {
68
99
  return [
69
- // Yui launches Codex in a detached tmux window and waits for the
70
- // composer before delivering work. The startup updater is itself an
71
- // interactive prompt, so leaving it enabled can consume the first
72
- // automated delivery as an update-menu answer.
100
+ // Yui launches Codex in a detached tmux window. Disable the startup
101
+ // updater so an unrelated native prompt cannot consume managed input.
73
102
  "--config", "check_for_update_on_startup=false",
74
103
  ...(config.model === undefined ? [] : ["--model", config.model]),
75
104
  ...(config.effort === undefined ? [] : ["--config", `model_reasoning_effort=\"${config.effort}\"`]),
76
- ...(config.permission?.sandbox === undefined ? [] : ["--sandbox", config.permission.sandbox]),
77
- ...(config.permission?.approval === undefined ? [] : ["--ask-for-approval", config.permission.approval]),
105
+ ...(config.permission.strategy === "bypass"
106
+ ? ["--dangerously-bypass-approvals-and-sandbox"]
107
+ : config.permission.strategy === "configured"
108
+ ? [
109
+ ...(config.permission.sandbox === undefined
110
+ ? [] : ["--sandbox", config.permission.sandbox]),
111
+ ...(config.permission.approval === undefined
112
+ ? [] : ["--ask-for-approval", config.permission.approval])
113
+ ]
114
+ : []),
78
115
  ...(config.search === true ? ["--search"] : []),
79
116
  ...(config.profile === undefined ? [] : ["--profile", config.profile]),
80
117
  ...(config.additionalDirectories ?? []).flatMap((path) => ["--add-dir", path])
81
118
  ];
82
119
  }
120
+ launchContextArgs(input) {
121
+ // Yui has already scoped and authorized this exact workspace. Declare that
122
+ // invocation-local trust explicitly so Codex does not place its interactive
123
+ // directory trust prompt in front of the managed first input. This does not
124
+ // mutate the user's Codex config or trust any parent/sibling directory.
125
+ const workspaceTrust = [
126
+ "--config",
127
+ `projects={${JSON.stringify(resolve(input.workspace))}={trust_level="trusted"}}`
128
+ ];
129
+ const instructions = [
130
+ input.developerInstructions,
131
+ ...(input.skills === undefined || input.skills.length === 0
132
+ ? []
133
+ : [
134
+ "Yui Role Skills are available at the paths below. Before performing work governed by one, read and follow its SKILL.md on demand; do not treat this list as a user message.",
135
+ ...input.skills.map((skill) => `- ${skill.id}: ${skill.path}/SKILL.md`)
136
+ ])
137
+ ].filter((value) => value !== undefined && value.trim().length > 0);
138
+ if (instructions.length === 0)
139
+ return workspaceTrust;
140
+ const nativeInstructions = input.codexDeveloperInstructions
141
+ ?? inspectCodexDeveloperInstructions({
142
+ workspace: input.workspace,
143
+ profile: input.config.profile,
144
+ trustWorkspace: true
145
+ });
146
+ if (nativeInstructions.status === "configured") {
147
+ throw new Error("Codex developer_instructions is already configured by "
148
+ + `${nativeInstructions.source}; Yui refuses to replace native developer instructions.`);
149
+ }
150
+ return [
151
+ ...workspaceTrust,
152
+ "--config",
153
+ `developer_instructions=${tomlString(instructions.join("\n"))}`
154
+ ];
155
+ }
83
156
  compileResume(input) {
84
157
  const launch = this.compileNew(input);
85
158
  return { ...launch, argv: [...launch.argv, "resume", nativeId(input.nativeSessionId)] };
@@ -89,7 +162,15 @@ class ClaudeAdapter extends BaseAdapter {
89
162
  id = "claude";
90
163
  label = "Claude";
91
164
  supportedVersion = "2.1.207";
92
- capabilities = { recover: true, interrupt: true, nativeSessionDiscovery: "preallocated" };
165
+ capabilities = {
166
+ recover: true,
167
+ interrupt: true,
168
+ nativeSessionDiscovery: "preallocated",
169
+ preInputReadiness: preInputReadinessCapability("claude")
170
+ };
171
+ discoverConfiguration(input) {
172
+ return discoverClaudeConfiguration(input);
173
+ }
93
174
  validateStructured(config) {
94
175
  exact(config, ["adapterId", "model", "effort", "permission", "additionalDirectories",
95
176
  "settingsFile", "settingsSources", "advanced"], "Claude Agent config");
@@ -104,16 +185,23 @@ class ClaudeAdapter extends BaseAdapter {
104
185
  if (config.settingsSources !== undefined && new Set(config.settingsSources).size !== config.settingsSources.length) {
105
186
  throw new Error("Claude settings sources contain duplicates.");
106
187
  }
107
- if (config.permission !== undefined) {
108
- exact(config.permission, ["mode", "allowedTools", "disallowedTools"], "Claude permission config");
188
+ if (config.permission === undefined) {
189
+ throw new Error("Claude permission strategy is required.");
190
+ }
191
+ if (config.permission.strategy === "configured") {
192
+ exact(config.permission, ["strategy", "mode", "allowedTools", "disallowedTools"], "Claude permission config");
109
193
  optionalText(config.permission.mode, "Claude permission mode");
110
194
  optionalTexts(config.permission.allowedTools, "Claude allowed tool");
111
195
  optionalTexts(config.permission.disallowedTools, "Claude disallowed tool");
112
- for (const tool of [...(config.permission.allowedTools ?? []), ...(config.permission.disallowedTools ?? [])]) {
113
- if (/(?:api[-_]?key|token|secret|password|credential|authorization|Bearer\s+\S+|sk-[\w-]{8,})/i.test(tool)) {
114
- throw new Error("Claude tool expressions cannot contain secret-bearing literals.");
115
- }
196
+ if (config.permission.allowedTools?.length === 0
197
+ || config.permission.disallowedTools?.length === 0) {
198
+ throw new Error("Claude configured tool lists must not be empty.");
116
199
  }
200
+ requireConfiguredPermissionOption(config.permission, "Claude");
201
+ }
202
+ else {
203
+ exact(config.permission, ["strategy"], "Claude permission config");
204
+ validateSimplePermissionStrategy(config.permission.strategy, "Claude permission strategy");
117
205
  }
118
206
  advanced(this.id, config.advanced);
119
207
  }
@@ -121,22 +209,72 @@ class ClaudeAdapter extends BaseAdapter {
121
209
  return [
122
210
  ...(config.model === undefined ? [] : ["--model", config.model]),
123
211
  ...(config.effort === undefined ? [] : ["--effort", config.effort]),
124
- ...(config.permission?.mode === undefined ? [] : ["--permission-mode", config.permission.mode]),
125
- ...(config.permission?.allowedTools === undefined ? [] : ["--allowed-tools", ...config.permission.allowedTools]),
126
- ...(config.permission?.disallowedTools === undefined ? [] : ["--disallowed-tools", ...config.permission.disallowedTools]),
212
+ ...(config.permission.strategy === "bypass"
213
+ ? ["--dangerously-skip-permissions"]
214
+ : config.permission.strategy === "configured"
215
+ && config.permission.mode !== undefined
216
+ ? ["--permission-mode", config.permission.mode]
217
+ : []),
218
+ ...(config.permission.strategy !== "configured"
219
+ || config.permission.allowedTools === undefined
220
+ ? [] : ["--allowed-tools", ...config.permission.allowedTools]),
221
+ ...(config.permission.strategy !== "configured"
222
+ || config.permission.disallowedTools === undefined
223
+ ? [] : ["--disallowed-tools", ...config.permission.disallowedTools]),
127
224
  ...(config.additionalDirectories ?? []).flatMap((path) => ["--add-dir", path]),
128
225
  ...(config.settingsFile === undefined ? [] : ["--settings", config.settingsFile]),
129
226
  ...(config.settingsSources === undefined ? [] : ["--setting-sources", config.settingsSources.join(",")])
130
227
  ];
131
228
  }
229
+ compileNew(input) {
230
+ const launch = super.compileNew(input);
231
+ return input.sessionTitle === undefined
232
+ ? launch
233
+ : {
234
+ ...launch,
235
+ argv: [...launch.argv, "--name", sessionTitle(input.sessionTitle)]
236
+ };
237
+ }
238
+ launchContextArgs(input) {
239
+ const sections = [
240
+ input.developerInstructions,
241
+ ...(input.skills ?? []).map((skill) => [
242
+ `# Yui Skill: ${skill.id}`,
243
+ skill.content
244
+ ].join("\n\n"))
245
+ ].filter((value) => value !== undefined && value.trim().length > 0);
246
+ if (sections.length === 0)
247
+ return [];
248
+ const context = sections.join("\n\n");
249
+ if (input.managedContextFile === undefined) {
250
+ throw new Error("Claude session context requires a managed context file under YUI_HOME.");
251
+ }
252
+ writeTextFileAtomically(input.managedContextFile, context);
253
+ return ["--append-system-prompt-file", input.managedContextFile];
254
+ }
132
255
  compileResume(input) {
133
- const launch = this.compileNew(input);
256
+ const launch = super.compileNew(input);
134
257
  return { ...launch, argv: [...launch.argv, "--resume", nativeId(input.nativeSessionId)] };
135
258
  }
136
259
  }
137
260
  const ADAPTERS = {
138
261
  codex: new CodexAdapter(), claude: new ClaudeAdapter()
139
262
  };
263
+ function tomlString(value) {
264
+ if (value.includes("\0"))
265
+ throw new Error("Agent launch context cannot contain NUL bytes.");
266
+ return JSON.stringify(value);
267
+ }
268
+ function sessionTitle(value) {
269
+ if (typeof value !== "string" || value.includes("\0")) {
270
+ throw new Error("Agent session title is invalid.");
271
+ }
272
+ const normalized = value.trim();
273
+ if (normalized.length === 0 || normalized.length > 1_024) {
274
+ throw new Error("Agent session title is invalid.");
275
+ }
276
+ return normalized;
277
+ }
140
278
  export { supportedAgentAdapterIds };
141
279
  export function findAgentAdapter(id) {
142
280
  return id === "codex" || id === "claude" ? ADAPTERS[id] : null;
@@ -171,24 +309,53 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
171
309
  reason: "Agent version probe did not return a semantic version.", probedAt: at
172
310
  }, baseline(agent.adapterId), at);
173
311
  }
174
- const supported = supports(version, adapter.supportedVersion);
312
+ const supported = supports(version, adapter);
175
313
  let fields = baseline(agent.adapterId);
176
- const warnings = supported ? [] : [`Installed version ${version} is not supported by adapter ${adapter.id}.`];
177
- if (supported) {
178
- const help = run(agent.command, ["--help"]);
179
- if (failed(help) === undefined)
180
- fields = fromHelp(agent.adapterId, output(help.stdout, help.stderr));
314
+ const warnings = [];
315
+ if (!supported) {
316
+ return snapshot(agent, adapter, {
317
+ status: "unsupported-version", command: agent.command, version,
318
+ reason: adapter.id === "codex"
319
+ ? `Minimum supported version is ${adapter.supportedVersion}.`
320
+ : `Supported version line starts at ${adapter.supportedVersion}.`,
321
+ probedAt: at
322
+ }, fields, at, [`Installed version ${version} is not supported by adapter ${adapter.id}.`]);
323
+ }
324
+ const help = run(agent.command, ["--help"]);
325
+ const helpFailure = failed(help);
326
+ if (adapter.id === "codex" && helpFailure !== undefined) {
327
+ return snapshot(agent, adapter, {
328
+ status: "probe-failed", command: agent.command, version,
329
+ reason: `Required Codex capability probe failed: ${helpFailure}`, probedAt: at
330
+ }, fields, at);
331
+ }
332
+ if (helpFailure === undefined) {
333
+ const helpOutput = output(help.stdout, help.stderr);
334
+ fields = fromHelp(agent.adapterId, helpOutput);
335
+ if (adapter.id === "codex") {
336
+ const missing = missingCodexCapabilities(helpOutput);
337
+ if (missing.length > 0) {
338
+ return snapshot(agent, adapter, {
339
+ status: "unsupported-version", command: agent.command, version,
340
+ reason: `Codex CLI is missing required capabilities: ${missing.join(", ")}.`,
341
+ probedAt: at
342
+ }, fields, at);
343
+ }
344
+ if (compareVersions(version, CODEX_TESTED_THROUGH_VERSION) > 0) {
345
+ warnings.push(`Installed Codex version ${version} is newer than the latest tested version `
346
+ + `${CODEX_TESTED_THROUGH_VERSION}; required capabilities were detected.`);
347
+ }
348
+ }
181
349
  }
182
350
  return snapshot(agent, adapter, {
183
- status: supported ? "installed" : "unsupported-version", command: agent.command, version,
184
- ...(supported ? {} : { reason: `Supported version line starts at ${adapter.supportedVersion}.` }),
185
- probedAt: at
351
+ status: "installed", command: agent.command, version, probedAt: at
186
352
  }, fields, at, warnings);
187
353
  }
188
354
  function baseline(id) {
189
355
  if (id === "codex")
190
356
  return [
191
357
  field("model", "enum", "degraded", true), field("effort", "enum", "unavailable", true),
358
+ field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
192
359
  field("permission.sandbox", "enum", "available", false, SANDBOXES),
193
360
  field("permission.approval", "enum", "available", false, APPROVALS),
194
361
  field("profile", "string", "available", true), field("search", "boolean", "available", false, ["true"]),
@@ -196,7 +363,9 @@ function baseline(id) {
196
363
  ];
197
364
  return [
198
365
  field("model", "enum", "degraded", true, ["fable", "opus", "sonnet"]),
199
- field("effort", "enum", "unavailable", true), field("permission.mode", "enum", "unavailable", true),
366
+ field("effort", "enum", "unavailable", true),
367
+ field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
368
+ field("permission.mode", "enum", "unavailable", true),
200
369
  field("permission.allowedTools", "string-list", "available", true),
201
370
  field("permission.disallowedTools", "string-list", "available", true),
202
371
  field("additionalDirectories", "path-list", "available", true), field("settingsFile", "path", "available", true),
@@ -206,15 +375,25 @@ function baseline(id) {
206
375
  function fromHelp(id, help) {
207
376
  const fields = baseline(id);
208
377
  const replacements = id === "codex"
209
- ? [choiceField("permission.sandbox", help, "--sandbox", SANDBOXES),
378
+ ? [permissionStrategyField(help, "--dangerously-bypass-approvals-and-sandbox"),
379
+ choiceField("permission.sandbox", help, "--sandbox", SANDBOXES),
210
380
  choiceField("permission.approval", help, "--ask-for-approval", APPROVALS)]
211
381
  : [choiceField("model", help, "--model", ["fable", "opus", "sonnet"], true),
212
382
  choiceField("effort", help, "--effort", [], true),
383
+ permissionStrategyField(help, "--dangerously-skip-permissions"),
213
384
  choiceField("permission.mode", help, "--permission-mode", [], true),
214
385
  choiceField("settingsSources", help, "--setting-sources", ["user", "project", "local"])];
215
386
  const byKey = new Map(replacements.map((value) => [value.key, value]));
216
387
  return fields.map((value) => byKey.get(value.key) ?? value);
217
388
  }
389
+ function permissionStrategyField(help, bypassFlag) {
390
+ const choices = [
391
+ "default",
392
+ ...(help.includes(bypassFlag) ? ["bypass"] : []),
393
+ "configured"
394
+ ];
395
+ return field("permission.strategy", "enum", choices.includes("bypass") ? "available" : "degraded", false, choices);
396
+ }
218
397
  function choiceField(key, help, flag, fallback, custom = false) {
219
398
  const choices = helpChoices(help, flag);
220
399
  return field(key, key === "settingsSources" ? "string-list" : "enum", choices.length > 0 ? "available" : fallback.length > 0 ? "degraded" : "unavailable", custom, choices.length > 0 ? choices : fallback);
@@ -238,7 +417,7 @@ function helpChoices(help, flag) {
238
417
  function snapshot(agent, adapter, installation, fields, at, warnings = []) {
239
418
  return { schemaVersion: 1, agentId: agent.id, adapterId: agent.adapterId, installation,
240
419
  lifecycle: { start: true, resume: true, nativeSessionDiscovery: adapter.capabilities.nativeSessionDiscovery,
241
- interrupt: true }, fields, warnings, refreshedAt: at };
420
+ interrupt: true, preInputReadiness: adapter.capabilities.preInputReadiness }, fields, warnings, refreshedAt: at };
242
421
  }
243
422
  function runProbe(command, args) {
244
423
  const result = spawnSync(command, [...args], { encoding: "utf8", shell: false, timeout: PROBE_TIMEOUT_MS,
@@ -258,25 +437,61 @@ function output(stdout, stderr) {
258
437
  throw new Error("Agent probe output exceeded 1 MiB.");
259
438
  return value;
260
439
  }
261
- function supports(version, baseline) {
262
- const left = version.split(".").map(Number), right = baseline.split(".").map(Number);
440
+ function supports(version, adapter) {
441
+ if (adapter.id === "codex")
442
+ return compareVersions(version, adapter.supportedVersion) >= 0;
443
+ const left = version.split(".").map(Number), right = adapter.supportedVersion.split(".").map(Number);
263
444
  return left[0] === right[0] && left[1] === right[1] && left[2] >= right[2];
264
445
  }
446
+ function compareVersions(leftVersion, rightVersion) {
447
+ const left = leftVersion.split(".").map(Number);
448
+ const right = rightVersion.split(".").map(Number);
449
+ for (let index = 0; index < 3; index += 1) {
450
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
451
+ if (difference !== 0)
452
+ return difference;
453
+ }
454
+ return 0;
455
+ }
456
+ function missingCodexCapabilities(help) {
457
+ const required = [
458
+ [/(?:^|\s)--config(?:\s|[=<,]|$)/m, "--config"],
459
+ [/^\s*resume(?:\s|$)/m, "resume"]
460
+ ];
461
+ return required.flatMap(([pattern, label]) => pattern.test(help) ? [] : [label]);
462
+ }
265
463
  function cloneConfig(config, paths) {
266
464
  const advancedConfig = config.advanced?.rawArgs === undefined ? config.advanced : { rawArgs: [...config.advanced.rawArgs] };
267
465
  if (config.adapterId === "codex")
268
466
  return { ...config,
269
- ...(config.permission === undefined ? {} : { permission: { ...config.permission } }),
467
+ permission: { ...config.permission },
270
468
  ...(paths === undefined ? {} : { additionalDirectories: [...paths] }),
271
469
  ...(advancedConfig === undefined ? {} : { advanced: advancedConfig }) };
272
470
  return { ...config,
273
- ...(config.permission === undefined ? {} : { permission: { ...config.permission,
471
+ permission: config.permission.strategy === "configured"
472
+ ? { ...config.permission,
274
473
  ...(config.permission.allowedTools === undefined ? {} : { allowedTools: [...config.permission.allowedTools] }),
275
- ...(config.permission.disallowedTools === undefined ? {} : { disallowedTools: [...config.permission.disallowedTools] }) } }),
474
+ ...(config.permission.disallowedTools === undefined ? {} : { disallowedTools: [...config.permission.disallowedTools] }) }
475
+ : { ...config.permission },
276
476
  ...(paths === undefined ? {} : { additionalDirectories: [...paths] }),
277
477
  ...(config.settingsSources === undefined ? {} : { settingsSources: [...config.settingsSources] }),
278
478
  ...(advancedConfig === undefined ? {} : { advanced: advancedConfig }) };
279
479
  }
480
+ export function defaultRoleAgentConfig(adapterId) {
481
+ return adapterId === "codex"
482
+ ? { adapterId: "codex", permission: { strategy: "bypass" } }
483
+ : { adapterId: "claude", permission: { strategy: "bypass" } };
484
+ }
485
+ function validateSimplePermissionStrategy(value, label) {
486
+ if (value !== "default" && value !== "bypass") {
487
+ throw new Error(`${label} is invalid.`);
488
+ }
489
+ }
490
+ function requireConfiguredPermissionOption(permission, provider) {
491
+ if (Object.keys(permission).every((key) => key === "strategy")) {
492
+ throw new Error(`${provider} configured permission requires at least one provider-native option.`);
493
+ }
494
+ }
280
495
  function advanced(id, value) {
281
496
  if (value === undefined)
282
497
  return;