@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,35 +1,131 @@
1
- import { accessSync, constants, existsSync, lstatSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { configuredAgentToDefinition } from "../agent/agent.js";
4
- import { inspectAgentCapabilities } from "../executor/agentAdapter.js";
1
+ import { accessSync, constants, existsSync, lstatSync, readFileSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
4
+ import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
5
+ import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
6
+ import { inspectCodexLaunchConfig } from "../executor/codexConfigConflict.js";
7
+ import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
8
+ import { compileRoleSessionContext } from "../context/roleSessionContext.js";
5
9
  import { usageError } from "../errors/cliError.js";
6
10
  import { defaultTableWidth, renderTable } from "../output/table.js";
7
- import { FileTaskStore, resolveYuiHome, STORAGE_STATE_FILE } from "../storage/taskStore.js";
11
+ import { resolveYuiHome, STORAGE_STATE_FILE } from "../storage/taskStore.js";
12
+ import { createProductionStorageRegistry, openCompatibleFileTaskStore, validateCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
8
13
  import { inspectStorageSchema } from "../storage/storageSchema.js";
14
+ import { classifyHome } from "../storage/upgrade/homeClassification.js";
15
+ import { latestStorageVersionState } from "../storage/upgrade/recordVersions.js";
9
16
  import { CommandExecutionError } from "../tmux/commandExecutor.js";
17
+ import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
18
+ /**
19
+ * The storage-relevant doctor checks, in the order they are produced. Post-update
20
+ * verification (P1-3) keys off exactly these: a green `yui doctor` for the update
21
+ * flow means every one of these is `ok`. Tool/agent checks are diagnostic only
22
+ * and never block an update.
23
+ */
24
+ export const STORAGE_DOCTOR_CHECK_NAMES = Object.freeze([
25
+ "storage schema",
26
+ "storage compatibility",
27
+ "storage state"
28
+ ]);
29
+ /**
30
+ * Classify the storage checks into a machine-readable health verdict. This is the
31
+ * canonical, parseable signal the update post-verify consumes (P1-3): storage is
32
+ * healthy only when schema, compatibility, and state are all `ok`. Any
33
+ * `unsupported` (version mismatch / needs-new-version), `invalid` (corrupted /
34
+ * unreadable), or `missing` (uninitialized) storage check is blocking — even
35
+ * though `yui doctor` itself exits 0.
36
+ */
37
+ export function summarizeStorageHealth(checks) {
38
+ const names = new Set(STORAGE_DOCTOR_CHECK_NAMES);
39
+ const blocking = checks.filter((check) => names.has(check.name) && check.status !== "ok");
40
+ return { healthy: blocking.length === 0, blocking };
41
+ }
42
+ /** Build the full machine-readable doctor report (checks + storage health). */
43
+ export function buildDoctorReport(env, executor, storageOptions = {}) {
44
+ const inspection = inspectDoctor(env, executor, storageOptions);
45
+ return {
46
+ checks: inspection.checks,
47
+ storage: summarizeStorageHealth(inspection.checks),
48
+ review: inspection.review
49
+ };
50
+ }
10
51
  /** Runs the read-only FileTaskStore diagnostics used by `yui doctor`. */
11
- export function runDoctorCommand(args, env, executor) {
52
+ export function runDoctorCommand(args, env, executor, storageOptions = {}) {
12
53
  if (args.length !== 0)
13
54
  throw usageError("Doctor usage: yui doctor");
14
- return renderDoctor(getDoctorChecks(env, executor));
55
+ const inspection = inspectDoctor(env, executor, storageOptions);
56
+ return renderDoctor(inspection.checks, inspection.review);
57
+ }
58
+ export function getDoctorChecks(env, executor, storageOptions = {}) {
59
+ return inspectDoctor(env, executor, storageOptions).checks;
15
60
  }
16
- export function getDoctorChecks(env, executor) {
61
+ function inspectDoctor(env, executor, storageOptions = {}) {
17
62
  const home = resolveYuiHome(env);
18
63
  const homeCheck = checkHome(home);
19
64
  const schema = readSchema(home);
20
- const schemaCheck = checkSchema(schema);
21
- const storage = inspectState(home, homeCheck, schema);
22
- return [
23
- homeCheck,
24
- schemaCheck,
25
- storage.check,
65
+ const compatibility = inspectCompatibility(home, homeCheck, schema, storageOptions);
66
+ const schemaCheck = checkSchema(schema, compatibility);
67
+ const storage = inspectState(home, homeCheck, schema, compatibility);
68
+ const domain = checkEphemeralDomain(home);
69
+ const toolChecks = [
26
70
  checkExecutable("git", env.YUI_GIT_BIN ?? "git", ["--version"], executor),
27
- checkExecutable("tmux", env.YUI_TMUX_BIN ?? "tmux", ["-V"], executor),
28
- ...storage.agents.flatMap((agent) => checkAgent(agent, executor))
71
+ checkExecutable("tmux", env.YUI_TMUX_BIN ?? "tmux", ["-V"], executor)
29
72
  ];
73
+ const agentChecks = storage.agents.flatMap((agent) => checkAgent(agent, executor, env));
74
+ const review = inspectReview({ ...storage.review, home }, agentChecks, env);
75
+ return {
76
+ checks: [
77
+ homeCheck,
78
+ schemaCheck,
79
+ compatibility.check,
80
+ storage.check,
81
+ ...(domain === undefined ? [] : [domain]),
82
+ ...toolChecks,
83
+ ...agentChecks,
84
+ ...review.checks
85
+ ],
86
+ review: review.report
87
+ };
30
88
  }
31
- export function renderDoctor(checks) {
32
- return `Yui doctor\n${renderTable("Checks", [
89
+ function checkEphemeralDomain(home) {
90
+ const identity = readEphemeralDomainIdentity(home);
91
+ if (identity.status === "absent")
92
+ return undefined;
93
+ if (identity.status === "invalid" || identity.identity === undefined) {
94
+ return {
95
+ name: "ephemeral domain",
96
+ status: "invalid",
97
+ detail: "runtime/domain.json is invalid; recreate the isolated test domain."
98
+ };
99
+ }
100
+ const host = readLinuxProcessStartIdentity(identity.identity.hostPid);
101
+ if (host === identity.identity.hostProcessStartIdentity) {
102
+ return {
103
+ name: "ephemeral domain",
104
+ status: "ok",
105
+ detail: `active host=${identity.identity.hostPid}:${host} token=${identity.identity.token.slice(0, 12)}`
106
+ };
107
+ }
108
+ if (host === undefined && existsSync(`/proc/${identity.identity.hostPid}`)) {
109
+ return {
110
+ name: "ephemeral domain",
111
+ status: "invalid",
112
+ detail: "host process is present but its start identity is unreadable; keep the domain protected and retry."
113
+ };
114
+ }
115
+ const ageMs = Math.max(0, Date.now() - Date.parse(identity.identity.createdAt));
116
+ return {
117
+ name: "ephemeral domain",
118
+ status: ageMs >= EPHEMERAL_DOMAIN_GRACE_MS ? "missing" : "invalid",
119
+ detail: ageMs >= EPHEMERAL_DOMAIN_GRACE_MS
120
+ ? "host is expired; run yui controller cleanup --all or wait for Controller recovery."
121
+ : "host identity is unavailable; bounded grace is still in progress."
122
+ };
123
+ }
124
+ export function renderDoctor(checks, review) {
125
+ const reviewLine = review === undefined
126
+ ? ""
127
+ : `Reviewer: ${review.status} (${review.detail})\n`;
128
+ return `Yui doctor\n${reviewLine}${renderTable("Checks", [
33
129
  { header: "Check", minWidth: 8, maxWidth: 28 },
34
130
  { header: "Status", minWidth: 7, maxWidth: 11 },
35
131
  { header: "Detail", minWidth: 12, maxWidth: 88 }
@@ -63,7 +159,7 @@ function readSchema(home) {
63
159
  return { status: "read-error", detail: errorMessage(error) };
64
160
  }
65
161
  }
66
- function checkSchema(state) {
162
+ function checkSchema(state, compatibility) {
67
163
  switch (state.status) {
68
164
  case "uninitialized":
69
165
  return { name: "storage schema", status: "missing", detail: "run yui setup" };
@@ -74,6 +170,15 @@ function checkSchema(state) {
74
170
  detail: `current=${state.currentVersion} latest=${state.latestVersion}`
75
171
  };
76
172
  case "unsupported":
173
+ if (compatibility.storageStatus === "compatible-old"
174
+ && compatibility.check.status === "ok") {
175
+ return {
176
+ name: "storage schema",
177
+ status: "ok",
178
+ detail: `current=${state.currentVersion} latest=${state.latestVersion} `
179
+ + `direction=${state.direction}; compatible-old validated`
180
+ };
181
+ }
77
182
  return {
78
183
  name: "storage schema",
79
184
  status: "unsupported",
@@ -85,20 +190,134 @@ function checkSchema(state) {
85
190
  return { name: "storage schema", status: "invalid", detail: state.detail };
86
191
  }
87
192
  }
88
- function inspectState(home, homeCheck, schema) {
193
+ /**
194
+ * The four-state storage compatibility check: USABLE / MIGRATABLE /
195
+ * NEEDS_NEW_VERSION / CORRUPTED. This complements the raw "storage schema" check
196
+ * with the migration framework's verdict, so a user sees whether a Home can be
197
+ * used as-is, upgraded with `yui upgrade`, needs a newer release, or is damaged.
198
+ * Doctor uses the same explicit production registry as `yui upgrade`; tests
199
+ * may inject a synthetic registry so the same classification and validation
200
+ * path proves compatible-old behavior. Missing paths fail closed, and
201
+ * CORRUPTED is reserved for structural or reference failure.
202
+ */
203
+ function inspectCompatibility(home, homeCheck, schema, storageOptions) {
204
+ const name = "storage compatibility";
205
+ if (homeCheck.status !== "ok") {
206
+ return { check: { name, status: homeCheck.status, detail: homeCheck.detail } };
207
+ }
208
+ if (schema.status === "uninitialized") {
209
+ return { check: { name, status: "missing", detail: "run yui setup" } };
210
+ }
211
+ if (schema.status === "read-error") {
212
+ return { check: { name, status: "invalid", detail: schema.detail } };
213
+ }
214
+ let classification;
215
+ let resolvedStorageOptions;
216
+ try {
217
+ resolvedStorageOptions = {
218
+ registry: storageOptions.registry ?? createProductionStorageRegistry(),
219
+ latest: storageOptions.latest ?? latestStorageVersionState()
220
+ };
221
+ classification = classifyHome({
222
+ home,
223
+ registry: resolvedStorageOptions.registry,
224
+ latest: resolvedStorageOptions.latest
225
+ });
226
+ }
227
+ catch (error) {
228
+ return { check: { name, status: "invalid", detail: errorMessage(error) } };
229
+ }
230
+ const storageStatus = classification.classification.status;
231
+ if (storageStatus === "compatible-old") {
232
+ try {
233
+ validateCompatibleFileTaskStore(home, resolvedStorageOptions);
234
+ }
235
+ catch (error) {
236
+ return {
237
+ storageStatus,
238
+ storageOptions: resolvedStorageOptions,
239
+ check: {
240
+ name,
241
+ status: "invalid",
242
+ detail: `unsupported compatible-old shape: ${errorMessage(error)}`
243
+ }
244
+ };
245
+ }
246
+ }
247
+ const versions = `layout=${classification.layoutVersion ?? "?"}/${classification.latestLayoutVersion}`
248
+ + ` aggregate=${classification.aggregateVersion ?? "?"}/${classification.latestAggregateVersion}`;
249
+ const incompatible = classification.incompatibleComponent === undefined
250
+ ? ""
251
+ : ` incompatibleComponent=${classification.incompatibleComponent}`;
252
+ switch (storageStatus) {
253
+ case "current":
254
+ return {
255
+ storageStatus,
256
+ storageOptions: resolvedStorageOptions,
257
+ check: { name, status: "ok", detail: `current (USABLE) ${versions}` }
258
+ };
259
+ case "compatible-old":
260
+ return {
261
+ storageStatus,
262
+ storageOptions: resolvedStorageOptions,
263
+ check: {
264
+ name,
265
+ status: "ok",
266
+ detail: `compatible-old (COMPATIBLE) ${versions}; ordinary commands are supported`
267
+ }
268
+ };
269
+ case "migration-required":
270
+ return {
271
+ storageStatus,
272
+ storageOptions: resolvedStorageOptions,
273
+ check: {
274
+ name,
275
+ status: "ok",
276
+ detail: `migration-required (MIGRATABLE) ${versions}; run yui update when Sessions are clear`
277
+ }
278
+ };
279
+ case "unsupported": {
280
+ const unsupported = classification.classification;
281
+ return {
282
+ storageStatus,
283
+ storageOptions: resolvedStorageOptions,
284
+ check: unsupported.verdict === "CORRUPTED"
285
+ ? {
286
+ name,
287
+ status: "invalid",
288
+ detail: `unsupported (CORRUPTED): ${unsupported.detail}`
289
+ }
290
+ : {
291
+ name,
292
+ status: "unsupported",
293
+ detail: `unsupported (NEEDS_NEW_VERSION) ${versions}${incompatible}; ` +
294
+ `${unsupported.blocker.reason}`
295
+ }
296
+ };
297
+ }
298
+ }
299
+ }
300
+ function inspectState(home, homeCheck, schema, compatibility) {
89
301
  if (homeCheck.status !== "ok") {
90
302
  return blockedStorage(homeCheck.status, homeCheck.detail);
91
303
  }
92
304
  if (schema.status === "uninitialized") {
93
305
  return blockedStorage("missing", "run yui setup");
94
306
  }
95
- if (schema.status === "unsupported") {
96
- return blockedStorage("unsupported", `current=${schema.currentVersion} latest=${schema.latestVersion}`);
97
- }
98
307
  if (schema.status === "invalid")
99
308
  return blockedStorage("invalid", schema.detail);
100
309
  if (schema.status === "read-error")
101
310
  return blockedStorage("invalid", schema.detail);
311
+ if (compatibility.check.status !== "ok") {
312
+ return blockedStorage(compatibility.check.status, storageBlockerDetail(compatibility.check));
313
+ }
314
+ if (compatibility.storageStatus !== "current"
315
+ && compatibility.storageStatus !== "compatible-old") {
316
+ return blockedStorage("unsupported", compatibility.check.detail);
317
+ }
318
+ if (compatibility.storageOptions === undefined) {
319
+ return blockedStorage("invalid", "Storage compatibility options were not resolved.");
320
+ }
102
321
  const statePath = join(home, STORAGE_STATE_FILE);
103
322
  if (!existsSync(statePath))
104
323
  return blockedStorage("missing", "run yui setup");
@@ -108,7 +327,7 @@ function inspectState(home, homeCheck, schema) {
108
327
  return blockedStorage("invalid", `${STORAGE_STATE_FILE} must be a regular file.`);
109
328
  }
110
329
  accessSync(statePath, constants.R_OK);
111
- const store = new FileTaskStore(home);
330
+ const store = openCompatibleFileTaskStore(home, compatibility.storageOptions);
112
331
  const config = store.getConfig();
113
332
  const agents = store.listConfiguredAgents();
114
333
  const tasks = store.listTasks();
@@ -120,17 +339,424 @@ function inspectState(home, homeCheck, schema) {
120
339
  status: "ok",
121
340
  detail: `readable agents=${agents.length} tasks=${tasks.length} roles=${roleCount} globalRoles=${globalRoles.length} defaultAgent=${config.defaultAgent ?? "none"}`
122
341
  },
123
- agents
342
+ agents,
343
+ review: {
344
+ storageReady: true,
345
+ storageDetail: "state.json is readable",
346
+ ...(config.review === undefined ? {} : { policy: config.review }),
347
+ ...(config.review === undefined
348
+ ? {}
349
+ : {
350
+ role: store.getGlobalRole(config.review.roleName),
351
+ agent: (() => {
352
+ const role = store.getGlobalRole(config.review.roleName);
353
+ return role === null
354
+ ? null
355
+ : store.getConfiguredAgent(role.activeAgentId);
356
+ })()
357
+ })
358
+ }
124
359
  };
125
360
  }
126
361
  catch (error) {
127
362
  return blockedStorage("invalid", errorMessage(error));
128
363
  }
129
364
  }
365
+ function storageBlockerDetail(check) {
366
+ const corruptionPrefix = "unsupported (CORRUPTED): ";
367
+ return check.status === "invalid" && check.detail.startsWith(corruptionPrefix)
368
+ ? `Invalid state.json: ${check.detail.slice(corruptionPrefix.length)}`
369
+ : check.detail;
370
+ }
130
371
  function blockedStorage(status, detail) {
131
372
  return {
132
373
  check: { name: "storage state", status, detail },
133
- agents: []
374
+ agents: [],
375
+ review: {
376
+ storageReady: false,
377
+ storageDetail: detail
378
+ }
379
+ };
380
+ }
381
+ function inspectReview(source, agentChecks, environment) {
382
+ if (!source.storageReady) {
383
+ const raw = source.home === undefined
384
+ ? null
385
+ : inspectRawReview(source.home, source.storageDetail);
386
+ if (raw !== null)
387
+ return raw;
388
+ return {
389
+ report: {
390
+ status: "misconfigured",
391
+ providerNative: "unverified",
392
+ detail: `Review configuration unavailable: ${source.storageDetail}`
393
+ },
394
+ checks: [{
395
+ name: "review policy",
396
+ status: "invalid",
397
+ detail: `unavailable: ${source.storageDetail}`
398
+ }]
399
+ };
400
+ }
401
+ const policy = source.policy;
402
+ if (policy === undefined) {
403
+ return {
404
+ report: {
405
+ status: "disabled",
406
+ providerNative: "unverified",
407
+ detail: "Review policy is disabled; no Reviewer dispatch is scheduled."
408
+ },
409
+ checks: [{
410
+ name: "review policy",
411
+ status: "ok",
412
+ detail: "disabled"
413
+ }]
414
+ };
415
+ }
416
+ const checks = [{
417
+ name: "review policy",
418
+ status: "ok",
419
+ detail: `role=${policy.roleName} trigger=${policy.trigger}`
420
+ }];
421
+ const role = source.role ?? null;
422
+ if (role === null) {
423
+ checks.push({
424
+ name: "reviewer role",
425
+ status: "missing",
426
+ detail: `Global Role not found: ${policy.roleName}.`
427
+ });
428
+ return reviewMisconfigured(policy, checks, `Reviewer Role is missing: ${policy.roleName}.`);
429
+ }
430
+ checks.push({
431
+ name: "reviewer role",
432
+ status: "ok",
433
+ detail: `name=${role.name} activeAgent=${role.activeAgentId}`
434
+ });
435
+ const binding = role.agentBindings[role.activeAgentId];
436
+ if (binding === undefined) {
437
+ checks.push({
438
+ name: "reviewer binding",
439
+ status: "missing",
440
+ detail: `active Agent binding is missing: ${role.activeAgentId}.`
441
+ });
442
+ return reviewMisconfigured(policy, checks, "Reviewer active Agent binding is missing.", role);
443
+ }
444
+ checks.push({
445
+ name: "reviewer binding",
446
+ status: "ok",
447
+ detail: `agent=${binding.agentId} adapter=${binding.adapterId}`
448
+ });
449
+ const agent = source.agent ?? null;
450
+ if (agent === null) {
451
+ checks.push({
452
+ name: "reviewer agent",
453
+ status: "missing",
454
+ detail: `Configured Agent not found: ${binding.agentId}.`
455
+ });
456
+ return reviewMisconfigured(policy, checks, `Reviewer Agent is missing: ${binding.agentId}.`, role, binding.agentId, binding.adapterId);
457
+ }
458
+ checks.push({
459
+ name: "reviewer agent",
460
+ status: "ok",
461
+ detail: `id=${agent.id} adapter=${agent.adapterId} command=${agent.command}`
462
+ });
463
+ if (agent.adapterId !== binding.adapterId) {
464
+ checks.push({
465
+ name: "reviewer adapter",
466
+ status: "invalid",
467
+ detail: `binding adapter=${binding.adapterId} does not match Agent adapter=${agent.adapterId}.`
468
+ });
469
+ return reviewMisconfigured(policy, checks, "Reviewer adapter identity does not match its Agent binding.", role, agent.id, agent.adapterId, agent.command);
470
+ }
471
+ let adapter;
472
+ try {
473
+ adapter = resolveAgentAdapter(agent.adapterId);
474
+ checks.push({
475
+ name: "reviewer adapter",
476
+ status: "ok",
477
+ detail: `adapter=${adapter.id} nativeSession=${adapter.capabilities.nativeSessionDiscovery}`
478
+ });
479
+ }
480
+ catch (error) {
481
+ checks.push({
482
+ name: "reviewer adapter",
483
+ status: "unsupported",
484
+ detail: errorMessage(error)
485
+ });
486
+ return reviewMisconfigured(policy, checks, `Reviewer adapter is unavailable: ${errorMessage(error)}`, role, agent.id, agent.adapterId, agent.command);
487
+ }
488
+ const commandCheck = agentChecks.find((check) => check.name === `agent:${agent.id}:command`);
489
+ const capabilityCheck = agentChecks.find((check) => check.name === `agent:${agent.id}:capability`);
490
+ checks.push({
491
+ name: "reviewer command",
492
+ status: commandCheck?.status ?? "invalid",
493
+ detail: commandCheck === undefined
494
+ ? `No command inspection was recorded for ${agent.id}.`
495
+ : commandCheck.detail
496
+ });
497
+ checks.push({
498
+ name: "reviewer capability",
499
+ status: capabilityCheck?.status ?? "invalid",
500
+ detail: capabilityCheck === undefined
501
+ ? `No capability inspection was recorded for ${agent.id}.`
502
+ : capabilityCheck.detail
503
+ });
504
+ const launch = checkReviewerLaunch(agent, role, binding, adapter, environment, source.home);
505
+ checks.push(launch);
506
+ const dispatch = checkReviewerDispatch(policy, role, binding, agent, launch, commandCheck, capabilityCheck);
507
+ checks.push(dispatch);
508
+ const failedCheck = checks.find((check) => check.status !== "ok");
509
+ if (failedCheck !== undefined) {
510
+ return reviewMisconfigured(policy, checks, `${failedCheck.name}: ${failedCheck.detail}`, role, agent.id, agent.adapterId, agent.command);
511
+ }
512
+ return {
513
+ report: {
514
+ status: "ready",
515
+ policy,
516
+ roleName: role.name,
517
+ activeAgentId: agent.id,
518
+ adapterId: agent.adapterId,
519
+ command: agent.command,
520
+ providerNative: "unverified",
521
+ detail: "Static Reviewer configuration and exact dispatch prerequisites are ready; provider-native acceptance is unverified."
522
+ },
523
+ checks
524
+ };
525
+ }
526
+ /**
527
+ * Preserve a bounded Reviewer diagnosis when another malformed record prevents
528
+ * FileTaskStore from opening. This is read-only evidence, never a compatibility
529
+ * path: the storage check remains invalid and the review projection cannot be
530
+ * reported as ready from unvalidated bytes.
531
+ */
532
+ function inspectRawReview(home, storageDetail) {
533
+ try {
534
+ const parsed = JSON.parse(readFileSync(join(home, STORAGE_STATE_FILE), "utf8"));
535
+ if (!isRecord(parsed) || !isRecord(parsed.config))
536
+ return null;
537
+ const rawPolicy = parsed.config.review;
538
+ if (!isRecord(rawPolicy)
539
+ || typeof rawPolicy.roleName !== "string"
540
+ || typeof rawPolicy.trigger !== "string") {
541
+ return {
542
+ report: {
543
+ status: "misconfigured",
544
+ providerNative: "unverified",
545
+ detail: `Storage state is invalid; Review policy is unavailable: ${storageDetail}`
546
+ },
547
+ checks: [{
548
+ name: "review policy",
549
+ status: "invalid",
550
+ detail: `storage state invalid: ${storageDetail}`
551
+ }]
552
+ };
553
+ }
554
+ const policy = {
555
+ roleName: rawPolicy.roleName,
556
+ trigger: rawPolicy.trigger
557
+ };
558
+ const roles = isRecord(parsed.globalRoles) ? parsed.globalRoles : {};
559
+ const roleValue = roles[policy.roleName];
560
+ if (!isRecord(roleValue)) {
561
+ return rawReviewMisconfigured(policy, storageDetail, [{
562
+ name: "review policy",
563
+ status: "ok",
564
+ detail: `role=${policy.roleName} trigger=${policy.trigger}`
565
+ }, {
566
+ name: "reviewer role",
567
+ status: "missing",
568
+ detail: `Global Role not found: ${policy.roleName}.`
569
+ }]);
570
+ }
571
+ const roleName = typeof roleValue.name === "string" ? roleValue.name : policy.roleName;
572
+ const activeAgentId = typeof roleValue.activeAgentId === "string"
573
+ ? roleValue.activeAgentId
574
+ : "";
575
+ const checks = [{
576
+ name: "review policy",
577
+ status: "ok",
578
+ detail: `role=${policy.roleName} trigger=${policy.trigger}`
579
+ }, {
580
+ name: "reviewer role",
581
+ status: "ok",
582
+ detail: `name=${roleName} activeAgent=${activeAgentId || "missing"}`
583
+ }];
584
+ const bindings = isRecord(roleValue.agentBindings) ? roleValue.agentBindings : {};
585
+ const bindingValue = activeAgentId.length === 0 ? undefined : bindings[activeAgentId];
586
+ if (!isRecord(bindingValue)) {
587
+ checks.push({
588
+ name: "reviewer binding",
589
+ status: "missing",
590
+ detail: `active Agent binding is missing: ${activeAgentId || "unknown"}.`
591
+ });
592
+ return rawReviewMisconfigured(policy, storageDetail, checks, roleName, activeAgentId || undefined);
593
+ }
594
+ const bindingAgentId = typeof bindingValue.agentId === "string"
595
+ ? bindingValue.agentId
596
+ : activeAgentId;
597
+ const adapterId = typeof bindingValue.adapterId === "string"
598
+ ? bindingValue.adapterId
599
+ : "unknown";
600
+ checks.push({
601
+ name: "reviewer binding",
602
+ status: "ok",
603
+ detail: `agent=${bindingAgentId} adapter=${adapterId}`
604
+ });
605
+ const configuredAgents = isRecord(parsed.configuredAgents)
606
+ ? parsed.configuredAgents
607
+ : {};
608
+ const agentValue = configuredAgents[bindingAgentId];
609
+ if (!isRecord(agentValue)) {
610
+ checks.push({
611
+ name: "reviewer agent",
612
+ status: "missing",
613
+ detail: `Configured Agent not found: ${bindingAgentId}.`
614
+ });
615
+ return rawReviewMisconfigured(policy, storageDetail, checks, roleName, bindingAgentId, adapterId);
616
+ }
617
+ checks.push({
618
+ name: "reviewer agent",
619
+ status: "invalid",
620
+ detail: `Storage state is invalid; configured Agent ${bindingAgentId} cannot be validated.`
621
+ });
622
+ return rawReviewMisconfigured(policy, storageDetail, checks, roleName, bindingAgentId, adapterId, typeof agentValue.command === "string" ? agentValue.command : undefined);
623
+ }
624
+ catch {
625
+ return null;
626
+ }
627
+ }
628
+ function rawReviewMisconfigured(policy, storageDetail, checks, roleName, activeAgentId, adapterId, command) {
629
+ return {
630
+ report: {
631
+ status: "misconfigured",
632
+ policy,
633
+ ...(roleName === undefined ? {} : { roleName }),
634
+ ...(activeAgentId === undefined ? {} : { activeAgentId }),
635
+ ...(adapterId === undefined ? {} : { adapterId }),
636
+ ...(command === undefined ? {} : { command }),
637
+ providerNative: "unverified",
638
+ detail: `Storage state is invalid; ${checks.at(-1)?.detail ?? storageDetail}`
639
+ },
640
+ checks
641
+ };
642
+ }
643
+ function isRecord(value) {
644
+ return value !== null && typeof value === "object" && !Array.isArray(value);
645
+ }
646
+ function reviewMisconfigured(policy, checks, detail, role, activeAgentId, adapterId, command) {
647
+ return {
648
+ report: {
649
+ status: "misconfigured",
650
+ policy,
651
+ roleName: role?.name ?? policy.roleName,
652
+ ...(activeAgentId === undefined ? {} : { activeAgentId }),
653
+ ...(adapterId === undefined ? {} : { adapterId }),
654
+ ...(command === undefined ? {} : { command }),
655
+ providerNative: "unverified",
656
+ detail
657
+ },
658
+ checks
659
+ };
660
+ }
661
+ function checkReviewerLaunch(agent, role, binding, adapter, environment, home) {
662
+ try {
663
+ const workspace = role.workspace;
664
+ if (!isAbsolute(workspace)) {
665
+ return {
666
+ name: "reviewer launch",
667
+ status: "invalid",
668
+ detail: "Reviewer Role workspace must be absolute."
669
+ };
670
+ }
671
+ const metadata = lstatSync(workspace);
672
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
673
+ return {
674
+ name: "reviewer launch",
675
+ status: "invalid",
676
+ detail: "Reviewer Role workspace must be an existing real directory."
677
+ };
678
+ }
679
+ const definition = configuredAgentToDefinition(agent);
680
+ const launchEnvironment = resolveDoctorAgentEnvironment(definition, environment);
681
+ const effective = resolveEffectiveLaunch({ role, purpose: "execution" });
682
+ const agentWorkspace = effective.workspace.entries.length === 1
683
+ ? effective.workspace.entries[0].path
684
+ : effective.workspace.root;
685
+ const codexConfig = adapter.id === "codex"
686
+ ? inspectCodexLaunchConfig({
687
+ environment: launchEnvironment,
688
+ workspace: agentWorkspace,
689
+ profile: binding.config.adapterId === "codex" ? binding.config.profile : undefined,
690
+ trustWorkspace: true
691
+ })
692
+ : undefined;
693
+ if (codexConfig?.notify.status === "configured") {
694
+ return {
695
+ name: "reviewer launch",
696
+ status: "invalid",
697
+ detail: "Codex notify is already configured by "
698
+ + `${codexConfig.notify.source}; Yui requires exclusive ownership of the structured `
699
+ + "notify callback and refuses to replace or be replaced by native configuration."
700
+ };
701
+ }
702
+ const reviewerContext = adapter.id === "codex"
703
+ ? compileRoleSessionContext(home, role, { scope: "global" }, { purpose: "review" })
704
+ : undefined;
705
+ const compiled = adapter.compileNew({
706
+ agent: definition,
707
+ config: binding.config,
708
+ workspace: agentWorkspace,
709
+ sessionTitle: "reviewer",
710
+ ...(reviewerContext === undefined
711
+ ? {}
712
+ : {
713
+ developerInstructions: reviewerContext.developerInstructions,
714
+ skills: reviewerContext.skills,
715
+ codexDeveloperInstructions: codexConfig?.developerInstructions
716
+ })
717
+ });
718
+ if (compiled.argv.length === 0)
719
+ throw new Error("compiled launch argv is empty");
720
+ return {
721
+ name: "reviewer launch",
722
+ status: "ok",
723
+ detail: `adapter=${adapter.id} strategy=${compiled.sessionStrategy} command=${agent.command}`
724
+ };
725
+ }
726
+ catch (error) {
727
+ return {
728
+ name: "reviewer launch",
729
+ status: "invalid",
730
+ detail: errorMessage(error)
731
+ };
732
+ }
733
+ }
734
+ function checkReviewerDispatch(_policy, role, binding, agent, launch, command, capability) {
735
+ if (role.defaultAccess !== "write") {
736
+ return {
737
+ name: "reviewer dispatch",
738
+ status: "invalid",
739
+ detail: "Reviewer Role must retain write access for an isolated ReviewRound workspace."
740
+ };
741
+ }
742
+ if (binding.agentId !== agent.id) {
743
+ return {
744
+ name: "reviewer dispatch",
745
+ status: "invalid",
746
+ detail: `active binding Agent id does not match configured Agent: ${binding.agentId}/${agent.id}.`
747
+ };
748
+ }
749
+ if (launch.status !== "ok" || command?.status !== "ok" || capability?.status !== "ok") {
750
+ return {
751
+ name: "reviewer dispatch",
752
+ status: "invalid",
753
+ detail: "Reviewer exact dispatch is blocked until command, capability, and launch checks are ok."
754
+ };
755
+ }
756
+ return {
757
+ name: "reviewer dispatch",
758
+ status: "ok",
759
+ detail: "static exact dispatch prerequisites confirmed; no Session or model was launched."
134
760
  };
135
761
  }
136
762
  function checkExecutable(name, command, args, executor) {
@@ -150,11 +776,13 @@ function checkExecutable(name, command, args, executor) {
150
776
  };
151
777
  }
152
778
  }
153
- function checkAgent(agent, executor) {
779
+ function checkAgent(agent, executor, environment) {
154
780
  let snapshot;
155
781
  try {
156
- snapshot = inspectAgentCapabilities(configuredAgentToDefinition(agent), {
157
- run: (command, args) => runAgentProbe(executor, command, args)
782
+ const definition = configuredAgentToDefinition(agent);
783
+ const launchEnvironment = resolveDoctorAgentEnvironment(definition, environment);
784
+ snapshot = inspectAgentCapabilities(definition, {
785
+ run: (command, args) => runAgentProbe(executor, command, args, launchEnvironment)
158
786
  });
159
787
  }
160
788
  catch (error) {
@@ -184,13 +812,22 @@ function checkAgent(agent, executor) {
184
812
  {
185
813
  name: `agent:${agent.id}:capability`,
186
814
  status,
187
- detail: `start resume interrupt nativeSession=${snapshot.lifecycle.nativeSessionDiscovery} fields=${available}/${degraded}/${unavailable}`
815
+ detail: [
816
+ `start resume interrupt nativeSession=${snapshot.lifecycle.nativeSessionDiscovery}`,
817
+ `preInputReady=${snapshot.lifecycle.preInputReadiness.status}`,
818
+ `fields=${available}/${degraded}/${unavailable}`,
819
+ ...snapshot.warnings.map((warning) => `warning=${warning}`)
820
+ ].join(" ")
188
821
  }
189
822
  ];
190
823
  }
191
- function runAgentProbe(executor, command, args) {
824
+ function runAgentProbe(executor, command, args, environment) {
192
825
  try {
193
- return { status: 0, stdout: executor.run(command, [...args]), stderr: "" };
826
+ return {
827
+ status: 0,
828
+ stdout: executor.run(command, [...args], { environment }),
829
+ stderr: ""
830
+ };
194
831
  }
195
832
  catch (error) {
196
833
  const missing = isMissingCommand(error);
@@ -205,6 +842,18 @@ function runAgentProbe(executor, command, args) {
205
842
  };
206
843
  }
207
844
  }
845
+ /**
846
+ * Resolve the complete environment used by FileRoleLaunchPlanner for an Agent.
847
+ * Doctor probes must inspect the same command resolution context as a native
848
+ * launch; inheriting the Doctor process PATH would allow a shell-installed
849
+ * command to mask a missing configured binding.
850
+ */
851
+ function resolveDoctorAgentEnvironment(agent, environment) {
852
+ return {
853
+ ...operationalAgentEnvironment(agent.adapterId, environment),
854
+ ...resolveAgentEnvironment(agent, environment)
855
+ };
856
+ }
208
857
  function isMissingCommand(error) {
209
858
  return error instanceof CommandExecutionError
210
859
  ? error.code === "COMMAND_NOT_FOUND"