@zq-silk/yui 0.16.1 → 1.0.0-alpha

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 (193) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +9 -4
  4. package/dist/agent/agent.js +4 -9
  5. package/dist/agent/executionComponents.js +4 -4
  6. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  7. package/dist/agentRun/agentRun.js +13 -23
  8. package/dist/artifacts/managedGit.js +3 -56
  9. package/dist/brief/taskBrief.js +3 -3
  10. package/dist/cli/agentConfigurationPicker.js +24 -21
  11. package/dist/cli/commandCatalog.js +18 -10
  12. package/dist/cli/roleWizard.js +41 -30
  13. package/dist/cli/updateCommand.js +35 -13
  14. package/dist/cli/updateOrchestrator.js +73 -44
  15. package/dist/cli/updatePorts.js +76 -86
  16. package/dist/cli/upgradeCommand.js +4 -8
  17. package/dist/cli.js +39 -33
  18. package/dist/commands/controllerCommands.js +2 -2
  19. package/dist/commands/executionAuditCommands.js +2 -2
  20. package/dist/commands/projectCommands.js +1 -1
  21. package/dist/commands/releaseCommands.js +9 -38
  22. package/dist/commands/taskCommands.js +91 -81
  23. package/dist/commands/taskCompletionGate.js +0 -117
  24. package/dist/commands/taskRoleRuntimeStatus.js +2 -2
  25. package/dist/commands/taskUpstreamCommands.js +93 -54
  26. package/dist/context/runContextPack.js +30 -54
  27. package/dist/context/runInputContract.js +9 -0
  28. package/dist/context/sessionBootstrapManifest.js +11 -19
  29. package/dist/controller/agentHostObservation.js +4 -2
  30. package/dist/controller/agentRuntimeObserver.js +4 -4
  31. package/dist/controller/clientRuntime.js +19 -44
  32. package/dist/controller/controller.js +24 -10
  33. package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
  34. package/dist/controller/globalInputDelivery.js +31 -8
  35. package/dist/controller/globalRuntimeAttention.js +34 -0
  36. package/dist/controller/jobSupervisor.js +3 -3
  37. package/dist/controller/operatorNotification.js +31 -0
  38. package/dist/controller/providerRetryAdmission.js +3 -1
  39. package/dist/controller/providerRetryDelivery.js +136 -119
  40. package/dist/controller/runtime.js +2 -28
  41. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  42. package/dist/controller/structuredProviderObservation.js +28 -1
  43. package/dist/controller/updateReconciliation.js +72 -20
  44. package/dist/coordination/workMailbox.js +4 -4
  45. package/dist/core/controllerIdentity.js +25 -0
  46. package/dist/core/controllerProcessIdentity.js +2 -2
  47. package/dist/core/controllerServer.js +4 -2
  48. package/dist/core/protocol.js +1 -1
  49. package/dist/doctor/doctor.js +2 -1
  50. package/dist/domain/validation.js +6 -0
  51. package/dist/event/taskEvent.js +3 -3
  52. package/dist/execution/workItemExecution.js +2 -2
  53. package/dist/executor/agentAdapter.js +28 -96
  54. package/dist/executor/agentConfigurationCatalog.js +13 -68
  55. package/dist/executor/agentConfigurationFields.js +120 -0
  56. package/dist/executor/agentConfigurationProbe.js +55 -80
  57. package/dist/executor/agentExecutor.js +11 -8
  58. package/dist/executor/effectiveLaunch.js +3 -3
  59. package/dist/executor/executorRegistry.js +0 -9
  60. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  61. package/dist/grant/capabilityGrant.js +2 -2
  62. package/dist/input/inputRequest.js +4 -4
  63. package/dist/integration/changeSet.js +3 -3
  64. package/dist/integration/integrationAttempt.js +3 -3
  65. package/dist/integration/integrationSourceApplication.js +1 -1
  66. package/dist/interaction/operatorPresentation.js +2 -1
  67. package/dist/job/durableJob.js +1 -1
  68. package/dist/job/jobRunner.js +2 -2
  69. package/dist/message/message.js +8 -6
  70. package/dist/message/messageContinuation.js +8 -9
  71. package/dist/milestone/milestone.js +3 -3
  72. package/dist/observability/executionAudit.js +1 -10
  73. package/dist/observability/runtimeIdentity.js +0 -23
  74. package/dist/output/agentConfigurationPresentation.js +4 -2
  75. package/dist/profile/agentProfile.js +3 -3
  76. package/dist/release/releaseHandover.js +2 -2
  77. package/dist/release/releaseIdempotencyStore.js +0 -23
  78. package/dist/release/releaseWorkflowPorts.js +8 -4
  79. package/dist/release/runtimeRelease.js +9 -7
  80. package/dist/repository/gitWorkspace.js +66 -19
  81. package/dist/repository/project.js +3 -3
  82. package/dist/repository/taskWorkspacePreparer.js +75 -68
  83. package/dist/resources/liveReferences.js +1 -1
  84. package/dist/resources/resourceRegistry.js +90 -44
  85. package/dist/resources/sqliteResourceRegistry.js +5 -7
  86. package/dist/review/reviewRound.js +5 -5
  87. package/dist/role/role.js +7 -8
  88. package/dist/runtime/acpProtocol.js +2 -3
  89. package/dist/runtime/acpSession.js +21 -5
  90. package/dist/runtime/agentDriverObservation.js +1 -1
  91. package/dist/runtime/agentEndpoint.js +4 -1
  92. package/dist/runtime/agentHost.js +25 -65
  93. package/dist/runtime/agentHostCleanup.js +85 -0
  94. package/dist/runtime/agentHostProtocol.js +3 -3
  95. package/dist/runtime/builtinAgentDrivers.js +46 -119
  96. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  97. package/dist/runtime/codexAppServerRuntime.js +55 -89
  98. package/dist/runtime/codexInteractiveHost.js +1 -1
  99. package/dist/runtime/jsonLineChannel.js +35 -7
  100. package/dist/runtime/launchBroker.js +1 -1
  101. package/dist/runtime/processExitObservation.js +1 -1
  102. package/dist/runtime/providerContinuationReconciliationService.js +86 -38
  103. package/dist/runtime/providerRetry.js +15 -0
  104. package/dist/runtime/providerRuntimeIdentity.js +3 -3
  105. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  106. package/dist/runtime/runtimeCoherence.js +7 -3
  107. package/dist/runtime/runtimeObservation.js +3 -3
  108. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  109. package/dist/runtime/structuredProviderHost.js +106 -81
  110. package/dist/runtime/taskRuntimeIsolation.js +3 -3
  111. package/dist/runtime/tmuxAdapters.js +3 -3
  112. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  113. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  114. package/dist/scheduler/taskWake.js +1 -1
  115. package/dist/storage/baselineSchema.js +606 -0
  116. package/dist/storage/homeLayout.js +5 -16
  117. package/dist/storage/recordValidation.js +16 -4
  118. package/dist/storage/sqliteSchema.js +106 -1741
  119. package/dist/storage/sqliteStore.js +16 -14
  120. package/dist/storage/storageSchema.js +8 -7
  121. package/dist/storage/storageVersions.js +23 -16
  122. package/dist/storage/taskStore.js +3 -29
  123. package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
  124. package/dist/task/nextAction.js +33 -23
  125. package/dist/task/task.js +17 -8
  126. package/dist/task/taskActivation.js +5 -4
  127. package/dist/telemetry/sqliteTelemetryStore.js +2 -2
  128. package/dist/verification/gateArtifact.js +5 -3
  129. package/dist/verification/verificationPlan.js +6 -7
  130. package/dist/web/assets/assetManifest.js +2 -0
  131. package/dist/web/assets/client/app.js +72 -3
  132. package/dist/web/assets/client/components.js +2 -1
  133. package/dist/web/assets/client/i18n.js +2 -0
  134. package/dist/web/assets/client/taskSummary.js +345 -0
  135. package/dist/web/assets/client/taskSurface.js +44 -26
  136. package/dist/web/assets/client/view.js +33 -0
  137. package/dist/web/assets/shell.js +1 -0
  138. package/dist/web/assets/styles/cards.js +21 -0
  139. package/dist/web/webServer.js +39 -5
  140. package/dist/web/webSessions.js +165 -0
  141. package/dist/web/webSnapshot.js +23 -0
  142. package/dist/web/webTaskSurface.js +23 -0
  143. package/dist/workItem/workItem.js +6 -6
  144. package/dist/workspace/cleanupInspection.js +1 -9
  145. package/dist/worktree/managedWorkspace.js +3 -3
  146. package/docs/managed-turn-and-session-runtime.md +50 -7
  147. package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
  148. package/docs/observability/README.md +47 -0
  149. package/docs/observability/README.zh-CN.md +37 -0
  150. package/docs/project-refresh.md +9 -0
  151. package/docs/project-refresh.zh-CN.md +8 -0
  152. package/docs/provider-retry.md +35 -0
  153. package/docs/release-workflow.md +70 -214
  154. package/docs/release-workflow.zh-CN.md +53 -157
  155. package/docs/roles-and-configuration.md +30 -0
  156. package/docs/roles-and-configuration.zh-CN.md +20 -0
  157. package/docs/sqlite-control-plane-design.md +48 -289
  158. package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
  159. package/docs/storage-baseline.md +132 -0
  160. package/docs/storage-baseline.zh-CN.md +106 -0
  161. package/docs/task-delivery.md +110 -2
  162. package/docs/task-delivery.zh-CN.md +82 -2
  163. package/docs/task-discovery.md +9 -0
  164. package/docs/task-discovery.zh-CN.md +6 -0
  165. package/docs/testing/verification-levels.md +42 -164
  166. package/docs/testing/verification-levels.zh-CN.md +29 -115
  167. package/i18n/README.zh-CN.md +7 -4
  168. package/package.json +1 -1
  169. package/skills/yui-leader/SKILL.md +11 -0
  170. package/skills/yui-leader/references/execution.md +22 -0
  171. package/skills/yui-leader/references/planning.md +6 -2
  172. package/skills/yui-operator/SKILL.md +45 -13
  173. package/skills/yui-operator/references/task-delivery.md +99 -0
  174. package/skills/yui-runtime/SKILL.md +5 -3
  175. package/skills/yui-runtime/references/publication.md +56 -5
  176. package/skills/yui-runtime/references/recovery.md +12 -2
  177. package/dist/runtime/agentHostCompatibility.js +0 -127
  178. package/dist/storage/migrations/agentFailureContext.js +0 -22
  179. package/dist/storage/migrations/agentRunContract.js +0 -159
  180. package/dist/storage/migrations/artifactsToGit.js +0 -338
  181. package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
  182. package/dist/storage/migrations/currentInputContract.js +0 -86
  183. package/dist/storage/migrations/currentRuntimeContract.js +0 -228
  184. package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
  185. package/dist/storage/migrations/integrationContinuation.js +0 -105
  186. package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
  187. package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
  188. package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
  189. package/dist/storage/migrations/submitIntent.js +0 -126
  190. package/dist/storage/migrations/unifyHomeLayout.js +0 -925
  191. package/dist/storage/migrations/verificationPlanV1.js +0 -162
  192. package/dist/storage/migrations/verificationPolicy.js +0 -74
  193. package/dist/storage/migrations/workItemHistory.js +0 -46
@@ -3,6 +3,14 @@ import { acquireHomeLifecycleLock } from "../core/controllerServer.js";
3
3
  import { cleanControllerResource } from "./resourceCleanupLinux.js";
4
4
  import { scanControllerResourceInventory } from "./resourceInventoryLinux.js";
5
5
  const MAX_RECONCILIATION_PASSES = 4;
6
+ export class UpdateControllerReconciliationError extends Error {
7
+ result;
8
+ constructor(message, result, options) {
9
+ super(message, options);
10
+ this.result = result;
11
+ this.name = "UpdateControllerReconciliationError";
12
+ }
13
+ }
6
14
  /**
7
15
  * Reconcile only Controller-owned resources for the Home being updated.
8
16
  *
@@ -12,21 +20,49 @@ const MAX_RECONCILIATION_PASSES = 4;
12
20
  * and inode fingerprints. Agent, tmux, app, and foreign-Home resources are
13
21
  * deliberately outside this operation.
14
22
  */
15
- export async function reconcileControllerResourcesForUpdate(home, environment = process.env, tmuxBin) {
23
+ export async function reconcileControllerResourcesForUpdate(home, environment = process.env, tmuxBin, ports = {
24
+ scan: scanControllerResourceInventory,
25
+ clean: cleanControllerResource,
26
+ acquireLock: acquireHomeLifecycleLock
27
+ }) {
16
28
  const resolvedHome = resolve(home);
17
- const releaseLock = await acquireHomeLifecycleLock(resolvedHome, {
18
- removeStaleOwner: true
19
- });
20
29
  const cleaned = new Set();
21
- try {
22
- for (let pass = 0; pass < MAX_RECONCILIATION_PASSES; pass += 1) {
23
- const snapshot = await scanControllerResourceInventory({
24
- currentHome: resolvedHome,
25
- scope: "current",
26
- environment,
30
+ const attempts = [];
31
+ let remaining = [];
32
+ let observedAt;
33
+ let observationError;
34
+ let lockReleaseError;
35
+ const result = () => ({
36
+ home: resolvedHome, cleaned: [...cleaned], attempts: [...attempts], remaining: [...remaining],
37
+ ...(observedAt === undefined ? {} : { observedAt }),
38
+ ...(observationError === undefined ? {} : { observationError }),
39
+ ...(lockReleaseError === undefined ? {} : { lockReleaseError })
40
+ });
41
+ const scan = async () => {
42
+ try {
43
+ const snapshot = await ports.scan({
44
+ currentHome: resolvedHome, scope: "current", environment,
27
45
  ...(tmuxBin === undefined ? {} : { tmuxBin })
28
46
  });
47
+ if (snapshot.scope === "current" && resolve(snapshot.currentHome) === resolvedHome) {
48
+ remaining = controllerResources(snapshot, resolvedHome);
49
+ observedAt = snapshot.observedAt;
50
+ }
29
51
  assertCertainSnapshot(snapshot, resolvedHome);
52
+ observationError = undefined;
53
+ return snapshot;
54
+ }
55
+ catch (error) {
56
+ observationError = messageOf(error);
57
+ throw error;
58
+ }
59
+ };
60
+ let releaseLock;
61
+ let failure;
62
+ try {
63
+ releaseLock = await ports.acquireLock(resolvedHome, { removeStaleOwner: true });
64
+ for (let pass = 0; pass < MAX_RECONCILIATION_PASSES; pass += 1) {
65
+ const snapshot = await scan();
30
66
  const resources = controllerResources(snapshot, resolvedHome);
31
67
  const controllers = resources.filter(({ kind }) => kind === "controller");
32
68
  const current = controllers.filter(({ state }) => state === "current");
@@ -50,36 +86,52 @@ export async function reconcileControllerResourcesForUpdate(home, environment =
50
86
  }
51
87
  const candidates = [...historicalCleanup, ...staleArtifactCleanup];
52
88
  if (candidates.length === 0) {
53
- return { cleaned: [...cleaned] };
89
+ break;
54
90
  }
55
91
  for (const candidate of candidates) {
56
92
  try {
57
- await cleanControllerResource(candidate, { environment, ...(tmuxBin === undefined ? {} : { tmuxBin }) });
93
+ await ports.clean(candidate, { environment, ...(tmuxBin === undefined ? {} : { tmuxBin }) });
58
94
  cleaned.add(candidate.id);
95
+ attempts.push({ resource: candidate, outcome: "cleaned" });
96
+ remaining = remaining.filter(({ id }) => id !== candidate.id);
59
97
  }
60
98
  catch (error) {
61
99
  // A concurrent exact cleanup that already reached the desired state
62
100
  // is harmless. Anything still present or reclassified is a real
63
101
  // ownership change and must remain a user-visible blocker.
64
- const afterFailure = await scanControllerResourceInventory({
65
- currentHome: resolvedHome,
66
- scope: "current",
67
- environment
68
- });
69
- assertCertainSnapshot(afterFailure, resolvedHome);
102
+ const attempt = { resource: candidate, outcome: "unknown", error: messageOf(error) };
103
+ attempts.push(attempt);
104
+ // If this observation also fails, keep BOTH failures and the exact
105
+ // attempted identity. Failure to observe absence is never success.
106
+ const afterFailure = await scan();
70
107
  if (!controllerResources(afterFailure, resolvedHome).some(({ id }) => id === candidate.id)) {
71
108
  cleaned.add(candidate.id);
109
+ attempts[attempts.length - 1] = { ...attempt, outcome: "absent" };
72
110
  continue;
73
111
  }
74
112
  throw reconciliationBlocked(`resource ${candidate.id} changed or could not be cleaned: ${messageOf(error)}`);
75
113
  }
76
114
  }
115
+ if (pass === MAX_RECONCILIATION_PASSES - 1) {
116
+ throw reconciliationBlocked("Controller resources did not converge after bounded cleanup");
117
+ }
77
118
  }
78
- throw reconciliationBlocked("Controller resources did not converge after bounded cleanup");
119
+ }
120
+ catch (error) {
121
+ failure = error;
79
122
  }
80
123
  finally {
81
- await releaseLock();
124
+ try {
125
+ await releaseLock?.();
126
+ }
127
+ catch (error) {
128
+ lockReleaseError = messageOf(error);
129
+ failure ??= error;
130
+ }
82
131
  }
132
+ if (failure !== undefined)
133
+ throw new UpdateControllerReconciliationError(messageOf(failure), result(), { cause: failure });
134
+ return result();
83
135
  }
84
136
  function assertCertainSnapshot(snapshot, resolvedHome) {
85
137
  if (snapshot.scope !== "current"
@@ -2,7 +2,7 @@ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
2
2
  const RECENT_DEDUPE_KEY_LIMIT = 256;
3
3
  export function createWorkMailbox(target) {
4
4
  return {
5
- schemaVersion: 5,
5
+ schemaVersion: 1,
6
6
  target: copyTarget(target),
7
7
  nextSequence: 1,
8
8
  processing: null,
@@ -13,8 +13,8 @@ export function createWorkMailbox(target) {
13
13
  export function validateWorkMailbox(value) {
14
14
  const mailbox = record(value, "WorkMailbox");
15
15
  exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending", "recentDedupeKeys"], "WorkMailbox");
16
- if (mailbox.schemaVersion !== 5)
17
- throw new Error("WorkMailbox must use schemaVersion 5");
16
+ if (mailbox.schemaVersion !== 1)
17
+ throw new Error("WorkMailbox must use schemaVersion 1");
18
18
  const target = parseTarget(mailbox.target);
19
19
  const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
20
20
  const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
@@ -38,7 +38,7 @@ export function validateWorkMailbox(value) {
38
38
  }
39
39
  }
40
40
  return {
41
- schemaVersion: 5,
41
+ schemaVersion: 1,
42
42
  target,
43
43
  nextSequence,
44
44
  processing,
@@ -0,0 +1,25 @@
1
+ import { compareStorageVersions, isStorageVersion } from "../storage/storageVersions.js";
2
+ export function isControllerIdentity(value) {
3
+ if (typeof value !== "object" || value === null || Array.isArray(value))
4
+ return false;
5
+ const record = value;
6
+ return typeof record.executablePath === "string" && record.executablePath.length > 0
7
+ && Array.isArray(record.args) && record.args.every(arg => typeof arg === "string")
8
+ && typeof record.version === "string" && record.version.length > 0
9
+ && Number.isSafeInteger(record.controllerProtocolVersion) && record.controllerProtocolVersion > 0
10
+ && isStorageVersion(record.storageVersion) && isStorageVersion(record.minimumStorageVersion)
11
+ && compareStorageVersions(record.minimumStorageVersion, record.storageVersion) <= 0;
12
+ }
13
+ export function parseControllerIdentity(value) {
14
+ if (!isControllerIdentity(value)) {
15
+ throw new Error("Controller identity must include the exact executable, argv, package, protocol and storage versions.");
16
+ }
17
+ return {
18
+ executablePath: value.executablePath,
19
+ args: [...value.args],
20
+ version: value.version,
21
+ controllerProtocolVersion: value.controllerProtocolVersion,
22
+ storageVersion: value.storageVersion,
23
+ minimumStorageVersion: value.minimumStorageVersion
24
+ };
25
+ }
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, readdirSync } from "node:fs";
2
2
  import { readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
3
3
  import { readHomeFilesystemId, validateHomeFilesystemId } from "./homeFilesystemIdentity.js";
4
- /** Exact live-process fence used only at Controller startup and v3 handoff. */
4
+ /** Exact live-process fence used at Controller startup and handover. */
5
5
  export function inspectLiveControllerProcess(pid, homeFilesystemId, expectedProcessStartIdentity) {
6
6
  if (!Number.isSafeInteger(pid) || pid < 1 || pid === process.pid)
7
7
  return undefined;
@@ -39,7 +39,7 @@ export function inspectLiveControllerProcess(pid, homeFilesystemId, expectedProc
39
39
  return undefined;
40
40
  }
41
41
  }
42
- /** Finds an older/current Controller whose discovery record was lost. */
42
+ /** Finds a Controller whose discovery record was lost. */
43
43
  export function findLiveControllerProcessForHome(homeFilesystemId) {
44
44
  validateHomeFilesystemId(homeFilesystemId);
45
45
  if (process.platform !== "linux")
@@ -171,7 +171,7 @@ async function startControllerServerLocked(home, dispatcher, beforeDiscoveryRemo
171
171
  }
172
172
  }
173
173
  /**
174
- * Fence an older Controller whose socket name was derived by another protocol.
174
+ * Prevent a second Controller from claiming a Home with existing discovery.
175
175
  * A dead or PID-reused owner is stale and may be replaced; an unverifiable
176
176
  * live owner fails closed so an endpoint migration can never create dual
177
177
  * writers for one durable Home.
@@ -362,6 +362,8 @@ async function routeRequest(socket, line, token, dispatcher, stop, status, telem
362
362
  cliRealpath: controllerCliRealpath(),
363
363
  controllerRealpath: realpathSync(fileURLToPath(import.meta.url)),
364
364
  controllerProtocolVersion: FILE_TASK_CONTROLLER_PROTOCOL_VERSION,
365
+ storageVersion: yuiVersionIdentity().storageVersion,
366
+ minimumStorageVersion: yuiVersionIdentity().minimumStorageVersion,
365
367
  homeId,
366
368
  homeFilesystemId,
367
369
  controllerInstanceId,
@@ -676,7 +678,7 @@ export function buildRuntimeIdentityReceipt(input) {
676
678
  }
677
679
  })();
678
680
  return Object.freeze({
679
- schemaVersion: 2,
681
+ schemaVersion: 1,
680
682
  version: YUI_VERSION,
681
683
  executablePath: process.execPath,
682
684
  args: process.argv.slice(1),
@@ -3,7 +3,7 @@ import { validateHomeFilesystemId } from "./homeFilesystemIdentity.js";
3
3
  export const MAX_CONTROLLER_MESSAGE_BYTES = 1_048_576;
4
4
  export const CONTROLLER_DISCOVERY_PATH = "runtime/controller.json";
5
5
  /** Bump when a running Controller cannot safely share one YUI_HOME with this CLI. */
6
- export const FILE_TASK_CONTROLLER_PROTOCOL_VERSION = 4;
6
+ export const FILE_TASK_CONTROLLER_PROTOCOL_VERSION = 1;
7
7
  export class ControllerProtocolError extends Error {
8
8
  code;
9
9
  id;
@@ -694,7 +694,8 @@ function checkAgent(agent, executor, environment) {
694
694
  detail: [
695
695
  `start resume interrupt nativeSession=${snapshot.lifecycle.nativeSessionDiscovery}`,
696
696
  `preInputReady=${snapshot.lifecycle.preInputReadiness.status}`,
697
- `fields=${available}/${degraded}/${unavailable}`,
697
+ `fields(help-observed/static-or-unverified/unavailable)=${available}/${degraded}/${unavailable}`,
698
+ "installation/help inspection only; no model or account capability query",
698
699
  ...snapshot.warnings.map((warning) => `warning=${warning}`)
699
700
  ].join(" ")
700
701
  }
@@ -51,6 +51,12 @@ export function normalizedUniqueIdentities(values, label) {
51
51
  export function cloneJson(value) {
52
52
  return JSON.parse(JSON.stringify(value));
53
53
  }
54
+ /** Current envelopes accept only declared fields; no historical-field denylist. */
55
+ export function requireKnownFields(value, fields, label) {
56
+ const unknown = Object.keys(value).find(key => !fields.includes(key));
57
+ if (unknown !== undefined)
58
+ throw new Error(`${label} has unsupported field: ${unknown}.`);
59
+ }
54
60
  /**
55
61
  * True for a concrete, pinnable package version — a semver-shaped `X.Y.Z` with
56
62
  * an optional pre-release/build suffix. Rejects dist-tag sentinels (`latest`,
@@ -2,7 +2,7 @@ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
2
2
  import { requireTimestamp } from "../domain/validation.js";
3
3
  export function createTaskEvent(id, taskId, type, payload, now) {
4
4
  const event = {
5
- schemaVersion: 2,
5
+ schemaVersion: 1,
6
6
  id: requireText(id, "Task event id"),
7
7
  taskId: requireText(taskId, "Task event Task id"),
8
8
  type: requireText(type, "Task event type"),
@@ -12,8 +12,8 @@ export function createTaskEvent(id, taskId, type, payload, now) {
12
12
  return validateTaskEvent(event);
13
13
  }
14
14
  export function validateTaskEvent(event) {
15
- if (event.schemaVersion !== 2)
16
- throw new Error("Task Event must use schemaVersion 2.");
15
+ if (event.schemaVersion !== 1)
16
+ throw new Error("Task Event must use schemaVersion 1.");
17
17
  validateTaskRecordReference({ taskId: event.taskId, localId: event.id }, "event");
18
18
  requireText(event.type, "Task event type");
19
19
  if (event.payload === null || typeof event.payload !== "object" || Array.isArray(event.payload)) {
@@ -3,8 +3,8 @@ import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requ
3
3
  import { validateContextSnapshotRef } from "../context/contextSnapshot.js";
4
4
  import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
5
5
  import { MAX_SYNTHESIS_SOURCE_RUNS } from "../context/sourceRunContext.js";
6
- export const WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION = 2;
7
- export const WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION = 2;
6
+ export const WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION = 1;
7
+ export const WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION = 1;
8
8
  export const WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION = 1;
9
9
  export const MINIMUM_EXECUTION_GROUP_LANES = 2;
10
10
  export const EXECUTION_GROUP_SCHEMA_VERSION = WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION;
@@ -6,11 +6,12 @@ import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgent
6
6
  import { writeTextFileAtomically } from "../storage/durableFile.js";
7
7
  import { discoverAcpConfiguration, discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
8
8
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
9
- const SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
10
- const APPROVALS = ["untrusted", "on-request", "never"];
9
+ import { CODEX_SANDBOXES as SANDBOXES, CODEX_APPROVALS as APPROVALS, configurationFieldsFromHelp, configurationFieldWarnings, staticAgentConfigurationFields, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
11
10
  const PROBE_TIMEOUT_MS = 2_000;
12
11
  const PROBE_MAX_BYTES = 1024 * 1024;
13
- const CODEX_TESTED_THROUGH_VERSION = "0.150.1";
12
+ // Offline producer/type evidence, not a claim of live account/model testing.
13
+ // Keep the existing minimum support floors separate: see provider-protocol-contracts.md.
14
+ const AUDITED_PRODUCER_VERSIONS = { codex: "0.153.4", claude: "2.1.270" };
14
15
  class BaseAdapter {
15
16
  launchContextArgs(_input) {
16
17
  return [];
@@ -556,7 +557,7 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
556
557
  const at = now.toISOString();
557
558
  const adapter = resolveAgentAdapter(agent.adapterId);
558
559
  validateAgentBaseArguments(agent.adapterId, agent.baseArgs);
559
- const versionRun = run(agent.command, ["--version"]);
560
+ const versionRun = run(agent.command, [...agent.baseArgs, "--version"]);
560
561
  const failure = failed(versionRun);
561
562
  if (failure !== undefined) {
562
563
  const missing = versionRun.error?.code === "ENOENT";
@@ -583,7 +584,7 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
583
584
  probedAt: at
584
585
  }, fields, at, [`Installed version ${version} is not supported by adapter ${adapter.id}.`]);
585
586
  }
586
- const help = run(agent.command, ["--help"]);
587
+ const help = run(agent.command, [...agent.baseArgs, "--help"]);
587
588
  const helpFailure = failed(help);
588
589
  if (helpFailure !== undefined) {
589
590
  return snapshot(agent, adapter, {
@@ -593,114 +594,45 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
593
594
  }, fields, at);
594
595
  }
595
596
  const helpOutput = output(help.stdout, help.stderr);
596
- fields = fromHelp(agent.adapterId, helpOutput);
597
+ const helpFields = configurationFieldsFromHelp(agent.adapterId, helpOutput);
598
+ fields = capabilityFields(helpFields);
599
+ warnings.push(...configurationFieldWarnings(helpFields));
597
600
  const missing = missingRequiredCapabilities(adapter.id, helpOutput);
598
601
  if (missing.length > 0) {
599
602
  return snapshot(agent, adapter, {
600
603
  status: "unsupported-version", command: agent.command, version,
601
604
  reason: `${adapter.label} CLI is missing required capabilities: ${missing.join(", ")}.`,
602
605
  probedAt: at
603
- }, fields, at);
606
+ }, fields, at, warnings);
604
607
  }
605
- if (adapter.id === "codex"
606
- && compareVersions(version, CODEX_TESTED_THROUGH_VERSION) > 0) {
607
- warnings.push(`Installed Codex version ${version} is newer than the latest tested version `
608
- + `${CODEX_TESTED_THROUGH_VERSION}; required capabilities were detected.`);
608
+ const audited = adapter.id === "acp" ? undefined : AUDITED_PRODUCER_VERSIONS[adapter.id];
609
+ if (audited !== undefined && compareVersions(version, audited) > 0) {
610
+ warnings.push(`Installed ${adapter.label} version ${version} is newer than the latest audited producer `
611
+ + `${audited}; required CLI flags were detected, not live protocol compatibility.`);
609
612
  }
610
613
  return snapshot(agent, adapter, {
611
614
  status: "installed", command: agent.command, version, probedAt: at
612
615
  }, fields, at, warnings);
613
616
  }
614
617
  function baseline(id) {
615
- // ACP negotiates its whole configurable surface per Session, so a static
616
- // baseline can only say which axes exist — never which values one Agent
617
- // accepts. Model, effort and mode are all `degraded` for exactly that reason:
618
- // they are configurable and the enumeration comes from the live Session, so
619
- // promising values here would be inventing them.
620
- if (id === "acp")
621
- return [
622
- field("model", "enum", "degraded", true),
623
- field("effort", "enum", "degraded", true),
624
- field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
625
- // The exact mode ids belong to the Agent, and only a live Session lists
626
- // them. `configured` carries whichever id the user names.
627
- field("permission.mode", "enum", "degraded", true),
628
- // Configurable, but only reaches an Agent that advertises
629
- // `sessionCapabilities.additionalDirectories` at `initialize`. A static
630
- // baseline cannot see that handshake, so it reports the field as degraded
631
- // rather than promising delivery it cannot guarantee.
632
- field("additionalDirectories", "path-list", "degraded", true)
633
- ];
634
- if (id === "codex")
635
- return [
636
- field("model", "enum", "degraded", true), field("effort", "enum", "unavailable", true),
637
- field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
638
- field("permission.sandbox", "enum", "available", false, SANDBOXES),
639
- field("permission.approval", "enum", "available", false, APPROVALS),
640
- field("profile", "string", "available", true), field("search", "boolean", "available", false, ["true"]),
641
- field("additionalDirectories", "path-list", "available", true)
642
- ];
643
- return [
644
- field("model", "enum", "degraded", true, ["fable", "opus", "sonnet"]),
645
- field("effort", "enum", "unavailable", true),
646
- field("permission.strategy", "enum", "available", false, ["default", "bypass", "configured"]),
647
- field("permission.mode", "enum", "unavailable", true),
648
- field("permission.allowedTools", "string-list", "available", true),
649
- field("permission.disallowedTools", "string-list", "available", true),
650
- field("additionalDirectories", "path-list", "available", true), field("settingsFile", "path", "available", true),
651
- field("settingsSources", "string-list", "degraded", false, ["user", "project", "local"])
652
- ];
653
- }
654
- function fromHelp(id, help) {
655
- const fields = baseline(id);
656
- // Nothing in an ACP Agent's `--help` describes the protocol it speaks, so
657
- // there is nothing here to refine: the baseline is already the whole truth.
658
- if (id === "acp")
659
- return fields;
660
- const replacements = id === "codex"
661
- ? [permissionStrategyField(help, "--dangerously-bypass-approvals-and-sandbox"),
662
- choiceField("permission.sandbox", help, "--sandbox", SANDBOXES),
663
- choiceField("permission.approval", help, "--ask-for-approval", APPROVALS)]
664
- : [choiceField("model", help, "--model", ["fable", "opus", "sonnet"], true),
665
- choiceField("effort", help, "--effort", [], true),
666
- permissionStrategyField(help, "--dangerously-skip-permissions"),
667
- choiceField("permission.mode", help, "--permission-mode", [], true),
668
- choiceField("settingsSources", help, "--setting-sources", ["user", "project", "local"])];
669
- const byKey = new Map(replacements.map((value) => [value.key, value]));
670
- return fields.map((value) => byKey.get(value.key) ?? value);
671
- }
672
- function permissionStrategyField(help, bypassFlag) {
673
- const choices = [
674
- "default",
675
- ...(help.includes(bypassFlag) ? ["bypass"] : []),
676
- "configured"
677
- ];
678
- return field("permission.strategy", "enum", choices.includes("bypass") ? "available" : "degraded", false, choices);
679
- }
680
- function choiceField(key, help, flag, fallback, custom = false) {
681
- const choices = helpChoices(help, flag);
682
- return field(key, key === "settingsSources" ? "string-list" : "enum", choices.length > 0 ? "available" : fallback.length > 0 ? "degraded" : "unavailable", custom, choices.length > 0 ? choices : fallback);
683
- }
684
- function field(key, kind, status, allowCustom, choices) {
685
- return { key, kind, status, allowCustom, ...(choices === undefined ? {} : { choices: [...choices] }) };
686
- }
687
- function helpChoices(help, flag) {
688
- const lines = help.replace(/\r\n/g, "\n").split("\n");
689
- const start = lines.findIndex((line) => line.includes(flag));
690
- if (start < 0)
691
- return [];
692
- let end = start + 1;
693
- while (end < lines.length && !/^\s{2,}(?:-[A-Za-z](?:,\s*)?|--)[\w-]/.test(lines[end]))
694
- end += 1;
695
- const body = /(?:\[possible values:\s*([^\]]+)\]|\(choices:\s*([^)]*)\)|\(([^)]*)\))/i
696
- .exec(lines.slice(start, end).join("\n"));
697
- return [...new Set((body?.[1] ?? body?.[2] ?? body?.[3] ?? "").replace(/["']/g, "").split(",")
698
- .map((value) => value.trim().replace(/\.$/, "")).filter((value) => /^[\w.+-]+$/.test(value)))];
618
+ return capabilityFields(staticAgentConfigurationFields(id));
619
+ }
620
+ function capabilityFields(fields) {
621
+ const kinds = {
622
+ profile: "string", search: "boolean", additionalDirectories: "path-list", settingsFile: "path",
623
+ settingsSources: "string-list", "permission.allowedTools": "string-list", "permission.disallowedTools": "string-list"
624
+ };
625
+ return fields.map(field => ({
626
+ key: field.key, kind: kinds[field.key] ?? "enum", allowCustom: field.allowCustom,
627
+ status: field.available === true ? "available" : field.available === false ? "unavailable" : "degraded",
628
+ choices: field.choices.map(choice => choice.value)
629
+ }));
699
630
  }
700
631
  function snapshot(agent, adapter, installation, fields, at, warnings = []) {
701
632
  return { schemaVersion: 1, agentId: agent.id, adapterId: agent.adapterId, installation,
702
633
  lifecycle: { start: true, resume: true, nativeSessionDiscovery: adapter.capabilities.nativeSessionDiscovery,
703
- interrupt: true, preInputReadiness: adapter.capabilities.preInputReadiness }, fields, warnings, refreshedAt: at };
634
+ interrupt: true, preInputReadiness: adapter.capabilities.preInputReadiness }, fields,
635
+ warnings: [...new Set([STATIC_CONFIGURATION_NOTICE, ...warnings])], refreshedAt: at };
704
636
  }
705
637
  function runProbe(command, args) {
706
638
  const result = spawnSync(command, [...args], { encoding: "utf8", shell: false, timeout: PROBE_TIMEOUT_MS,
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { writeTextFileAtomically } from "../storage/durableFile.js";
6
6
  import { resolveAgentAdapter } from "./agentAdapter.js";
7
7
  import { redactAgentErrorText } from "../runtime/agentError.js";
8
+ import { staticAgentConfigurationFields, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
8
9
  const DEFAULT_TIMEOUT_MS = 8_000;
9
10
  export class AgentConfigurationCatalogService {
10
11
  yuiHome;
@@ -119,71 +120,14 @@ export class AgentConfigurationCatalogService {
119
120
  }
120
121
  }
121
122
  export function fallbackAgentConfigurationCatalog(agent) {
122
- const choice = (value) => ({ value, label: value });
123
- const common = [
124
- field("model", [], true),
125
- field("effort", [], true)
126
- ];
127
- // The fallback exists for when the probe could not run, so it must still be
128
- // this Agent's own shape. Falling through to Claude's fields handed the
129
- // caller a catalog whose `adapterId` contradicted the Agent it described.
130
- if (agent.adapterId === "acp") {
131
- return {
132
- schemaVersion: 1,
133
- agentId: agent.id,
134
- adapterId: "acp",
135
- models: [],
136
- fields: [
137
- ...common,
138
- field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false),
139
- // The mode ids come from a live Session, so an offline catalog can only
140
- // say the axis exists. Listing candidates here would invent an Agent's
141
- // vocabulary from a build-time guess.
142
- field("permission.mode", [], true)
143
- ],
144
- warnings: ["Runtime configuration catalog is unavailable."]
145
- };
146
- }
147
- return agent.adapterId === "codex"
148
- ? {
149
- schemaVersion: 1,
150
- agentId: agent.id,
151
- adapterId: "codex",
152
- models: [],
153
- fields: [
154
- ...common,
155
- field("permission.strategy", ["default", "bypass", "configured"].map(choice), false),
156
- field("permission.sandbox", [
157
- "read-only", "workspace-write", "danger-full-access"
158
- ].map(choice), false),
159
- field("permission.approval", [
160
- "untrusted", "on-request", "never"
161
- ].map(choice), false),
162
- field("search", [choice("true")], false),
163
- field("profile", [], true),
164
- field("additionalDirectories", [], true)
165
- ],
166
- warnings: ["Runtime configuration catalog is unavailable."]
167
- }
168
- : {
169
- schemaVersion: 1,
170
- agentId: agent.id,
171
- adapterId: "claude",
172
- models: [],
173
- fields: [
174
- ...common,
175
- field("permission.strategy", ["default", "bypass", "configured"].map(choice), false),
176
- field("permission.mode", [
177
- "acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"
178
- ].map(choice), true),
179
- field("permission.allowedTools", [], true),
180
- field("permission.disallowedTools", [], true),
181
- field("settingsSources", ["user", "project", "local"].map(choice), false),
182
- field("settingsFile", [], true),
183
- field("additionalDirectories", [], true)
184
- ],
185
- warnings: ["Runtime configuration catalog is unavailable."]
186
- };
123
+ return {
124
+ schemaVersion: 1,
125
+ agentId: agent.id,
126
+ adapterId: agent.adapterId,
127
+ models: [],
128
+ fields: staticAgentConfigurationFields(agent.adapterId),
129
+ warnings: ["Runtime configuration catalog is unavailable.", STATIC_CONFIGURATION_NOTICE]
130
+ };
187
131
  }
188
132
  export function configurationField(catalog, key) {
189
133
  return catalog.fields.find((candidate) => candidate.key === key);
@@ -232,9 +176,6 @@ export function validateAgentLaunchConfiguration(catalog, config) {
232
176
  + `Inspect current options: yui config agent capabilities ${catalog.agentId}.`);
233
177
  }
234
178
  }
235
- function field(key, choices, allowCustom) {
236
- return { key, choices, allowCustom };
237
- }
238
179
  function catalogFingerprint(input, environment) {
239
180
  const bindings = input.agent.environment.map((binding) => ({
240
181
  target: binding.target,
@@ -259,6 +200,10 @@ function catalogFingerprint(input, environment) {
259
200
  }
260
201
  : null;
261
202
  return createHash("sha256").update(JSON.stringify({
203
+ // Old derived caches may contain guessed help values. They cannot establish
204
+ // the current discovery contract. No durable record or cache shape changes.
205
+ discoveryContract: "explicit-native-enumeration",
206
+ agentId: input.agent.id,
262
207
  // The component, not just the plan: two ACP products answer the same
263
208
  // handshake differently, so a cache keyed on the plan alone would serve one
264
209
  // product's capabilities for the other.