@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.
- package/ARCHITECTURE.md +194 -399
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +306 -1131
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +165 -74
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +245 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +34 -9
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +281 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1461 -720
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +481 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +120 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +255 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +118 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +388 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +75 -0
- package/dist/resources/projectResourceService.js +343 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +3 -7
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/sqliteSchema.js +467 -7
- package/dist/storage/sqliteStore.js +355 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +327 -0
- package/dist/task/taskActivationService.js +408 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/task/taskSubmission.js +236 -0
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +121 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +442 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +133 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +84 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +96 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +93 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +50 -0
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -0
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +224 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +83 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +393 -0
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +165 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +305 -0
- package/docs/release-workflow.zh-CN.md +237 -0
- package/docs/roles-and-configuration.md +115 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +78 -0
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -0
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +105 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +8 -6
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +88 -0
- package/docs/testing/verification-levels.zh-CN.md +69 -0
- package/i18n/README.zh-CN.md +270 -722
- package/package.json +3 -2
- package/skills/yui-leader/SKILL.md +88 -304
- package/skills/yui-leader/references/execution.md +303 -0
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +37 -0
- package/skills/yui-operator/SKILL.md +46 -62
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- 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
|
-
|
|
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", "
|
|
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 === "
|
|
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: [],
|
|
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
|
-
["
|
|
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-
|
|
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
|
|
236
|
+
const RUN_PAGE_SIZE = 12;
|
|
236
237
|
const LIST_PAGE_SIZE = 10;
|
|
237
|
-
const
|
|
238
|
+
const RUN_FILTERS = ["all", "active", "completed", "failed"];
|
|
238
239
|
|
|
239
|
-
function
|
|
240
|
-
const row = node("div", "filter-row
|
|
240
|
+
function runFilterRow(sortedRuns, t, onChange) {
|
|
241
|
+
const row = node("div", "filter-row run-filter");
|
|
241
242
|
const buttons = [];
|
|
242
|
-
|
|
243
|
+
RUN_FILTERS.forEach(function (status) {
|
|
243
244
|
const count = status === "all"
|
|
244
|
-
?
|
|
245
|
-
:
|
|
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("
|
|
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 === "
|
|
351
|
+
} else if (task.status === "cancelled" || task.retirementSummary) {
|
|
350
352
|
const conclusion = node("div", "conclusion archived");
|
|
351
|
-
conclusion.append(node("h3", "", t("detail.
|
|
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, "
|
|
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
|
|
381
|
-
? data.runtimeHealth.
|
|
382
|
+
const stalledRuns = data.runtimeHealth && data.runtimeHealth.needsAttentionRuns
|
|
383
|
+
? data.runtimeHealth.needsAttentionRuns
|
|
382
384
|
: [];
|
|
383
|
-
if (
|
|
385
|
+
if (stalledRuns.length) {
|
|
384
386
|
const runtimeHealthBody = node("div", "section-body");
|
|
385
|
-
|
|
387
|
+
stalledRuns.forEach(function (run) {
|
|
386
388
|
const card = node("article", "record-card");
|
|
387
389
|
card.append(
|
|
388
|
-
node("strong", "",
|
|
389
|
-
node("p", "record-copy", (
|
|
390
|
-
node("small", "", t("detail.lastProgress") + " · " + formatDateTime(
|
|
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:
|
|
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.
|
|
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", "
|
|
444
|
-
const
|
|
445
|
-
if (!
|
|
446
|
-
execBody.append(emptyRow(t, "empty.
|
|
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
|
|
450
|
-
const filter =
|
|
451
|
-
|
|
451
|
+
let activeRunFilter = "all";
|
|
452
|
+
const filter = runFilterRow(sortedRuns, t, function (status) {
|
|
453
|
+
activeRunFilter = status;
|
|
452
454
|
filter.sync(status);
|
|
453
|
-
|
|
455
|
+
renderRunGrid();
|
|
454
456
|
});
|
|
455
|
-
function
|
|
457
|
+
function renderRunGrid() {
|
|
456
458
|
clear(execBody);
|
|
457
|
-
const visible =
|
|
458
|
-
?
|
|
459
|
-
:
|
|
460
|
-
pagedList(execBody, visible,
|
|
461
|
-
return
|
|
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(
|
|
466
|
+
filter.sync(activeRunFilter);
|
|
465
467
|
execWrap.append(filter.row, execBody);
|
|
466
|
-
|
|
468
|
+
renderRunGrid();
|
|
467
469
|
}
|
|
468
470
|
scaffold.append(anchorSection("detail-exec",
|
|
469
|
-
sectionHead(t("detail.execution"), { count: data.
|
|
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
|
-
|
|
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",
|
package/dist/web/assets/shell.js
CHANGED
|
@@ -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">
|
|
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,
|
|
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
|
|
113
|
-
.
|
|
114
|
-
.
|
|
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
|
+
}
|
package/dist/web/webServer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|