@zq-silk/yui 0.15.7 → 0.15.9

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 (308) hide show
  1. package/ARCHITECTURE.md +194 -399
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +306 -1131
  4. package/dist/agent/adapterCatalog.js +15 -2
  5. package/dist/agent/agent.js +23 -3
  6. package/dist/agent/argumentPolicy.js +7 -1
  7. package/dist/agent/connectionPlan.js +62 -0
  8. package/dist/agent/executionComponents.js +158 -0
  9. package/dist/agent/launchEnvironment.js +31 -3
  10. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  11. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  12. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  13. package/dist/artifacts/artifactCapability.js +74 -0
  14. package/dist/artifacts/artifactCommitLock.js +249 -0
  15. package/dist/artifacts/artifactPaths.js +151 -0
  16. package/dist/artifacts/gitArtifactRef.js +146 -0
  17. package/dist/artifacts/managedGit.js +332 -0
  18. package/dist/artifacts/taskArtifactRepository.js +277 -0
  19. package/dist/brief/taskBrief.js +12 -0
  20. package/dist/cli/agentConfigurationPicker.js +13 -0
  21. package/dist/cli/commandCatalog.js +165 -74
  22. package/dist/cli/interactionCandidates.js +5 -5
  23. package/dist/cli/interactionPolicy.js +38 -8
  24. package/dist/cli/invocationRouter.js +1 -1
  25. package/dist/cli/managedDiagnostics.js +28 -0
  26. package/dist/cli/operatorWizard.js +1 -7
  27. package/dist/cli/roleOptionOrder.js +27 -0
  28. package/dist/cli/roleWizard.js +50 -14
  29. package/dist/cli/updateOrchestrator.js +1 -1
  30. package/dist/cli/updatePorts.js +3 -4
  31. package/dist/cli.js +245 -95
  32. package/dist/commands/agentCommands.js +72 -14
  33. package/dist/commands/capabilityCommands.js +9 -6
  34. package/dist/commands/configCommands.js +20 -20
  35. package/dist/commands/deliveryGuardPreflight.js +2 -2
  36. package/dist/commands/executionAuditCommands.js +24 -24
  37. package/dist/commands/globalRoleCommands.js +1 -1
  38. package/dist/commands/grantCommands.js +4 -4
  39. package/dist/commands/operatorCommands.js +34 -9
  40. package/dist/commands/projectCommands.js +4 -4
  41. package/dist/commands/resourcesCommands.js +2 -2
  42. package/dist/commands/roleConfiguration.js +25 -5
  43. package/dist/commands/roleRuntimeGuard.js +4 -5
  44. package/dist/commands/sessionCommands.js +3 -7
  45. package/dist/commands/taskActivationCommands.js +281 -0
  46. package/dist/commands/taskActor.js +28 -49
  47. package/dist/commands/taskCommands.js +1461 -720
  48. package/dist/commands/taskContextCommand.js +39 -583
  49. package/dist/commands/taskExecutionCommands.js +32 -32
  50. package/dist/commands/taskInputCommands.js +40 -104
  51. package/dist/commands/taskIntegrationCommands.js +3 -2
  52. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  53. package/dist/commands/taskNextActionCommand.js +8 -8
  54. package/dist/commands/taskOverviewCommand.js +33 -45
  55. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  56. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  57. package/dist/commands/telemetryCommands.js +36 -38
  58. package/dist/config/configCatalog.js +4 -4
  59. package/dist/config/yuiConfig.js +8 -8
  60. package/dist/context/contextSnapshot.js +10 -10
  61. package/dist/context/dispatchContext.js +11 -11
  62. package/dist/context/roleSessionContext.js +6 -3
  63. package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
  64. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  65. package/dist/context/sessionBootstrapManifest.js +21 -2
  66. package/dist/context/sourceRunContext.js +30 -0
  67. package/dist/context/taskContext.js +481 -0
  68. package/dist/context/wakeNotification.js +27 -27
  69. package/dist/controller/agentRuntimeObserver.js +21 -24
  70. package/dist/controller/capabilityBridge.js +17 -6
  71. package/dist/controller/clientRuntime.js +65 -92
  72. package/dist/controller/controller.js +120 -188
  73. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  74. package/dist/controller/jobControl.js +54 -85
  75. package/dist/controller/resourceInventory.js +8 -27
  76. package/dist/controller/resourceInventoryLinux.js +12 -13
  77. package/dist/controller/runtime.js +529 -479
  78. package/dist/controller/runtimeEventInbox.js +55 -25
  79. package/dist/controller/runtimeEventProcessor.js +22 -31
  80. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  81. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  82. package/dist/controller/runtimeObservationHook.js +14 -18
  83. package/dist/controller/sessionNotify.js +16 -24
  84. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  85. package/dist/controller/structuredProviderObservation.js +138 -99
  86. package/dist/coordination/workMailbox.js +3 -3
  87. package/dist/coordination/workMailboxQueue.js +36 -33
  88. package/dist/core/boundedRpc.js +8 -1
  89. package/dist/core/controllerClient.js +20 -1
  90. package/dist/core/controllerServer.js +4 -4
  91. package/dist/doctor/doctor.js +13 -2
  92. package/dist/domain/agentResultTransport.js +9 -9
  93. package/dist/execution/codexThreadNaming.js +2 -8
  94. package/dist/execution/executionHealth.js +51 -63
  95. package/dist/execution/reviewMainRun.js +137 -0
  96. package/dist/execution/workItemExecution.js +28 -29
  97. package/dist/execution/workItemExecutionProjection.js +99 -107
  98. package/dist/execution/workItemMainRun.js +141 -0
  99. package/dist/executor/agentAdapter.js +227 -20
  100. package/dist/executor/agentConfigurationCatalog.js +126 -4
  101. package/dist/executor/agentConfigurationProbe.js +162 -4
  102. package/dist/executor/agentExecutor.js +79 -78
  103. package/dist/executor/effectiveLaunch.js +105 -18
  104. package/dist/executor/executorRegistry.js +29 -44
  105. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  106. package/dist/executor/workspacePreflightClassification.js +16 -16
  107. package/dist/grant/capabilityGrant.js +6 -3
  108. package/dist/input/inputRequest.js +12 -10
  109. package/dist/integration/gitIntegrationService.js +4 -11
  110. package/dist/integration/integrationQueueService.js +4 -4
  111. package/dist/interaction/operatorPresentation.js +1 -1
  112. package/dist/kernel/builtinCapabilities.js +255 -12
  113. package/dist/kernel/capabilityRegistry.js +64 -18
  114. package/dist/kernel/instanceHost.js +12 -1
  115. package/dist/kernel/kernelPorts.js +2 -2
  116. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  117. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  118. package/dist/message/message.js +118 -6
  119. package/dist/message/messageContinuation.js +204 -0
  120. package/dist/observability/executionAudit.js +70 -72
  121. package/dist/observability/faultClassification.js +2 -2
  122. package/dist/observability/orchestrationMetrics.js +8 -8
  123. package/dist/operator/operatorSessionHistory.js +1 -7
  124. package/dist/output/agentConfigurationPresentation.js +8 -3
  125. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  126. package/dist/output/rolePresentation.js +54 -3
  127. package/dist/plugins/pluginChild.js +104 -0
  128. package/dist/plugins/pluginIntent.js +26 -0
  129. package/dist/plugins/pluginInterpreter.js +43 -0
  130. package/dist/plugins/pluginPackage.js +101 -0
  131. package/dist/plugins/pluginProcess.js +112 -0
  132. package/dist/plugins/pluginService.js +388 -0
  133. package/dist/profile/agentProfile.js +1 -1
  134. package/dist/repository/gitWorkspace.js +26 -4
  135. package/dist/repository/project.js +19 -4
  136. package/dist/repository/taskBaseFreshness.js +13 -13
  137. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  138. package/dist/repository/taskWorkspacePreparer.js +344 -83
  139. package/dist/resources/autoResourceGc.js +3 -3
  140. package/dist/resources/liveReferences.js +3 -3
  141. package/dist/resources/projectResource.js +75 -0
  142. package/dist/resources/projectResourceService.js +343 -0
  143. package/dist/resources/resourceDiscovery.js +6 -6
  144. package/dist/resources/resourceGc.js +1 -1
  145. package/dist/resources/resourceRegistrar.js +1 -1
  146. package/dist/resources/resourceTypes.js +1 -1
  147. package/dist/review/deltaRecheck.js +3 -3
  148. package/dist/review/reviewAcceptance.js +16 -16
  149. package/dist/review/reviewDecision.js +7 -7
  150. package/dist/review/reviewRound.js +21 -20
  151. package/dist/review/reviewerAvailability.js +2 -2
  152. package/dist/role/role.js +51 -7
  153. package/dist/role/taskRoleUpdate.js +30 -0
  154. package/dist/runtime/acpProtocol.js +425 -0
  155. package/dist/runtime/acpSession.js +731 -0
  156. package/dist/runtime/acpSessionConfiguration.js +260 -0
  157. package/dist/runtime/agentDriver.js +30 -11
  158. package/dist/runtime/agentEndpoint.js +278 -0
  159. package/dist/runtime/agentEndpointIdentity.js +86 -0
  160. package/dist/runtime/agentEndpointOwnership.js +239 -0
  161. package/dist/runtime/agentError.js +2 -10
  162. package/dist/runtime/agentHost.js +565 -314
  163. package/dist/runtime/agentRunConfiguration.js +258 -0
  164. package/dist/runtime/builtinAgentDrivers.js +134 -18
  165. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  166. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  167. package/dist/runtime/claude-process-owner +0 -0
  168. package/dist/runtime/codexAppServerRuntime.js +38 -30
  169. package/dist/runtime/codexInteractiveHost.js +41 -6
  170. package/dist/runtime/continuationManager.js +2 -6
  171. package/dist/runtime/executionEnvironment.js +30 -0
  172. package/dist/runtime/firstProgressAdvisory.js +11 -11
  173. package/dist/runtime/index.js +4 -3
  174. package/dist/runtime/jsonLineChannel.js +109 -0
  175. package/dist/runtime/launchBroker.js +91 -16
  176. package/dist/runtime/launchDiagnostics.js +2 -2
  177. package/dist/runtime/lifecycleReservation.js +10 -18
  178. package/dist/runtime/managedCaller.js +61 -17
  179. package/dist/runtime/nativeSessionControl.js +102 -0
  180. package/dist/runtime/ports.js +6 -21
  181. package/dist/runtime/processExitObservation.js +8 -7
  182. package/dist/runtime/promptEnvelope.js +17 -6
  183. package/dist/runtime/providerContinuation.js +3 -9
  184. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  185. package/dist/runtime/providerControl.js +2 -7
  186. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  187. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  188. package/dist/runtime/runtimeBinding.js +0 -1
  189. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  190. package/dist/runtime/runtimeDeadlines.js +9 -0
  191. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  192. package/dist/runtime/runtimeObservation.js +29 -65
  193. package/dist/runtime/runtimeProjection.js +43 -51
  194. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  195. package/dist/runtime/sessionLaunchRequest.js +3 -7
  196. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  197. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  198. package/dist/runtime/sessionReconciliation.js +4 -8
  199. package/dist/runtime/sessionTerminationGuard.js +70 -259
  200. package/dist/runtime/sessionTokenMetrics.js +5 -16
  201. package/dist/runtime/structuredProviderHost.js +237 -117
  202. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  203. package/dist/runtime/tmuxAdapters.js +39 -86
  204. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  205. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  206. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  207. package/dist/scheduler/ports.js +80 -9
  208. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  209. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  210. package/dist/scheduler/taskExecutionProjection.js +120 -124
  211. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  212. package/dist/scheduler/taskWake.js +11 -4
  213. package/dist/scheduler/wakeReason.js +9 -1
  214. package/dist/setup/setupCommand.js +3 -7
  215. package/dist/storage/migrations/agentRunContract.js +159 -0
  216. package/dist/storage/migrations/artifactsToGit.js +338 -0
  217. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  218. package/dist/storage/migrations/submitIntent.js +126 -0
  219. package/dist/storage/sqliteSchema.js +467 -7
  220. package/dist/storage/sqliteStore.js +355 -220
  221. package/dist/storage/storageVersions.js +1 -1
  222. package/dist/storage/storeRpc.js +10 -5
  223. package/dist/storage/taskStore.js +13 -11
  224. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  225. package/dist/surface/surfaceContributions.js +102 -0
  226. package/dist/task/completionReadiness.js +32 -6
  227. package/dist/task/deliveryGuard.js +16 -16
  228. package/dist/task/draftPlan.js +72 -12
  229. package/dist/task/nextAction.js +144 -128
  230. package/dist/task/remoteDelivery.js +6 -6
  231. package/dist/task/task.js +184 -18
  232. package/dist/task/taskActivation.js +327 -0
  233. package/dist/task/taskActivationService.js +408 -0
  234. package/dist/task/taskRecordReference.js +5 -4
  235. package/dist/task/taskRecordRetirement.js +1 -1
  236. package/dist/task/taskSubmission.js +236 -0
  237. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  238. package/dist/telemetry/telemetryConfig.js +14 -14
  239. package/dist/telemetry/telemetryWiring.js +2 -2
  240. package/dist/web/assets/assetManifest.js +2 -0
  241. package/dist/web/assets/client/app.js +121 -20
  242. package/dist/web/assets/client/components.js +87 -54
  243. package/dist/web/assets/client/i18n.js +83 -41
  244. package/dist/web/assets/client/markdown.js +1 -1
  245. package/dist/web/assets/client/taskSurface.js +442 -0
  246. package/dist/web/assets/client/view.js +49 -44
  247. package/dist/web/assets/shell.js +1 -1
  248. package/dist/web/assets/styles/cards.js +22 -4
  249. package/dist/web/controllerWeb.js +60 -0
  250. package/dist/web/webMutation.js +28 -0
  251. package/dist/web/webServer.js +133 -8
  252. package/dist/web/webSnapshot.js +81 -74
  253. package/dist/web/webTaskSurface.js +64 -0
  254. package/dist/workItem/dependencyGate.js +1 -1
  255. package/dist/workItem/workItem.js +84 -48
  256. package/dist/workspace/workItemChangeSetManager.js +16 -9
  257. package/docs/agent-result-consumption.md +96 -0
  258. package/docs/agent-result-consumption.zh-CN.md +81 -0
  259. package/docs/agent-runtime-drivers.md +93 -0
  260. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  261. package/docs/architecture/README.md +50 -0
  262. package/docs/architecture/README.zh-CN.md +43 -0
  263. package/docs/architecture/capabilities-and-resources.md +118 -0
  264. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  265. package/docs/managed-turn-and-session-runtime.md +224 -0
  266. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  267. package/docs/observability/README.md +83 -0
  268. package/docs/observability/README.zh-CN.md +71 -0
  269. package/docs/plugin-sdk.md +393 -0
  270. package/docs/plugin-sdk.zh-CN.md +293 -0
  271. package/docs/provider-runtime.md +165 -0
  272. package/docs/provider-runtime.zh-CN.md +132 -0
  273. package/docs/release-workflow.md +305 -0
  274. package/docs/release-workflow.zh-CN.md +237 -0
  275. package/docs/roles-and-configuration.md +115 -0
  276. package/docs/roles-and-configuration.zh-CN.md +96 -0
  277. package/docs/sqlite-control-plane-design.md +78 -0
  278. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  279. package/docs/task-dag-semantics.md +80 -0
  280. package/docs/task-dag-semantics.zh-CN.md +59 -0
  281. package/docs/task-delivery.md +105 -0
  282. package/docs/task-delivery.zh-CN.md +82 -0
  283. package/docs/task-local-identity.md +8 -6
  284. package/docs/task-local-identity.zh-CN.md +58 -0
  285. package/docs/testing/verification-levels.md +88 -0
  286. package/docs/testing/verification-levels.zh-CN.md +69 -0
  287. package/i18n/README.zh-CN.md +270 -722
  288. package/package.json +3 -2
  289. package/skills/yui-leader/SKILL.md +88 -304
  290. package/skills/yui-leader/references/execution.md +303 -0
  291. package/skills/yui-leader/references/integration.md +39 -0
  292. package/skills/yui-leader/references/planning.md +109 -0
  293. package/skills/yui-leader/references/replicated-execution.md +42 -0
  294. package/skills/yui-leader/references/task-plugins.md +37 -0
  295. package/skills/yui-operator/SKILL.md +46 -62
  296. package/skills/yui-reviewer/SKILL.md +35 -36
  297. package/skills/yui-runtime/SKILL.md +88 -24
  298. package/skills/yui-runtime/references/publication.md +22 -0
  299. package/skills/yui-runtime/references/recovery.md +64 -0
  300. package/skills/yui-worker/SKILL.md +37 -39
  301. package/dist/cli/roleOptionCatalog.js +0 -68
  302. package/dist/context/sourceTurnContext.js +0 -30
  303. package/dist/execution/reviewMainTurn.js +0 -161
  304. package/dist/execution/workItemMainTurn.js +0 -164
  305. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  306. package/dist/runtime/preallocatedNativeSession.js +0 -13
  307. package/dist/runtime/runtimeStopReceipt.js +0 -42
  308. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -2,6 +2,7 @@ export const VIEW_SCRIPT = `
2
2
  // Page composition: sidebar lists, overview, and the task detail scaffold.
3
3
  // All reusable widgets and cards come from components.js.
4
4
  import { clear, node } from "/assets/js/dom.js";
5
+ import { renderTaskSurface } from "/assets/js/task-surface.js";
5
6
  import { byNewest, formatDateTime } from "/assets/js/format.js";
6
7
  import {
7
8
  anchorSection,
@@ -23,14 +24,14 @@ import {
23
24
  reviewCard,
24
25
  richText,
25
26
  roleCard,
26
- turnCard,
27
+ runCard,
27
28
  sectionHead,
28
29
  taskCard,
29
30
  translatedStatus,
30
31
  workItemCard
31
32
  } from "/assets/js/components.js";
32
33
 
33
- const statuses = ["all", "active", "draft", "completed", "retired", "archived"];
34
+ const statuses = ["all", "active", "draft", "completed", "cancelled", "archived"];
34
35
 
35
36
  export function renderFilters(container, state, t, onFilter) {
36
37
  const counts = state.counts || {};
@@ -83,7 +84,7 @@ function taskGroupOf(task, attentionIds) {
83
84
  if (attentionIds.has(task.id)) return "attention";
84
85
  if (task.status === "active") return "active";
85
86
  if (task.status === "draft") return "draft";
86
- if (task.status === "retired") return "retired";
87
+ if (task.status === "cancelled") return "cancelled";
87
88
  if (task.status === "archived") return "archived";
88
89
  return "finished";
89
90
  }
@@ -102,7 +103,7 @@ export function renderTasks(container, state, t, locale, onSelect) {
102
103
  clear(container);
103
104
  const query = state.query.trim().toLocaleLowerCase(locale);
104
105
  const attentionIds = new Set((state.attention || []).map(function (item) { return item.taskId; }));
105
- const groups = { attention: [], active: [], draft: [], finished: [], retired: [], archived: [] };
106
+ const groups = { attention: [], active: [], draft: [], finished: [], cancelled: [], archived: [] };
106
107
  (state.tasks || []).forEach(function (task) {
107
108
  if (state.filter !== "all" && task.status !== state.filter) return;
108
109
  if (!taskMatchesQuery(task, query, locale)) return;
@@ -114,7 +115,7 @@ export function renderTasks(container, state, t, locale, onSelect) {
114
115
  ["active", t("group.active")],
115
116
  ["draft", t("group.draft")],
116
117
  ["finished", t("group.finished")],
117
- ["retired", t("group.retired")],
118
+ ["cancelled", t("group.cancelled")],
118
119
  ["archived", t("group.archived")]
119
120
  ];
120
121
  const firstGroup = order.find(function (entry) { return groups[entry[0]].length > 0; });
@@ -182,7 +183,7 @@ export function renderOverview(detail, state, t, locale, onSelect) {
182
183
  wrap.append(inbox);
183
184
 
184
185
  // Tasks whose Task-first projection says they need attention: blocked,
185
- // recovering, or in an attention state. This replaces the raw stalled-Turn
186
+ // recovering, or in an attention state. This replaces the raw stalled-AgentRun
186
187
  // count with the derived execution status.
187
188
  const attentionTasks = (state.tasks || []).filter(function (task) {
188
189
  const status = task.executionStatus;
@@ -232,23 +233,23 @@ export function renderError(container, message) {
232
233
  container.append(node("div", "error", message));
233
234
  }
234
235
 
235
- const TURN_PAGE_SIZE = 12;
236
+ const RUN_PAGE_SIZE = 12;
236
237
  const LIST_PAGE_SIZE = 10;
237
- const TURN_FILTERS = ["all", "active", "completed", "failed"];
238
+ const RUN_FILTERS = ["all", "active", "completed", "failed"];
238
239
 
239
- function turnFilterRow(sortedTurns, t, onChange) {
240
- const row = node("div", "filter-row turn-filter");
240
+ function runFilterRow(sortedRuns, t, onChange) {
241
+ const row = node("div", "filter-row run-filter");
241
242
  const buttons = [];
242
- TURN_FILTERS.forEach(function (status) {
243
+ RUN_FILTERS.forEach(function (status) {
243
244
  const count = status === "all"
244
- ? sortedTurns.length
245
- : sortedTurns.filter(function (turn) { return turn.status === status; }).length;
245
+ ? sortedRuns.length
246
+ : sortedRuns.filter(function (run) { return run.status === status; }).length;
246
247
  if (status !== "all" && count === 0) return;
247
248
  const btn = node("button", "filter-chip");
248
249
  btn.type = "button";
249
250
  btn.dataset.status = status;
250
251
  if (status !== "all") btn.append(node("span", "filter-dot " + status));
251
- btn.append(document.createTextNode(status === "all" ? t("status.all") : t("turn." + status)));
252
+ btn.append(document.createTextNode(status === "all" ? t("status.all") : t("run." + status)));
252
253
  btn.append(node("span", "filter-count", String(count)));
253
254
  btn.addEventListener("click", function () { onChange(status); });
254
255
  buttons.push({ status: status, element: btn });
@@ -265,6 +266,7 @@ function turnFilterRow(sortedTurns, t, onChange) {
265
266
  }
266
267
 
267
268
  export function renderTaskDetail(detail, data, t, locale, actions) {
269
+ if (data.core) return renderTaskSurface(detail, data, t, locale, actions);
268
270
  actions = actions || {};
269
271
  clear(detail);
270
272
  const task = data.task;
@@ -346,14 +348,14 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
346
348
  conclusion.append(richText(null, task.completionSummary, t));
347
349
  conclusion.append(conclusionMeta(task, t, locale, "completed"));
348
350
  summaryBody.append(conclusion);
349
- } else if (task.status === "retired" || task.retirementSummary) {
351
+ } else if (task.status === "cancelled" || task.retirementSummary) {
350
352
  const conclusion = node("div", "conclusion archived");
351
- conclusion.append(node("h3", "", t("detail.retired")));
353
+ conclusion.append(node("h3", "", t("detail.cancelled")));
352
354
  if (task.retirementSummary) conclusion.append(richText(null, task.retirementSummary, t));
353
355
  if (task.replacementTaskId) {
354
356
  conclusion.append(node("p", "muted", t("detail.replacement") + " · " + task.replacementTaskId));
355
357
  }
356
- conclusion.append(conclusionMeta(task, t, locale, "retired"));
358
+ conclusion.append(conclusionMeta(task, t, locale, "cancelled"));
357
359
  summaryBody.append(conclusion);
358
360
  } else if (task.status === "archived" || task.archiveSummary || task.archiveReason) {
359
361
  const conclusion = node("div", "conclusion archived");
@@ -377,23 +379,23 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
377
379
  attentionBody));
378
380
  }
379
381
 
380
- const stalledTurns = data.runtimeHealth && data.runtimeHealth.needsAttentionTurns
381
- ? data.runtimeHealth.needsAttentionTurns
382
+ const stalledRuns = data.runtimeHealth && data.runtimeHealth.needsAttentionRuns
383
+ ? data.runtimeHealth.needsAttentionRuns
382
384
  : [];
383
- if (stalledTurns.length) {
385
+ if (stalledRuns.length) {
384
386
  const runtimeHealthBody = node("div", "section-body");
385
- stalledTurns.forEach(function (turn) {
387
+ stalledRuns.forEach(function (run) {
386
388
  const card = node("article", "record-card");
387
389
  card.append(
388
- node("strong", "", turn.roleName + " · " + turn.turnId),
389
- node("p", "record-copy", (turn.kind || "workflow-not-progressing") + " · " + (turn.classification || "truly-stalled")),
390
- node("small", "", t("detail.lastProgress") + " · " + formatDateTime(turn.progressAt, locale))
390
+ node("strong", "", run.roleName + " · " + run.runId),
391
+ node("p", "record-copy", (run.kind || "workflow-not-progressing") + " · " + (run.classification || "truly-stalled")),
392
+ node("small", "", t("detail.lastProgress") + " · " + formatDateTime(run.progressAt, locale))
391
393
  );
392
394
  runtimeHealthBody.append(card);
393
395
  });
394
396
  scaffold.append(anchorSection(
395
397
  "detail-health",
396
- sectionHead(t("detail.runtimeHealth"), { count: stalledTurns.length }),
398
+ sectionHead(t("detail.runtimeHealth"), { count: stalledRuns.length }),
397
399
  runtimeHealthBody
398
400
  ));
399
401
  }
@@ -438,35 +440,35 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
438
440
  sectionHead(t("detail.workItems"), { count: data.workItems.length }),
439
441
  workBody));
440
442
 
441
- // 5. Turns (anchor #detail-exec) — execution grid with status filter + paging
443
+ // 5. AgentRuns (anchor #detail-exec) — execution grid with status filter + paging
442
444
  const execWrap = node("div", "section-body");
443
- const execBody = node("div", "turn-grid");
444
- const sortedTurns = data.turns.slice().sort(byNewest);
445
- if (!sortedTurns.length) {
446
- execBody.append(emptyRow(t, "empty.turns"));
445
+ const execBody = node("div", "run-grid");
446
+ const sortedRuns = data.runs.slice().sort(byNewest);
447
+ if (!sortedRuns.length) {
448
+ execBody.append(emptyRow(t, "empty.runs"));
447
449
  execWrap.append(execBody);
448
450
  } else {
449
- let activeTurnFilter = "all";
450
- const filter = turnFilterRow(sortedTurns, t, function (status) {
451
- activeTurnFilter = status;
451
+ let activeRunFilter = "all";
452
+ const filter = runFilterRow(sortedRuns, t, function (status) {
453
+ activeRunFilter = status;
452
454
  filter.sync(status);
453
- renderTurnGrid();
455
+ renderRunGrid();
454
456
  });
455
- function renderTurnGrid() {
457
+ function renderRunGrid() {
456
458
  clear(execBody);
457
- const visible = activeTurnFilter === "all"
458
- ? sortedTurns
459
- : sortedTurns.filter(function (turn) { return turn.status === activeTurnFilter; });
460
- pagedList(execBody, visible, TURN_PAGE_SIZE, function (turn) {
461
- return turnCard(turn, t, locale);
459
+ const visible = activeRunFilter === "all"
460
+ ? sortedRuns
461
+ : sortedRuns.filter(function (run) { return run.status === activeRunFilter; });
462
+ pagedList(execBody, visible, RUN_PAGE_SIZE, function (run) {
463
+ return runCard(run, t, locale);
462
464
  }, t);
463
465
  }
464
- filter.sync(activeTurnFilter);
466
+ filter.sync(activeRunFilter);
465
467
  execWrap.append(filter.row, execBody);
466
- renderTurnGrid();
468
+ renderRunGrid();
467
469
  }
468
470
  scaffold.append(anchorSection("detail-exec",
469
- sectionHead(t("detail.execution"), { count: data.turns.length }),
471
+ sectionHead(t("detail.execution"), { count: data.runs.length }),
470
472
  execWrap));
471
473
 
472
474
  // 5b. Reviews (anchor #detail-reviews) — review rounds for completed candidates
@@ -522,7 +524,10 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
522
524
  messagesBody.append(emptyRow(t));
523
525
  } else {
524
526
  pagedList(messagesBody, data.messages.slice().sort(byNewest), LIST_PAGE_SIZE, function (message) {
525
- return messageCard(message, t, locale);
527
+ const source = message.resultRef && data.runs.find(function (run) {
528
+ return run.id === message.resultRef.runId && run.roleName === message.author.roleName;
529
+ });
530
+ return messageCard(message, t, locale, source && source.result);
526
531
  }, t);
527
532
  }
528
533
  scaffold.append(anchorSection("detail-messages",
@@ -83,7 +83,7 @@ export const DASHBOARD_HTML = `<!doctype html>
83
83
  <button class="tab" type="button" data-target="detail-top" data-i18n="tabs.summary">Summary</button>
84
84
  <button class="tab" type="button" data-target="detail-focus" data-i18n="tabs.focus">Focus</button>
85
85
  <button class="tab" type="button" data-target="detail-work" data-i18n="tabs.work">Work items</button>
86
- <button class="tab" type="button" data-target="detail-exec" data-i18n="tabs.exec">Turns</button>
86
+ <button class="tab" type="button" data-target="detail-exec" data-i18n="tabs.exec">AgentRuns</button>
87
87
  <button class="tab" type="button" data-target="detail-reviews" data-i18n="tabs.reviews">Reviews</button>
88
88
  <button class="tab" type="button" data-target="detail-roles" data-i18n="tabs.roles">Roles</button>
89
89
  <button class="tab" type="button" data-target="detail-history" data-i18n="tabs.history">History</button>
@@ -1,10 +1,28 @@
1
1
  /*
2
2
  * CARDS — content-bearing surfaces.
3
3
  * Overview blocks, detail head / conclusion band, sections, record cards,
4
- * input cards, Turn cards, criteria lists. Widgets (pills, chips, metrics)
4
+ * input cards, AgentRun cards, criteria lists. Widgets (pills, chips, metrics)
5
5
  * live in widgets.ts; markdown typography lives in markdown.ts.
6
6
  */
7
7
  export const CARD_STYLES = `
8
+ .surface-json { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 36rem; overflow: auto; }
9
+ .task-surface .section-body { display:grid; gap:12px; min-width:0; }
10
+ .task-surface { grid-template-columns:minmax(0,1fr); }
11
+ .task-surface .detail-section,.task-surface .record-card,.task-surface .execute-card { min-width:0; max-width:100%; }
12
+ .task-surface .record-meta { min-width:0; overflow-wrap:anywhere; }
13
+ @media(max-width:620px){
14
+ .task-surface .record-head{flex-wrap:wrap}
15
+ .task-surface .record-title-row{flex-basis:100%}
16
+ }
17
+ .task-surface .section-body > .pill { width:max-content; }
18
+ .task-surface form,.task-surface label { display:grid; gap:10px; min-width:0; }
19
+ .task-surface label { font-size:13px; font-weight:500; }
20
+ .task-surface input,.task-surface textarea { box-sizing:border-box; width:100%; min-width:0; padding:12px; border:1px solid var(--border-strong); border-radius:var(--radius); background:var(--bg-1); color:var(--text); font:inherit; }
21
+ .task-surface textarea { min-height:96px; resize:vertical; }
22
+ .task-surface input:focus-visible,.task-surface textarea:focus-visible,.task-surface summary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
23
+ .task-surface button { min-height:40px; }
24
+ .task-surface summary { cursor:pointer; padding:8px 0; font-weight:600; }
25
+ .task-surface [role=status] { overflow-wrap:anywhere; }
8
26
  /* Overview scaffolding */
9
27
  .overview{display:grid;gap:14px;padding-top:2px}
10
28
  .overview-duo{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
@@ -109,9 +127,9 @@ details.work-item-card>summary.record-head~*{margin-top:0}
109
127
  .input-answer:hover{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft)}
110
128
  .input-form{display:flex;gap:8px;flex:1}
111
129
  .input-form input{min-width:0;flex:1;padding:8px 12px;color:var(--text);background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius);font-family:var(--font-body);font-size:12.5px}
112
- /* Execution Turns */
113
- .turn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr));gap:9px}
114
- .turn-filter{margin-bottom:3px}
130
+ /* Execution AgentRuns */
131
+ .run-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr));gap:9px}
132
+ .run-filter{margin-bottom:3px}
115
133
  .execute-card{background:var(--bg-2);border:1px solid var(--border);border-left:2px solid var(--border);border-radius:var(--radius);padding:9px 12px;display:grid;gap:6px;box-shadow:var(--shadow-card);transition:border-color var(--motion-fast)}
116
134
  .execute-card:hover{border-left-color:var(--border-strong)}
117
135
  .execute-card[data-status=active]{border-left-color:var(--accent)}
@@ -0,0 +1,60 @@
1
+ import { parseWebCommandOptions, startYuiWebServer } from "./webServer.js";
2
+ /** HTTP listener ownership only, alongside the Controller's one Kernel Host.
3
+ * The authenticated Controller control socket starts/stops the exact listener;
4
+ * no capability identity is accepted through this lifecycle API. */
5
+ export function createControllerWeb(store, dependencies) {
6
+ let current;
7
+ let starting = false;
8
+ const close = async () => {
9
+ const owned = current;
10
+ if (!owned)
11
+ return;
12
+ current = undefined;
13
+ owned.server.closeTerminals();
14
+ await new Promise((resolve, reject) => {
15
+ owned.server.close((error) => error ? reject(error) : resolve());
16
+ owned.server.closeAllConnections();
17
+ });
18
+ };
19
+ return {
20
+ close,
21
+ async dispatch(method, value) {
22
+ if (!value || typeof value !== "object" || Array.isArray(value))
23
+ throw new Error("Invalid Web listener request.");
24
+ const params = value;
25
+ if (method === "web.status") {
26
+ if (Object.keys(params).length)
27
+ throw new Error("Web status takes no arguments.");
28
+ return current ? { id: current.id, url: current.url } : null;
29
+ }
30
+ if (method === "web.stop") {
31
+ if (Object.keys(params).some((key) => key !== "id") || typeof params.id !== "string")
32
+ throw new Error("Expected Web listener id.");
33
+ if (!current)
34
+ return { stopped: false };
35
+ if (current.id !== params.id)
36
+ throw new Error("Web listener identity changed.");
37
+ await close();
38
+ return { stopped: true };
39
+ }
40
+ if (method !== "web.start" || Object.keys(params).some((key) => !["id", "host", "port"].includes(key))
41
+ || typeof params.id !== "string" || !params.id.trim()
42
+ || typeof params.host !== "string" || typeof params.port !== "number") {
43
+ throw new Error("Expected id, host and port for Web start.");
44
+ }
45
+ const options = parseWebCommandOptions(["--host", params.host, "--port", String(params.port)]);
46
+ if (starting || current)
47
+ throw new Error("A Web listener is already starting or running in this Controller.");
48
+ starting = true;
49
+ try {
50
+ const server = await startYuiWebServer(store, options, dependencies);
51
+ const url = `http://${options.host === "::1" ? "[::1]" : options.host}:${options.port}`;
52
+ current = { id: params.id, server, url };
53
+ return { id: params.id, url };
54
+ }
55
+ finally {
56
+ starting = false;
57
+ }
58
+ }
59
+ };
60
+ }
@@ -0,0 +1,28 @@
1
+ export class WebRequestRejected extends Error {
2
+ disposition = "not-submitted";
3
+ }
4
+ /** For Store-only mutations with notifications excluded: an exception from
5
+ * the callback, propagated unchanged after rollback, proves non-submission.
6
+ * Commit/rollback failures and post-commit notifications remain unknown. */
7
+ export function webLocalMutation(store, apply) {
8
+ let rejected = false;
9
+ let callbackError;
10
+ try {
11
+ return store.transaction((tx) => {
12
+ try {
13
+ return apply(tx);
14
+ }
15
+ catch (error) {
16
+ rejected = true;
17
+ callbackError = error;
18
+ throw error;
19
+ }
20
+ });
21
+ }
22
+ catch (error) {
23
+ if (rejected && error === callbackError) {
24
+ throw new WebRequestRejected(error instanceof Error ? error.message : "Mutation rejected.");
25
+ }
26
+ throw error;
27
+ }
28
+ }
@@ -2,6 +2,8 @@ import { randomBytes, timingSafeEqual } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
3
  import WebSocket, { WebSocketServer } from "ws";
4
4
  import { usageError } from "../errors/cliError.js";
5
+ import { WebRequestRejected } from "./webMutation.js";
6
+ import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
5
7
  import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
6
8
  import { buildWebDashboardSnapshot, buildWebTaskDetail } from "./webSnapshot.js";
7
9
  const MAX_JSON_BODY_BYTES = 16 * 1024;
@@ -53,12 +55,17 @@ export function createYuiWebServer(store, dependencies = {}) {
53
55
  server.on("upgrade", (request, socket, head) => {
54
56
  void handleTerminalUpgrade(request, socket, head, webSocketServer, dependencies.terminal, token);
55
57
  });
56
- server.on("close", () => {
58
+ let terminalsClosed = false;
59
+ const closeTerminals = () => {
60
+ if (terminalsClosed)
61
+ return;
62
+ terminalsClosed = true;
57
63
  for (const socket of webSocketServer.clients)
58
64
  socket.terminate();
59
65
  webSocketServer.close();
60
- });
61
- return server;
66
+ };
67
+ server.on("close", closeTerminals);
68
+ return Object.assign(server, { closeTerminals });
62
69
  }
63
70
  export async function startYuiWebServer(store, options, dependencies = {}) {
64
71
  const server = createYuiWebServer(store, dependencies);
@@ -75,7 +82,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
75
82
  setSecurityHeaders(response);
76
83
  const method = request.method ?? "GET";
77
84
  if (!isLoopbackHost(headerValue(request, "host"))) {
78
- sendJson(response, 403, { error: "Invalid Host." }, method === "HEAD");
85
+ sendJson(response, 403, { error: "Invalid Host.", disposition: "not-submitted" }, method === "HEAD");
79
86
  return;
80
87
  }
81
88
  let pathname;
@@ -83,7 +90,104 @@ async function handleHttpRequest(request, response, store, dependencies, token,
83
90
  pathname = new URL(request.url ?? "/", "http://localhost").pathname;
84
91
  }
85
92
  catch {
86
- sendJson(response, 400, { error: "Invalid URL." }, method === "HEAD");
93
+ sendJson(response, 400, { error: "Invalid URL.", disposition: "not-submitted" }, method === "HEAD");
94
+ return;
95
+ }
96
+ // The loopback page token is the actual user ingress. A request body cannot
97
+ // select Operator/Leader authority; all API reads use this boundary too.
98
+ if (pathname.startsWith("/api/") && !tokenMatches(headerValue(request, "x-yui-web-token"), token)) {
99
+ sendJson(response, 403, { error: "Invalid Yui web token.", disposition: "not-submitted" }, method === "HEAD");
100
+ return;
101
+ }
102
+ const panelTarget = /^\/api\/tasks\/([^/]+)\/panels$/.exec(pathname);
103
+ if (panelTarget && dependencies.panels) {
104
+ try {
105
+ const taskId = decodeURIComponent(panelTarget[1]);
106
+ if (method === "GET") {
107
+ sendJson(response, 200, { panels: dependencies.panels.list(taskId), observedAt: now().toISOString() }, false);
108
+ }
109
+ else if (method === "POST") {
110
+ const body = await readMutationBody(request);
111
+ if (!body || typeof body !== "object" || Array.isArray(body)
112
+ || Object.keys(body).some((key) => !["ref", "input"].includes(key))
113
+ || !("ref" in body) || !("input" in body))
114
+ throw new WebRequestRejected("Expected panel ref and input.");
115
+ const ref = body.ref;
116
+ if (!ref || typeof ref !== "object" || typeof ref.capability !== "string"
117
+ || typeof ref.contractVersion !== "string" || !ref.provider
118
+ || typeof ref.provider.id !== "string" || typeof ref.provider.generation !== "string") {
119
+ throw new WebRequestRejected("Invalid panel reference.");
120
+ }
121
+ const result = await dependencies.panels.read(taskId, ref, body.input);
122
+ sendJson(response, 200, { result, observedAt: now().toISOString() }, false);
123
+ }
124
+ else
125
+ sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, false);
126
+ }
127
+ catch (error) {
128
+ sendJson(response, 409, { error: error instanceof Error ? error.message : "Panel unavailable.",
129
+ disposition: "not-submitted" }, false);
130
+ }
131
+ return;
132
+ }
133
+ const surfaceTarget = /^\/api\/tasks\/([^/]+)\/(context|delta|inspect|metadata|messages)$/.exec(pathname);
134
+ if (surfaceTarget && dependencies.surface) {
135
+ try {
136
+ let taskId;
137
+ try {
138
+ taskId = decodeURIComponent(surfaceTarget[1]);
139
+ }
140
+ catch {
141
+ throw new WebRequestRejected("Invalid Task URL encoding.");
142
+ }
143
+ const action = surfaceTarget[2];
144
+ const query = new URL(request.url, "http://localhost").searchParams;
145
+ let value;
146
+ if (method === "GET" && action === "context") {
147
+ value = await dependencies.surface.read(taskId);
148
+ }
149
+ else if (method === "GET" && action === "delta") {
150
+ value = dependencies.surface.delta(taskId, {
151
+ after: query.get("after") ?? "",
152
+ ...(query.has("continuation") ? { continuation: query.get("continuation") } : {})
153
+ });
154
+ }
155
+ else if (method === "GET" && action === "inspect") {
156
+ value = dependencies.surface.inspect(taskId, {
157
+ store: query.get("store") ?? "", refId: query.get("ref") ?? "",
158
+ ...(query.has("digest") ? { digest: query.get("digest") } : {})
159
+ });
160
+ }
161
+ else if (method === "POST" && action === "messages") {
162
+ const body = await readMutationBody(request);
163
+ if (typeof body !== "object" || body === null || Array.isArray(body)
164
+ || Object.keys(body).some((key) => !["body", "requestId", "intent"].includes(key))
165
+ || !("requestId" in body) || typeof body.requestId !== "string" || !body.requestId.trim()
166
+ || !("body" in body) || typeof body.body !== "string")
167
+ throw new WebRequestRejected("Expected body, requestId and optional intent.");
168
+ const intent = webSubmissionIntent(body);
169
+ // requestId is threaded as the submission key (§2.3) and echoed back on the receipt.
170
+ value = { ...dependencies.surface.message(taskId, body.body, intent, body.requestId), requestId: body.requestId };
171
+ }
172
+ else if (method === "POST" && action === "metadata") {
173
+ const body = await readMutationBody(request);
174
+ if (typeof body !== "object" || body === null || Array.isArray(body)
175
+ || Object.keys(body).some((key) => !["patch", "requestId"].includes(key))
176
+ || !("requestId" in body) || typeof body.requestId !== "string" || !body.requestId.trim()
177
+ || !("patch" in body))
178
+ throw new WebRequestRejected("Expected patch and requestId only.");
179
+ value = { ...dependencies.surface.update(taskId, body.patch), requestId: body.requestId };
180
+ }
181
+ else {
182
+ sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, false);
183
+ return;
184
+ }
185
+ sendJson(response, 200, value, false);
186
+ }
187
+ catch (error) {
188
+ sendJson(response, 409, { error: error instanceof Error ? error.message : "Surface unavailable.",
189
+ disposition: error instanceof WebRequestRejected ? "not-submitted" : "unknown" }, false);
190
+ }
87
191
  return;
88
192
  }
89
193
  if (method === "GET" || method === "HEAD") {
@@ -130,7 +234,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
130
234
  }
131
235
  catch (error) {
132
236
  sendJson(response, 400, {
133
- error: error instanceof Error ? error.message : "Invalid input answer."
237
+ error: error instanceof Error ? error.message : "Invalid input answer.", disposition: "not-submitted"
134
238
  }, false);
135
239
  return;
136
240
  }
@@ -143,13 +247,14 @@ async function handleHttpRequest(request, response, store, dependencies, token,
143
247
  }
144
248
  catch (error) {
145
249
  sendJson(response, 409, {
146
- error: error instanceof Error ? error.message : "Unable to answer input request."
250
+ error: error instanceof Error ? error.message : "Unable to answer input request.",
251
+ disposition: error instanceof WebRequestRejected ? "not-submitted" : "unknown"
147
252
  }, false);
148
253
  }
149
254
  return;
150
255
  }
151
256
  response.setHeader("allow", "GET, HEAD");
152
- sendJson(response, 405, { error: "Method not allowed." }, method === "HEAD");
257
+ sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, method === "HEAD");
153
258
  }
154
259
  async function handleTerminalUpgrade(request, socket, head, webSocketServer, terminalPort, token) {
155
260
  try {
@@ -340,6 +445,26 @@ function parseWebInputAnswer(value) {
340
445
  }
341
446
  throw new Error("Exactly one non-empty choiceKey or text is required.");
342
447
  }
448
+ async function readMutationBody(request) {
449
+ try {
450
+ return await readJsonBody(request);
451
+ }
452
+ catch (error) {
453
+ throw new WebRequestRejected(error instanceof Error ? error.message : "Invalid request body.");
454
+ }
455
+ }
456
+ /** Validate an optional submission intent from a Web message body. Absent leaves
457
+ * it undefined so the shared service applies the discuss default (task-32 §2.5).
458
+ * A present-but-invalid value is rejected rather than silently downgraded. */
459
+ function webSubmissionIntent(body) {
460
+ if (!("intent" in body) || body.intent === undefined)
461
+ return undefined;
462
+ if (typeof body.intent === "string"
463
+ && TASK_SUBMISSION_INTENTS.includes(body.intent)) {
464
+ return body.intent;
465
+ }
466
+ throw new WebRequestRejected(`intent must be one of ${TASK_SUBMISSION_INTENTS.join(", ")}.`);
467
+ }
343
468
  async function readJsonBody(request) {
344
469
  const chunks = [];
345
470
  let size = 0;