@zq-silk/yui 0.15.12 → 0.16.0

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 (236) hide show
  1. package/ARCHITECTURE.md +3 -2
  2. package/ARCHITECTURE.zh-CN.md +3 -2
  3. package/dist/artifacts/artifactCommitLock.js +16 -55
  4. package/dist/artifacts/taskArtifactRepository.js +3 -3
  5. package/dist/cli/agentConfigurationPicker.js +1 -1
  6. package/dist/cli/commandCatalog.js +57 -41
  7. package/dist/cli/completion.js +18 -18
  8. package/dist/cli/completionWizard.js +1 -1
  9. package/dist/cli/dynamicCompletion.js +1 -1
  10. package/dist/cli/interactionPolicy.js +7 -3
  11. package/dist/cli/invocationAuthority.js +64 -0
  12. package/dist/cli/managedDiagnostics.js +1 -1
  13. package/dist/cli/parseRepeatable.js +35 -0
  14. package/dist/cli/updatePorts.js +26 -5
  15. package/dist/cli.js +1280 -1243
  16. package/dist/commands/agentCommands.js +4 -4
  17. package/dist/commands/capabilityCommands.js +1 -1
  18. package/dist/commands/configCommands.js +6 -57
  19. package/dist/commands/configOverview.js +2 -2
  20. package/dist/commands/durableJobCommands.js +12 -6
  21. package/dist/commands/globalRoleCommands.js +53 -29
  22. package/dist/commands/grantCommands.js +0 -3
  23. package/dist/commands/jobCommands.js +1 -4
  24. package/dist/commands/operatorCommands.js +3 -3
  25. package/dist/commands/projectCommands.js +1 -10
  26. package/dist/commands/resourcesCommands.js +10 -40
  27. package/dist/commands/roleConfiguration.js +2 -6
  28. package/dist/commands/taskActivationCommands.js +4 -8
  29. package/dist/commands/taskAgentCapabilities.js +9 -0
  30. package/dist/commands/taskCommandSupport.js +155 -0
  31. package/dist/commands/taskCommandTypes.js +1 -0
  32. package/dist/commands/taskCommands.js +271 -710
  33. package/dist/commands/taskCompletionGate.js +1 -1
  34. package/dist/commands/taskExecutionCommands.js +1 -1
  35. package/dist/commands/taskFactCommands.js +325 -0
  36. package/dist/commands/taskInputCommands.js +12 -42
  37. package/dist/commands/taskIntegrationCommands.js +17 -57
  38. package/dist/commands/taskNextActionCommand.js +7 -6
  39. package/dist/commands/taskPublicationAdoptCommand.js +3 -3
  40. package/dist/commands/taskPublicationCommands.js +1 -1
  41. package/dist/commands/taskPublicationVerifyCommand.js +1 -1
  42. package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
  43. package/dist/commands/taskUpstreamCommands.js +19 -8
  44. package/dist/commands/workflowCommands.js +6 -1
  45. package/dist/completion/completionInstaller.js +26 -26
  46. package/dist/completion/completionState.js +26 -26
  47. package/dist/completion/fileCompletionManager.js +6 -11
  48. package/dist/config/configCatalog.js +0 -2
  49. package/dist/config/timeZone.js +14 -0
  50. package/dist/config/yuiConfig.js +0 -29
  51. package/dist/context/runContextPack.js +2 -3
  52. package/dist/context/taskCatalog.js +3 -5
  53. package/dist/context/taskContext.js +139 -151
  54. package/dist/context/wakeRunReferences.js +11 -0
  55. package/dist/controller/agentCapabilities.js +58 -0
  56. package/dist/controller/agentRuntimeObserver.js +6 -11
  57. package/dist/controller/clientRuntime.js +22 -14
  58. package/dist/controller/controller.js +9 -22
  59. package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
  60. package/dist/controller/jobClient.js +4 -4
  61. package/dist/controller/jobControl.js +60 -25
  62. package/dist/controller/jobSupervisor.js +4 -4
  63. package/dist/controller/providerRetryDelivery.js +7 -7
  64. package/dist/controller/runtime.js +51 -44
  65. package/dist/controller/runtimeEventProcessor.js +0 -5
  66. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  67. package/dist/controller/taskAgentError.js +35 -0
  68. package/dist/coordination/workMailboxQueue.js +2 -24
  69. package/dist/core/controllerClient.js +2 -2
  70. package/dist/core/fileLockOwner.js +74 -0
  71. package/dist/decision/decision.js +17 -0
  72. package/dist/doctor/doctor.js +18 -9
  73. package/dist/event/taskEvent.js +12 -0
  74. package/dist/execution/workItemExecutionProjection.js +2 -2
  75. package/dist/executor/agentCapabilityConfig.js +43 -0
  76. package/dist/executor/agentConfigurationCatalog.js +49 -11
  77. package/dist/executor/agentExecutor.js +5 -8
  78. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  79. package/dist/executor/taskAgentCapabilities.js +64 -0
  80. package/dist/integration/deliveryObligation.js +1 -69
  81. package/dist/integration/gitIntegrationService.js +60 -84
  82. package/dist/integration/integrationAttempt.js +3 -14
  83. package/dist/integration/integrationSourceApplication.js +4 -12
  84. package/dist/integration/manifestTags.js +2 -2
  85. package/dist/job/durableJob.js +4 -25
  86. package/dist/job/jobAssignmentScope.js +22 -0
  87. package/dist/job/jobOperation.js +16 -0
  88. package/dist/job/jobRunner.js +2 -1
  89. package/dist/job/stepDirectory.js +15 -0
  90. package/dist/kernel/builtinCapabilities.js +15 -11
  91. package/dist/kernel/kernelPorts.js +1 -17
  92. package/dist/lifecycle/exactRunTerminalization.js +1 -9
  93. package/dist/message/inputControlResolution.js +3 -4
  94. package/dist/message/message.js +17 -20
  95. package/dist/message/messageContinuation.js +1 -1
  96. package/dist/milestone/milestone.js +11 -0
  97. package/dist/observability/orchestrationMetrics.js +24 -1
  98. package/dist/observability/runtimeIdentity.js +1 -17
  99. package/dist/output/agentConfigurationPresentation.js +2 -0
  100. package/dist/output/timePresentation.js +1 -14
  101. package/dist/plugins/pluginService.js +12 -4
  102. package/dist/release/releaseWorkflowPorts.js +45 -96
  103. package/dist/release/runtimeRelease.js +9 -15
  104. package/dist/repository/projectMaintenanceLock.js +23 -64
  105. package/dist/repository/taskBaseFreshness.js +1 -1
  106. package/dist/repository/taskWorkspaceCoordinator.js +13 -22
  107. package/dist/repository/taskWorkspacePreparer.js +29 -25
  108. package/dist/repository/workspaceCleanupInspection.js +5 -5
  109. package/dist/resources/autoResourceGc.js +4 -77
  110. package/dist/resources/liveReferences.js +7 -54
  111. package/dist/resources/resourceDiscovery.js +5 -118
  112. package/dist/resources/resourceGc.js +233 -181
  113. package/dist/resources/resourceRegistrar.js +5 -4
  114. package/dist/resources/resourceRegistry.js +3 -37
  115. package/dist/resources/resourceRegistryStore.js +13 -36
  116. package/dist/resources/sqliteResourceRegistry.js +43 -26
  117. package/dist/review/deltaRecheck.js +1 -1
  118. package/dist/review/reviewAcceptance.js +1 -1
  119. package/dist/review/reviewDecision.js +1 -1
  120. package/dist/review/reviewRound.js +8 -7
  121. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  122. package/dist/runtime/acpProtocol.js +4 -51
  123. package/dist/runtime/acpSession.js +10 -75
  124. package/dist/runtime/acpSessionConfiguration.js +1 -8
  125. package/dist/runtime/agentEndpoint.js +3 -2
  126. package/dist/runtime/agentFailureContext.js +43 -0
  127. package/dist/runtime/agentHost.js +15 -15
  128. package/dist/runtime/codexInteractiveHost.js +2 -2
  129. package/dist/runtime/index.js +0 -1
  130. package/dist/runtime/managedCaller.js +21 -1
  131. package/dist/runtime/providerErrors.js +38 -0
  132. package/dist/runtime/runtimeObservation.js +0 -5
  133. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  134. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  135. package/dist/runtime/structuredProviderHost.js +4 -41
  136. package/dist/runtime/tmuxAdapters.js +6 -8
  137. package/dist/scheduler/activeRoleRunDelivery.js +7 -7
  138. package/dist/scheduler/leaderWakeupProcessor.js +19 -6
  139. package/dist/scheduler/operatorEvent.js +12 -20
  140. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  141. package/dist/scheduler/ports.js +5 -8
  142. package/dist/scheduler/roleRunLiveness.js +4 -4
  143. package/dist/scheduler/roleRunStall.js +16 -23
  144. package/dist/scheduler/taskExecutionProjection.js +25 -33
  145. package/dist/scheduler/taskObservabilityProjection.js +0 -1
  146. package/dist/scheduler/taskWake.js +5 -10
  147. package/dist/scheduler/wakeReason.js +2 -0
  148. package/dist/scheduler/wakeupQueue.js +2 -4
  149. package/dist/setup/setupCommand.js +1 -1
  150. package/dist/storage/contextRecords.js +106 -0
  151. package/dist/storage/currentTaskStore.js +1 -1
  152. package/dist/storage/homeLayout.js +13 -17
  153. package/dist/storage/migrations/agentFailureContext.js +22 -0
  154. package/dist/storage/migrations/currentInputContract.js +86 -0
  155. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  156. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  157. package/dist/storage/migrations/integrationContinuation.js +5 -4
  158. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  159. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  160. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  161. package/dist/storage/migrations/verificationPolicy.js +74 -0
  162. package/dist/storage/migrations/workItemHistory.js +46 -0
  163. package/dist/storage/persistenceWorker.js +12 -4
  164. package/dist/storage/recordValidation.js +87 -0
  165. package/dist/storage/sqliteSchema.js +142 -1
  166. package/dist/storage/sqliteStore.js +156 -162
  167. package/dist/storage/storageSchema.js +2 -14
  168. package/dist/storage/storageVersions.js +1 -1
  169. package/dist/storage/storeRpc.js +15 -7
  170. package/dist/storage/taskStore.js +3 -7
  171. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  172. package/dist/task/completionReadiness.js +5 -24
  173. package/dist/task/draftPlan.js +0 -53
  174. package/dist/task/nextAction.js +7 -13
  175. package/dist/task/remoteDeliveryService.js +89 -0
  176. package/dist/task/taskActivation.js +2 -25
  177. package/dist/task/taskActivationService.js +0 -2
  178. package/dist/task/taskRecordReference.js +0 -1
  179. package/dist/task/taskSubmission.js +3 -14
  180. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  181. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  182. package/dist/telemetry/telemetryWiring.js +4 -3
  183. package/dist/tmux/tmuxManager.js +29 -20
  184. package/dist/verification/gateArtifact.js +15 -24
  185. package/dist/verification/gateArtifactStore.js +14 -7
  186. package/dist/verification/verificationGateService.js +29 -88
  187. package/dist/verification/verificationPlan.js +27 -72
  188. package/dist/web/assets/client/app.js +1 -1
  189. package/dist/web/assets/client/taskSurface.js +4 -4
  190. package/dist/web/webServer.js +4 -6
  191. package/dist/web/webSnapshot.js +3 -83
  192. package/dist/web/webTaskSurface.js +17 -46
  193. package/dist/workItem/workItem.js +1 -12
  194. package/docs/architecture/capabilities-and-resources.md +14 -2
  195. package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
  196. package/docs/managed-turn-and-session-runtime.md +83 -10
  197. package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
  198. package/docs/plugin-sdk.md +4 -2
  199. package/docs/provider-runtime.md +3 -1
  200. package/docs/provider-runtime.zh-CN.md +4 -2
  201. package/docs/release-workflow.md +190 -7
  202. package/docs/release-workflow.zh-CN.md +139 -5
  203. package/docs/roles-and-configuration.md +29 -0
  204. package/docs/roles-and-configuration.zh-CN.md +21 -0
  205. package/docs/sqlite-control-plane-design.md +16 -3
  206. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  207. package/docs/task-dag-semantics.md +3 -3
  208. package/docs/task-dag-semantics.zh-CN.md +2 -2
  209. package/docs/task-delivery.md +91 -5
  210. package/docs/task-delivery.zh-CN.md +72 -4
  211. package/docs/task-discovery.md +10 -11
  212. package/docs/task-discovery.zh-CN.md +8 -9
  213. package/docs/testing/verification-levels.md +145 -9
  214. package/docs/testing/verification-levels.zh-CN.md +103 -8
  215. package/package.json +1 -1
  216. package/skills/yui-leader/SKILL.md +5 -0
  217. package/skills/yui-leader/references/integration.md +9 -5
  218. package/skills/yui-leader/references/planning.md +9 -3
  219. package/skills/yui-operator/SKILL.md +15 -10
  220. package/skills/yui-runtime/SKILL.md +6 -6
  221. package/skills/yui-runtime/references/recovery.md +47 -0
  222. package/dist/agent/agentRegistry.js +0 -10
  223. package/dist/agentRun/runIdentity.js +0 -22
  224. package/dist/commands/deliveryGuardPreflight.js +0 -30
  225. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  226. package/dist/commands/taskOverviewCommand.js +0 -377
  227. package/dist/completion/completionWizard.js +0 -125
  228. package/dist/context/dispatchContext.js +0 -110
  229. package/dist/context/wakeNotification.js +0 -144
  230. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  231. package/dist/integration/integrationQueueEntry.js +0 -191
  232. package/dist/integration/integrationQueueService.js +0 -810
  233. package/dist/release/fakeReleasePorts.js +0 -55
  234. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  235. package/dist/task/deliveryGuard.js +0 -226
  236. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -2,9 +2,10 @@ import { spawn } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import { mkdir, open, rm } from "node:fs/promises";
4
4
  import { join, resolve } from "node:path";
5
- import { completeGateArtifact, createGateArtifact, gateArtifactRef, isReusableGateArtifact, parseGateArtifactRef, recordGateArtifactReuse, validateGateArtifact, verifyGateArtifactLogs } from "./gateArtifact.js";
6
- import { findGateArtifact, importGateArtifactSteps, loadGateArtifact, saveGateArtifact, touchGateArtifact } from "./gateArtifactStore.js";
7
- import { planL1JobSteps, resolveProjectVerificationPlan, resolveToolchain, selectL1Checks, toolchainDigest, verificationPlanDigest, verificationStepCommand } from "./verificationPlan.js";
5
+ import { jobStepDirectory } from "../job/stepDirectory.js";
6
+ import { completeGateArtifact, createGateArtifact, gateArtifactRef, isReusableGateArtifact, parseGateArtifactRef, verifyGateArtifactLogs } from "./gateArtifact.js";
7
+ import { findGateArtifact, importGateArtifactSteps, loadGateArtifact, saveGateArtifact } from "./gateArtifactStore.js";
8
+ import { resolveProjectVerificationPlan, resolveToolchain, toolchainDigest, verificationPlanDigest } from "./verificationPlan.js";
8
9
  /** Resolve a Project's active plan and the runtime toolchain it gates under. */
9
10
  export function resolveVerificationGate(project, _environment = process.env) {
10
11
  const plan = resolveProjectVerificationPlan(project);
@@ -13,26 +14,22 @@ export function resolveVerificationGate(project, _environment = process.env) {
13
14
  const toolchain = resolveToolchain();
14
15
  return Object.freeze({
15
16
  plan,
16
- mode: plan.mode,
17
17
  toolchain,
18
18
  planDigest: verificationPlanDigest(plan),
19
19
  toolchainDigest: toolchainDigest(plan, toolchain)
20
20
  });
21
21
  }
22
22
  export function gateIdentityForCandidate(input) {
23
- const boundary = input.level === "L2"
24
- ? {
25
- targetRef: input.targetRef ?? "master",
26
- baseHead: input.baseHead ?? input.commit
27
- }
28
- : undefined;
23
+ if (input.level !== "L2" || input.targetRef === undefined || input.baseHead === undefined) {
24
+ throw new Error("L2 verification requires its exact target ref and base head.");
25
+ }
29
26
  return Object.freeze({
30
27
  projectId: input.projectId,
31
28
  level: input.level,
32
29
  commit: input.commit,
33
30
  planDigest: input.gate.planDigest,
34
31
  toolchainDigest: input.gate.toolchainDigest,
35
- ...(boundary === undefined ? {} : { boundary })
32
+ boundary: { targetRef: input.targetRef, baseHead: input.baseHead }
36
33
  });
37
34
  }
38
35
  /**
@@ -48,6 +45,17 @@ export async function lookupReusableGateArtifact(store, identity) {
48
45
  const verification = verifyGateArtifactLogs(artifact, logs);
49
46
  return verification.ok ? artifact : null;
50
47
  }
48
+ /** Withdraw prior reusable evidence before execution. An interrupted attempt
49
+ * remains incomplete, never an excuse to return the previous success. */
50
+ export function beginGateVerification(store, identity, plan, now) {
51
+ const existing = findGateArtifact(store, identity);
52
+ const artifact = {
53
+ ...createGateArtifact(identity, { planId: plan.id, planVersion: plan.version, generator: "yui" }, now),
54
+ reuseCount: existing?.reuseCount ?? 0
55
+ };
56
+ saveGateArtifact(store, artifact, new Map());
57
+ return artifact;
58
+ }
51
59
  /**
52
60
  * Record a GateArtifact from a terminal check DurableJob. The job's step
53
61
  * logs are copied into the artifact store and bound to their digests, so the
@@ -91,38 +99,17 @@ export async function recordGateArtifactFromStepOutcomes(store, identity, plan,
91
99
  return recordGateArtifact(store, identity, plan, steps, succeeded, now);
92
100
  }
93
101
  async function recordGateArtifact(store, identity, plan, steps, succeeded, now) {
94
- const created = createGateArtifact(identity, {
95
- planId: plan.id,
96
- planVersion: plan.version,
97
- generator: "yui"
98
- }, now);
102
+ const created = beginGateVerification(store, identity, plan, now);
99
103
  const { steps: importedSteps, logs } = await importGateArtifactSteps(steps);
100
- let artifact = completeGateArtifact(created, importedSteps, succeeded ? "succeeded" : "failed", now);
101
- // Preserve shadow/reuse counters when re-recording the same identity tuple
102
- // (e.g. record mode always re-runs the gate but must not lose the potential
103
- // reuse observations from earlier runs).
104
- const existing = findGateArtifact(store, identity);
105
- if (existing !== null) {
106
- if (isReusableGateArtifact(existing) && !succeeded) {
107
- // A failed re-run must not downgrade a proven successful artifact.
108
- touchGateArtifact(store, existing);
109
- return existing;
110
- }
111
- artifact = validateGateArtifact({
112
- ...artifact,
113
- potentialReuseCount: existing.potentialReuseCount,
114
- reuseCount: existing.reuseCount,
115
- createdAt: existing.createdAt
116
- });
117
- }
104
+ const artifact = completeGateArtifact(created, importedSteps, succeeded && importedSteps.length > 0 && importedSteps.every(step => step.outcome === "passed")
105
+ ? "succeeded" : "failed", now);
118
106
  saveGateArtifact(store, artifact, logs);
119
107
  return artifact;
120
108
  }
121
109
  const GATE_STEP_TIMEOUT_MS = 30 * 60_000;
122
110
  const SIGKILL_GRACE_MS = 2_000;
123
111
  /**
124
- * Run structured gate steps in-process (L1 targeted checks and the jobless
125
- * L2 fallback). Each step gets its own log file; argv steps run without a
112
+ * Run structured gate steps in-process. Each step gets its own log file; argv steps run without a
126
113
  * shell. This is the local form of the gate; the DurableJob form is the
127
114
  * Controller-owned one used by production Integration.
128
115
  */
@@ -144,9 +131,9 @@ export async function runGateStepsInProcess(workspace, steps, environment, logsD
144
131
  const output = await open(absoluteLogPath, "w", 0o600);
145
132
  let timedOut = false;
146
133
  let child;
147
- const stepCwd = step.cwd ?? cwd;
148
134
  const stepEnv = step.env === undefined ? env : { ...env, ...step.env };
149
135
  try {
136
+ const stepCwd = jobStepDirectory(cwd, step.cwd);
150
137
  if (step.argv !== undefined) {
151
138
  const [file, ...args] = step.argv;
152
139
  child = spawn(file, args, {
@@ -228,7 +215,7 @@ function sanitizeLogName(name) {
228
215
  * environment/registry failure is never recorded as a Candidate test
229
216
  * failure; the Integration gate has not started when bootstrap fails.
230
217
  */
231
- export function checkResultsFromGateJob(job, home) {
218
+ export function checkResultsFromGateJob(job, _home) {
232
219
  const results = new Map((job.result?.steps ?? []).map((step) => [step.name, step]));
233
220
  const checks = [];
234
221
  for (const step of job.steps) {
@@ -285,6 +272,10 @@ export function checkResultsFromGateArtifact(artifact, reused = false) {
285
272
  outcome: step.outcome,
286
273
  ...(step.logPath === undefined ? {} : { logPath: step.logPath })
287
274
  }));
275
+ if (!isReusableGateArtifact(artifact) && !checks.some(check => check.outcome === "failed")) {
276
+ checks.push({ name: "verification-result", outcome: "failed",
277
+ details: `Verification is ${artifact.status}/${artifact.outcome}; successful evidence is not established.` });
278
+ }
288
279
  if (reused) {
289
280
  checks.push({
290
281
  name: gateArtifactRef(artifact.key),
@@ -331,56 +322,6 @@ export async function verifyGateArtifactForReview(store, projectId, key, expecte
331
322
  }
332
323
  return { ok: true, artifact };
333
324
  }
334
- /**
335
- * Enforce mode (rollout step 4): reject ad-hoc full-suite shell checks that
336
- * duplicate the plan's L2 steps for a configured Project. Explicit targeted
337
- * diagnostic checks (anything that is not an L2 step command) stay allowed.
338
- */
339
- export function assertNoAdHocFullSuiteChecks(plan, checkCommands) {
340
- const l2Commands = new Set(plan.l2.steps.map((step) => verificationStepCommand(step)));
341
- for (const command of checkCommands) {
342
- if (l2Commands.has(command)) {
343
- throw new Error(`Ad-hoc full-suite check is rejected for plan-enabled Project `
344
- + `${plan.id}: use the VerificationPlan L2 gate (or pass a targeted `
345
- + `diagnostic command that is not an L2 step): ${command}`);
346
- }
347
- }
348
- }
349
- /**
350
- * Run an L1 gate for a change: select the affected categories' checks, run
351
- * them in-process, and record the L1 artifact. Reuse mode returns an existing
352
- * successful artifact for the same tuple.
353
- */
354
- export async function runL1Gate(input) {
355
- const identity = gateIdentityForCandidate({
356
- projectId: input.projectId,
357
- gate: input.gate,
358
- level: "L1",
359
- commit: input.commit
360
- });
361
- if (input.gate.mode !== "record") {
362
- const existing = await lookupReusableGateArtifact(input.store, identity);
363
- if (existing !== null) {
364
- const reused = recordGateArtifactReuse(existing, input.now);
365
- touchGateArtifact(input.store, reused);
366
- return {
367
- artifact: reused,
368
- reused: true,
369
- checks: checkResultsFromGateArtifact(reused, true)
370
- };
371
- }
372
- }
373
- const selected = selectL1Checks(input.gate.plan, input.changedPaths);
374
- const outcomes = await runGateStepsInProcess(input.workspace, planL1JobSteps(selected), input.environment, input.logsDirectory, input.commit);
375
- const succeeded = outcomes.length > 0
376
- && outcomes.every((outcome) => outcome.exitCode === 0 && outcome.signal === null && !outcome.timedOut);
377
- const artifact = await recordGateArtifactFromStepOutcomes(input.store, identity, input.gate.plan, outcomes, succeeded, input.now);
378
- return {
379
- artifact,
380
- reused: false,
381
- checks: checkResultsFromGateArtifact(artifact, false)
382
- };
383
- }
384
325
  /** Stable digest of a log file, for ad-hoc evidence binding. */
385
326
  export function digestLogContent(content) {
386
327
  return createHash("sha256").update(content).digest("hex");
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
2
3
  import { requireIdentity, requirePositiveInteger, requireText } from "../domain/validation.js";
3
4
  /**
4
5
  * Issue 08: the per-Project VerificationPlan contract.
@@ -10,7 +11,7 @@ import { requireIdentity, requirePositiveInteger, requireText } from "../domain/
10
11
  * explicit `--check <shell>` path, which is marked unstructured and is not
11
12
  * reusable across stages.
12
13
  */
13
- export const VERIFICATION_PLAN_SCHEMA_VERSION = 1;
14
+ export const VERIFICATION_PLAN_SCHEMA_VERSION = 2;
14
15
  export const VERIFICATION_PLAN_KIND = "verification-plan";
15
16
  /** The reserved knowledge marker that carries a Project's VerificationPlan. */
16
17
  export const VERIFICATION_PLAN_KNOWLEDGE_KIND = VERIFICATION_PLAN_KIND;
@@ -22,25 +23,21 @@ export function normalizeVerificationPlan(raw) {
22
23
  if (record.kind !== VERIFICATION_PLAN_KIND) {
23
24
  throw new Error(`VerificationPlan kind must be "${VERIFICATION_PLAN_KIND}".`);
24
25
  }
25
- const schemaVersion = record.schemaVersion ?? VERIFICATION_PLAN_SCHEMA_VERSION;
26
+ const schemaVersion = record.schemaVersion;
26
27
  if (schemaVersion !== VERIFICATION_PLAN_SCHEMA_VERSION) {
27
28
  throw new Error(`VerificationPlan schemaVersion must be ${VERIFICATION_PLAN_SCHEMA_VERSION}.`);
28
29
  }
29
- const mode = record.mode ?? "record";
30
- if (mode !== "record" && mode !== "reuse" && mode !== "enforce") {
31
- throw new Error(`VerificationPlan mode is invalid: ${String(mode)}.`);
32
- }
30
+ if (Object.hasOwn(record, "mode"))
31
+ throw new Error("VerificationPlan mode is retired; request an explicit rerun on the operation.");
32
+ if (Object.hasOwn(record, "l1"))
33
+ throw new Error("VerificationPlan l1 is retired; declare current checks in l2.");
33
34
  const plan = {
34
35
  schemaVersion: VERIFICATION_PLAN_SCHEMA_VERSION,
35
36
  kind: VERIFICATION_PLAN_KIND,
36
37
  id: requireIdentity(record.id, "VerificationPlan id"),
37
38
  version: requireText(record.version, "VerificationPlan version"),
38
- mode,
39
39
  toolchain: normalizeToolchain(record.toolchain),
40
40
  bootstrap: normalizeSteps(record.bootstrap, "bootstrap"),
41
- l1: {
42
- categories: normalizeL1Categories(record.l1)
43
- },
44
41
  l2: {
45
42
  steps: normalizeSteps(record.l2?.steps, "l2")
46
43
  },
@@ -126,34 +123,6 @@ function normalizeStepEnv(raw, label) {
126
123
  }
127
124
  return Object.freeze(env);
128
125
  }
129
- function normalizeL1Categories(raw) {
130
- if (raw === undefined)
131
- return Object.freeze([]);
132
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
133
- throw new Error("VerificationPlan l1 must be an object.");
134
- }
135
- const categoriesRaw = raw.categories;
136
- if (!Array.isArray(categoriesRaw)) {
137
- throw new Error("VerificationPlan l1 categories must be an array.");
138
- }
139
- const ids = new Set();
140
- return Object.freeze(categoriesRaw.map((entry) => {
141
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
142
- throw new Error("VerificationPlan l1 category must be an object.");
143
- }
144
- const record = entry;
145
- const id = requireIdentity(record.id, "VerificationPlan l1 category id");
146
- if (ids.has(id)) {
147
- throw new Error(`VerificationPlan l1 category ids must be unique: ${id}.`);
148
- }
149
- ids.add(id);
150
- return Object.freeze({
151
- id,
152
- paths: normalizedTextList(record.paths, `VerificationPlan l1 category ${id} paths`),
153
- checks: normalizeSteps(record.checks, `l1 ${id}`)
154
- });
155
- }));
156
- }
157
126
  function normalizedTextList(raw, label) {
158
127
  if (!Array.isArray(raw)) {
159
128
  throw new Error(`${label} must be an array.`);
@@ -169,16 +138,18 @@ function normalizedTextList(raw, label) {
169
138
  }
170
139
  /**
171
140
  * The stable digest of the gate contract. It deliberately excludes the
172
- * rollout `mode`, retention window, and documentation fields: changing the
173
- * rollout switch must not invalidate proven gate evidence.
141
+ * retention window and documentation fields. Execution semantics belong to
142
+ * the identity so evidence from a prior contract is never silently reused.
174
143
  */
175
144
  export function verificationPlanDigest(plan) {
176
145
  const canonical = canonicalJson({
146
+ // Old artifacts may have skipped shell commands or run in the wrong cwd.
147
+ // Keep that history, but never reuse it as proof under corrected semantics.
148
+ executionContract: "workspace-argv-or-shell/clean-candidate/l2-only/v5",
177
149
  id: plan.id,
178
150
  version: plan.version,
179
151
  toolchain: plan.toolchain,
180
152
  bootstrap: plan.bootstrap,
181
- l1: plan.l1,
182
153
  l2: plan.l2,
183
154
  ...(plan.l3 === undefined ? {} : { l3: plan.l3 })
184
155
  });
@@ -208,46 +179,30 @@ export function toolchainDigest(plan, toolchain) {
208
179
  });
209
180
  return createHash("sha256").update(canonical).digest("hex");
210
181
  }
211
- /**
212
- * Select the L1 checks for a change: every category whose path prefixes match
213
- * a changed path. A category with no path prefixes matches nothing (it must
214
- * be selected explicitly by the caller).
215
- */
216
- export function selectL1Checks(plan, changedPaths) {
217
- const selected = new Map();
218
- for (const category of plan.l1.categories) {
219
- if (category.paths.length === 0)
220
- continue;
221
- const matches = changedPaths.some((path) => category.paths.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)));
222
- if (!matches)
223
- continue;
224
- for (const check of category.checks) {
225
- if (!selected.has(check.name))
226
- selected.set(check.name, check);
227
- }
228
- }
229
- return Object.freeze([...selected.values()]);
230
- }
231
182
  /** Bootstrap steps as DurableJob steps, named `bootstrap-N`. */
232
- export function planBootstrapJobSteps(plan) {
233
- return plan.bootstrap.map((step, index) => toDurableJobStep(step, `bootstrap-${index + 1}`));
183
+ export function planBootstrapJobSteps(plan, workspace) {
184
+ return plan.bootstrap.map((step, index) => toDurableJobStep(step, `bootstrap-${index + 1}`, workspace));
234
185
  }
235
186
  /** L2 gate steps as DurableJob steps, named `gate-N`. */
236
- export function planL2JobSteps(plan) {
237
- return plan.l2.steps.map((step, index) => toDurableJobStep(step, `gate-${index + 1}`));
187
+ export function planL2JobSteps(plan, workspace) {
188
+ return plan.l2.steps.map((step, index) => toDurableJobStep(step, `gate-${index + 1}`, workspace));
238
189
  }
239
- /** L1 steps as DurableJob steps, named `l1-N`. */
240
- export function planL1JobSteps(steps) {
241
- return steps.map((step, index) => toDurableJobStep(step, `l1-${index + 1}`));
242
- }
243
- function toDurableJobStep(step, name) {
190
+ function toDurableJobStep(step, name, workspace) {
191
+ if (!isAbsolute(workspace))
192
+ throw new Error("Verification workspace must be absolute.");
193
+ const cwd = step.cwd === undefined ? undefined : resolve(workspace, step.cwd);
194
+ const nested = cwd === undefined ? "" : relative(workspace, cwd);
195
+ if (step.cwd !== undefined && (isAbsolute(step.cwd) || nested === ".."
196
+ || nested.startsWith(`..${sep}`) || isAbsolute(nested))) {
197
+ throw new Error(`Verification step cwd is outside its workspace: ${step.name}.`);
198
+ }
244
199
  return {
245
200
  name,
246
201
  // The shell-equivalent command stays as the human-readable fallback and
247
202
  // the coverage-matching string; argv is the executable form.
248
203
  command: step.shell === true ? step.argv.join(" ") : shellQuote(step.argv),
249
- argv: step.argv,
250
- ...(step.cwd === undefined ? {} : { cwd: step.cwd }),
204
+ ...(step.shell === true ? {} : { argv: step.argv }),
205
+ ...(cwd === undefined ? {} : { cwd }),
251
206
  ...(step.env === undefined ? {} : { env: step.env })
252
207
  };
253
208
  }
@@ -504,7 +504,7 @@ async function refreshDashboard(options) {
504
504
  }
505
505
  const previousInputs = state.counts ? state.counts.openInputs : null;
506
506
  try {
507
- const query = new URLSearchParams({ view: "compact" });
507
+ const query = new URLSearchParams();
508
508
  if (state.catalogAll) query.set("all", "true");
509
509
  if (state.filter !== "all") query.set("status", state.filter);
510
510
  if (state.query.trim()) query.set("search", state.query.trim());
@@ -527,11 +527,11 @@ function renderSubmissionFacets(container, submission, say) {
527
527
  const step = submission.nextStep;
528
528
  if (step) {
529
529
  const nextText = step.kind === "activate-manually"
530
- ? say("Next: activate this Task explicitly (yui task activate " + step.taskId + ").",
531
- "下一步:显式激活该 Task(yui task activate " + step.taskId + ")。")
530
+ ? say("Next: request activation with an explicit environment (yui task activation request " + step.taskId + " --request-id <id> --environment <plan>).",
531
+ "下一步:明确环境并请求激活(yui task activation request " + step.taskId + " --request-id <id> --environment <plan>)。")
532
532
  : step.kind === "start-execution"
533
- ? say("Next: start execution first, then activate (" + step.taskId + ").",
534
- "下一步:先启动执行,再激活(" + step.taskId + ")。")
533
+ ? say("Next: start execution first, then request activation (" + step.taskId + ").",
534
+ "下一步:先启动执行,再请求激活(" + step.taskId + ")。")
535
535
  : step.kind === "await-pending-activation"
536
536
  ? say("Next: waiting on the pending activation " + step.activationRef + ".",
537
537
  "下一步:等待待处理的激活 " + step.activationRef + "。")
@@ -6,7 +6,7 @@ import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
6
6
  import { WebRequestRejected } from "./webMutation.js";
7
7
  import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
8
8
  import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
9
- import { buildWebDashboardSnapshot, buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
9
+ import { buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
10
10
  const MAX_JSON_BODY_BYTES = 16 * 1024;
11
11
  const MAX_TERMINAL_MESSAGE_BYTES = 64 * 1024;
12
12
  const MAX_TERMINAL_BUFFERED_BYTES = 1024 * 1024;
@@ -238,11 +238,9 @@ async function handleHttpRequest(request, response, store, dependencies, token,
238
238
  }
239
239
  else if (pathname === "/api/dashboard") {
240
240
  const query = new URL(request.url, "http://localhost").searchParams;
241
- const snapshot = query.has("view")
242
- ? buildWebTaskCatalog(store, parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
243
- ? (value === "true" ? ["--all"] : ["--invalid-all"])
244
- : [`--${key}`, value])))
245
- : buildWebDashboardSnapshot(store, now());
241
+ const snapshot = buildWebTaskCatalog(store, parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
242
+ ? (value === "true" ? ["--all"] : ["--invalid-all"])
243
+ : [`--${key}`, value])));
246
244
  sendJson(response, 200, snapshot, method === "HEAD");
247
245
  }
248
246
  else if (pathname.startsWith("/api/tasks/")) {
@@ -14,57 +14,6 @@ import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
14
14
  export function buildWebTaskCatalog(store, options) {
15
15
  return store.transaction(reader => readTaskCatalog(reader, options));
16
16
  }
17
- export function buildWebDashboardSnapshot(store, now = new Date()) {
18
- return store.transaction((reader) => {
19
- const statusCounts = {
20
- draft: 0,
21
- active: 0,
22
- completed: 0,
23
- cancelled: 0,
24
- archived: 0
25
- };
26
- const projectNames = new Map(reader.listProjects().map((project) => [project.id, project.name]));
27
- let openInputs = 0;
28
- const attention = [];
29
- const tasks = reader.listTasks().map((task) => {
30
- statusCounts[task.status] += 1;
31
- const taskOpenInputs = reader.listInputRequests(task.id)
32
- .filter((request) => request.status === "open").length;
33
- if (task.status !== "archived")
34
- openInputs += taskOpenInputs;
35
- const taskOpen = reader.listInputRequests(task.id)
36
- .filter((request) => request.status === "open");
37
- for (const request of task.status === "archived" ? [] : taskOpen) {
38
- attention.push({ taskId: task.id, taskTitle: task.title, request });
39
- }
40
- const events = reader.listEvents?.(task.id) ?? [];
41
- const needsAttentionCount = task.status === "archived" ? 0 : reader.listRuns(task.id)
42
- .filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
43
- .length;
44
- const execution = buildTaskExecutionProjection(reader, task.id, task, now);
45
- const names = task.projectBindings.flatMap(({ projectId }) => {
46
- const name = projectNames.get(projectId);
47
- return name === undefined ? [] : [name];
48
- });
49
- return {
50
- ...task,
51
- ...(names.length === 0 ? {} : { projectNames: names }),
52
- workItems: countWorkItems(reader.listWorkItems(task.id)),
53
- roleCount: reader.listRoles(task.id).length,
54
- openInputCount: taskOpenInputs,
55
- needsAttentionCount,
56
- execution,
57
- executionStatus: execution?.status ?? null
58
- };
59
- }).sort(compareDashboardTasks);
60
- return {
61
- generatedAt: now.toISOString(),
62
- counts: { total: tasks.length, ...statusCounts, openInputs },
63
- attention: attention.sort(compareAttention),
64
- tasks
65
- };
66
- });
67
- }
68
17
  export function buildWebTaskDetail(store, taskId, now = new Date()) {
69
18
  return store.transaction((reader) => {
70
19
  const task = reader.getTask(taskId);
@@ -77,7 +26,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
77
26
  return name === undefined ? [] : [name];
78
27
  });
79
28
  const runs = reader.listRuns(taskId);
80
- const events = reader.listEvents?.(taskId) ?? [];
29
+ const events = reader.listEvents(taskId);
81
30
  const retiredMessageIds = retiredTaskRecordIds(events, "message");
82
31
  const needsAttentionRuns = runs
83
32
  .filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
@@ -128,7 +77,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
128
77
  && effectiveLaunch.sourceDesiredRevision !== role.launchRevision
129
78
  };
130
79
  });
131
- const execution = buildTaskExecutionProjection(reader, taskId, task, now);
80
+ const execution = buildTaskExecutionProjection(reader, taskId, now);
132
81
  if (execution === null)
133
82
  return null;
134
83
  const remoteDelivery = webRemoteDelivery(reader, task);
@@ -166,7 +115,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
166
115
  function webRemoteDelivery(reader, task) {
167
116
  return projectTaskRemoteDelivery({
168
117
  task,
169
- events: reader.listEvents?.(task.id) ?? [],
118
+ events: reader.listEvents(task.id),
170
119
  publications: reader.listPublicationReferences(task.id),
171
120
  managedWorkspaces: reader.listManagedWorkspaces(task.id),
172
121
  runs: reader.listRuns(task.id),
@@ -267,32 +216,3 @@ function latestStallField(events, runId, field) {
267
216
  .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
268
217
  return stalled?.payload[field];
269
218
  }
270
- function countWorkItems(items) {
271
- const counts = {
272
- total: items.length,
273
- open: 0,
274
- accepted: 0,
275
- retired: 0
276
- };
277
- const mutable = counts;
278
- for (const item of items) {
279
- mutable[item.status] += 1;
280
- }
281
- return counts;
282
- }
283
- function compareDashboardTasks(left, right) {
284
- const statusOrder = {
285
- active: 0,
286
- draft: 1,
287
- completed: 2,
288
- cancelled: 3,
289
- archived: 4
290
- };
291
- return statusOrder[left.status] - statusOrder[right.status]
292
- || Date.parse(right.updatedAt) - Date.parse(left.updatedAt)
293
- || left.id.localeCompare(right.id);
294
- }
295
- function compareAttention(left, right) {
296
- return Date.parse(left.request.createdAt) - Date.parse(right.request.createdAt)
297
- || left.request.id.localeCompare(right.request.id);
298
- }
@@ -1,37 +1,17 @@
1
- import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
2
- import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
3
- import { runGlobalRoleCommand } from "../commands/globalRoleCommands.js";
4
- import { readTaskContext, readTaskContextDelta, inspectTaskContext, withContextObservations } from "../context/taskContext.js";
1
+ import { applyGlobalInputControl } from "../commands/globalRoleCommands.js";
2
+ import { applyTaskInputControl, sendTaskMessageCommand, updateTaskMetadataCommand } from "../commands/taskCommands.js";
3
+ import { runTaskInputCommand } from "../commands/taskInputCommands.js";
4
+ import { inspectTaskContext, readTaskContext, readTaskContextDelta, withContextObservations } from "../context/taskContext.js";
5
5
  import { BUILTIN_CAPABILITIES } from "../kernel/builtinCapabilities.js";
6
6
  import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
7
- import { updateTaskMetadataCommand, sendTaskMessageCommand, runTaskCommand } from "../commands/taskCommands.js";
7
+ import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
8
+ import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
9
+ import { AGENT_HOST_CONTROL_PROTOCOL, foldInterruptLiveReceipt, foldSteerLiveReceipt, sendAgentHostCancelControl, sendAgentHostSteerControl } from "../runtime/agentHost.js";
8
10
  import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
9
- import { runTaskInputCommand } from "../commands/taskInputCommands.js";
10
- import { sendAgentHostSteerControl, sendAgentHostCancelControl, AGENT_HOST_CONTROL_PROTOCOL, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "../runtime/agentHost.js";
11
11
  const DEFAULT_WEB_HOST_CONTROL = {
12
12
  steer: sendAgentHostSteerControl,
13
13
  cancel: sendAgentHostCancelControl
14
14
  };
15
- /** The store-only CLI argv for the shared application-layer primitive
16
- * (decision-3 §7). The Web surface never re-implements the queue/steer/interrupt
17
- * decisions; it drives the exact same command the CLI drives. */
18
- function controlArgv(taskId, input) {
19
- if (input.action === "queue") {
20
- return ["message", "queue", taskId, input.body, "--request-id", input.requestId,
21
- ...(input.to === undefined ? [] : ["--to", input.to]),
22
- ...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
23
- ...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
24
- }
25
- if (input.action === "steer") {
26
- return ["message", "steer", taskId, input.body, "--request-id", input.requestId,
27
- "--expected-target", input.expectedTarget, "--to", input.to,
28
- ...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
29
- ...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
30
- }
31
- return ["role", "interrupt", taskId, input.role, "--expected-target", input.expectedTarget,
32
- ...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage]),
33
- "--request-id", input.requestId];
34
- }
35
15
  /** A queue is delivered to the Leader mailbox only when it is unaddressed or
36
16
  * addressed to the Leader; an addressed Worker/Reviewer queue goes to the Task
37
17
  * mailbox. A steer/interrupt is a live control op, so it only reconciles the
@@ -53,12 +33,8 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
53
33
  // Notifications are after the outer transaction. Their failure must not be
54
34
  // reclassified as a rejection of a mutation that already committed.
55
35
  const notify = (taskId, leader = false) => {
56
- if (options.runtime?.notifyMailboxChanged) {
57
- void options.runtime.notifyMailboxChanged(leader
58
- ? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
59
- }
60
- else
61
- options.runtime?.notifyStateChanged(taskId);
36
+ void options.runtime?.notifyMailboxChanged(leader
37
+ ? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
62
38
  };
63
39
  return {
64
40
  globalState: (roleName) => {
@@ -79,20 +55,15 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
79
55
  };
80
56
  },
81
57
  globalControl: async (roleName, input) => {
82
- const argv = input.action === "interrupt"
83
- ? ["interrupt", roleName, "--request-id", input.requestId, "--expected-target", input.expectedTarget,
84
- ...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage])]
85
- : ["message", input.action, roleName, input.body, "--request-id", input.requestId,
86
- ...(input.action === "steer" ? ["--expected-target", input.expectedTarget] : [])];
87
- const result = webLocalMutation(store, tx => runGlobalRoleCommand(argv, tx, {
88
- env: {}, yuiHome: options.yuiHome, jsonOutput: true
58
+ const result = webLocalMutation(store, tx => applyGlobalInputControl(roleName, input, tx, {
59
+ env: {}, yuiHome: options.yuiHome
89
60
  }));
90
- if (typeof result === "string") {
61
+ if (result.kind === "output") {
91
62
  if (input.action === "queue")
92
- void options.runtime?.notifyMailboxChanged?.({
63
+ void options.runtime?.notifyMailboxChanged({
93
64
  kind: "global-role-runtime", roleName
94
65
  });
95
- return { action: input.action, ...JSON.parse(result) };
66
+ return { action: input.action, ...result.data };
96
67
  }
97
68
  if (result.kind !== "input-steer" && result.kind !== "input-interrupt") {
98
69
  throw new WebRequestRejected("Global input cannot perform a Session lifecycle operation.");
@@ -138,7 +109,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
138
109
  message: (taskId, body, intent, requestId) => {
139
110
  // Preserve the submission's intent and frozen receipt separately from
140
111
  // queue/steer controls; an omitted intent still means discuss.
141
- const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, undefined, commandOptions, undefined, intent, requestId));
112
+ const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, commandOptions, undefined, intent, requestId));
142
113
  notify(taskId, queuedForLeader);
143
114
  return { record: message, revision: message.createdAt,
144
115
  disposition: queuedForLeader ? "queued" : "saved",
@@ -149,7 +120,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
149
120
  /**
150
121
  * The decision-3 three-action input-control path for the local-user Web
151
122
  * surface. Its store-only phase is the identical shared application-layer
152
- * primitive the CLI uses (`runTaskCommand`), run inside `webLocalMutation`
123
+ * typed primitive the CLI uses, run inside `webLocalMutation`
153
124
  * so a rejected input is provably not-submitted. A ready steer/interrupt
154
125
  * returns a live intent; the single Agent Host edge then runs OUTSIDE the
155
126
  * transaction exactly as cli.ts performs it — never a fallback, retarget, or
@@ -158,7 +129,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
158
129
  * the durable Message is retained (decision-3 §1/§3/§5, message-5 gap F).
159
130
  */
160
131
  control: async (taskId, input) => {
161
- const execution = webLocalMutation(store, (tx) => runTaskCommand(controlArgv(taskId, input), tx, commandOptions));
132
+ const execution = webLocalMutation(store, (tx) => applyTaskInputControl(taskId, input, tx, commandOptions));
162
133
  if (execution.kind === "output") {
163
134
  // A queue receipt, or a steer/interrupt that was saved-but-not-delivered
164
135
  // or an idempotent replay: fully durable, no live edge, exact disposition.