@zq-silk/yui 0.15.7 → 0.15.8
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 +192 -399
- package/README.md +127 -1149
- 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/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- 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 +178 -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 +1 -7
- 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 +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- 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} +146 -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 +458 -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 +114 -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 +247 -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 +62 -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 +380 -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 +123 -0
- package/dist/resources/projectResourceService.js +421 -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 +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -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 +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- 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 +120 -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 +353 -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 +118 -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 +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- 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
|
@@ -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,7 @@ 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";
|
|
5
6
|
import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
|
|
6
7
|
import { buildWebDashboardSnapshot, buildWebTaskDetail } from "./webSnapshot.js";
|
|
7
8
|
const MAX_JSON_BODY_BYTES = 16 * 1024;
|
|
@@ -53,12 +54,17 @@ export function createYuiWebServer(store, dependencies = {}) {
|
|
|
53
54
|
server.on("upgrade", (request, socket, head) => {
|
|
54
55
|
void handleTerminalUpgrade(request, socket, head, webSocketServer, dependencies.terminal, token);
|
|
55
56
|
});
|
|
56
|
-
|
|
57
|
+
let terminalsClosed = false;
|
|
58
|
+
const closeTerminals = () => {
|
|
59
|
+
if (terminalsClosed)
|
|
60
|
+
return;
|
|
61
|
+
terminalsClosed = true;
|
|
57
62
|
for (const socket of webSocketServer.clients)
|
|
58
63
|
socket.terminate();
|
|
59
64
|
webSocketServer.close();
|
|
60
|
-
}
|
|
61
|
-
|
|
65
|
+
};
|
|
66
|
+
server.on("close", closeTerminals);
|
|
67
|
+
return Object.assign(server, { closeTerminals });
|
|
62
68
|
}
|
|
63
69
|
export async function startYuiWebServer(store, options, dependencies = {}) {
|
|
64
70
|
const server = createYuiWebServer(store, dependencies);
|
|
@@ -75,7 +81,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
75
81
|
setSecurityHeaders(response);
|
|
76
82
|
const method = request.method ?? "GET";
|
|
77
83
|
if (!isLoopbackHost(headerValue(request, "host"))) {
|
|
78
|
-
sendJson(response, 403, { error: "Invalid Host." }, method === "HEAD");
|
|
84
|
+
sendJson(response, 403, { error: "Invalid Host.", disposition: "not-submitted" }, method === "HEAD");
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
81
87
|
let pathname;
|
|
@@ -83,7 +89,102 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
83
89
|
pathname = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
84
90
|
}
|
|
85
91
|
catch {
|
|
86
|
-
sendJson(response, 400, { error: "Invalid URL." }, method === "HEAD");
|
|
92
|
+
sendJson(response, 400, { error: "Invalid URL.", disposition: "not-submitted" }, method === "HEAD");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// The loopback page token is the actual user ingress. A request body cannot
|
|
96
|
+
// select Operator/Leader authority; all API reads use this boundary too.
|
|
97
|
+
if (pathname.startsWith("/api/") && !tokenMatches(headerValue(request, "x-yui-web-token"), token)) {
|
|
98
|
+
sendJson(response, 403, { error: "Invalid Yui web token.", disposition: "not-submitted" }, method === "HEAD");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const panelTarget = /^\/api\/tasks\/([^/]+)\/panels$/.exec(pathname);
|
|
102
|
+
if (panelTarget && dependencies.panels) {
|
|
103
|
+
try {
|
|
104
|
+
const taskId = decodeURIComponent(panelTarget[1]);
|
|
105
|
+
if (method === "GET") {
|
|
106
|
+
sendJson(response, 200, { panels: dependencies.panels.list(taskId), observedAt: now().toISOString() }, false);
|
|
107
|
+
}
|
|
108
|
+
else if (method === "POST") {
|
|
109
|
+
const body = await readMutationBody(request);
|
|
110
|
+
if (!body || typeof body !== "object" || Array.isArray(body)
|
|
111
|
+
|| Object.keys(body).some((key) => !["ref", "input"].includes(key))
|
|
112
|
+
|| !("ref" in body) || !("input" in body))
|
|
113
|
+
throw new WebRequestRejected("Expected panel ref and input.");
|
|
114
|
+
const ref = body.ref;
|
|
115
|
+
if (!ref || typeof ref !== "object" || typeof ref.capability !== "string"
|
|
116
|
+
|| typeof ref.contractVersion !== "string" || !ref.provider
|
|
117
|
+
|| typeof ref.provider.id !== "string" || typeof ref.provider.generation !== "string") {
|
|
118
|
+
throw new WebRequestRejected("Invalid panel reference.");
|
|
119
|
+
}
|
|
120
|
+
const result = await dependencies.panels.read(taskId, ref, body.input);
|
|
121
|
+
sendJson(response, 200, { result, observedAt: now().toISOString() }, false);
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, false);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
sendJson(response, 409, { error: error instanceof Error ? error.message : "Panel unavailable.",
|
|
128
|
+
disposition: "not-submitted" }, false);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const surfaceTarget = /^\/api\/tasks\/([^/]+)\/(context|delta|inspect|metadata|messages)$/.exec(pathname);
|
|
133
|
+
if (surfaceTarget && dependencies.surface) {
|
|
134
|
+
try {
|
|
135
|
+
let taskId;
|
|
136
|
+
try {
|
|
137
|
+
taskId = decodeURIComponent(surfaceTarget[1]);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new WebRequestRejected("Invalid Task URL encoding.");
|
|
141
|
+
}
|
|
142
|
+
const action = surfaceTarget[2];
|
|
143
|
+
const query = new URL(request.url, "http://localhost").searchParams;
|
|
144
|
+
let value;
|
|
145
|
+
if (method === "GET" && action === "context") {
|
|
146
|
+
value = await dependencies.surface.read(taskId);
|
|
147
|
+
}
|
|
148
|
+
else if (method === "GET" && action === "delta") {
|
|
149
|
+
value = dependencies.surface.delta(taskId, {
|
|
150
|
+
after: query.get("after") ?? "",
|
|
151
|
+
...(query.has("continuation") ? { continuation: query.get("continuation") } : {})
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else if (method === "GET" && action === "inspect") {
|
|
155
|
+
value = dependencies.surface.inspect(taskId, {
|
|
156
|
+
store: query.get("store") ?? "", refId: query.get("ref") ?? "",
|
|
157
|
+
...(query.has("digest") ? { digest: query.get("digest") } : {})
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else if (method === "POST" && action === "messages") {
|
|
161
|
+
const body = await readMutationBody(request);
|
|
162
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)
|
|
163
|
+
|| Object.keys(body).some((key) => !["body", "requestId"].includes(key))
|
|
164
|
+
|| !("requestId" in body) || typeof body.requestId !== "string" || !body.requestId.trim()
|
|
165
|
+
|| !("body" in body) || typeof body.body !== "string")
|
|
166
|
+
throw new WebRequestRejected("Expected body and requestId only.");
|
|
167
|
+
value = { ...dependencies.surface.message(taskId, body.body), requestId: body.requestId };
|
|
168
|
+
}
|
|
169
|
+
else if (method === "POST" && action === "metadata") {
|
|
170
|
+
const body = await readMutationBody(request);
|
|
171
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)
|
|
172
|
+
|| Object.keys(body).some((key) => !["patch", "requestId"].includes(key))
|
|
173
|
+
|| !("requestId" in body) || typeof body.requestId !== "string" || !body.requestId.trim()
|
|
174
|
+
|| !("patch" in body))
|
|
175
|
+
throw new WebRequestRejected("Expected patch and requestId only.");
|
|
176
|
+
value = { ...dependencies.surface.update(taskId, body.patch), requestId: body.requestId };
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, false);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
sendJson(response, 200, value, false);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
sendJson(response, 409, { error: error instanceof Error ? error.message : "Surface unavailable.",
|
|
186
|
+
disposition: error instanceof WebRequestRejected ? "not-submitted" : "unknown" }, false);
|
|
187
|
+
}
|
|
87
188
|
return;
|
|
88
189
|
}
|
|
89
190
|
if (method === "GET" || method === "HEAD") {
|
|
@@ -130,7 +231,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
130
231
|
}
|
|
131
232
|
catch (error) {
|
|
132
233
|
sendJson(response, 400, {
|
|
133
|
-
error: error instanceof Error ? error.message : "Invalid input answer."
|
|
234
|
+
error: error instanceof Error ? error.message : "Invalid input answer.", disposition: "not-submitted"
|
|
134
235
|
}, false);
|
|
135
236
|
return;
|
|
136
237
|
}
|
|
@@ -143,13 +244,14 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
143
244
|
}
|
|
144
245
|
catch (error) {
|
|
145
246
|
sendJson(response, 409, {
|
|
146
|
-
error: error instanceof Error ? error.message : "Unable to answer input request."
|
|
247
|
+
error: error instanceof Error ? error.message : "Unable to answer input request.",
|
|
248
|
+
disposition: error instanceof WebRequestRejected ? "not-submitted" : "unknown"
|
|
147
249
|
}, false);
|
|
148
250
|
}
|
|
149
251
|
return;
|
|
150
252
|
}
|
|
151
253
|
response.setHeader("allow", "GET, HEAD");
|
|
152
|
-
sendJson(response, 405, { error: "Method not allowed." }, method === "HEAD");
|
|
254
|
+
sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, method === "HEAD");
|
|
153
255
|
}
|
|
154
256
|
async function handleTerminalUpgrade(request, socket, head, webSocketServer, terminalPort, token) {
|
|
155
257
|
try {
|
|
@@ -340,6 +442,14 @@ function parseWebInputAnswer(value) {
|
|
|
340
442
|
}
|
|
341
443
|
throw new Error("Exactly one non-empty choiceKey or text is required.");
|
|
342
444
|
}
|
|
445
|
+
async function readMutationBody(request) {
|
|
446
|
+
try {
|
|
447
|
+
return await readJsonBody(request);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
throw new WebRequestRejected(error instanceof Error ? error.message : "Invalid request body.");
|
|
451
|
+
}
|
|
452
|
+
}
|
|
343
453
|
async function readJsonBody(request) {
|
|
344
454
|
const chunks = [];
|
|
345
455
|
let size = 0;
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
2
2
|
import { retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
3
3
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
4
4
|
import { projectWorkItemExecution } from "../execution/workItemExecutionProjection.js";
|
|
5
5
|
import { classifyRuntimeHealth, projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
6
6
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { formatRunReceiptId } from "../task/taskRecordReference.js";
|
|
8
|
+
import { runExecutionObservation } from "../agentRun/agentRun.js";
|
|
8
9
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
9
10
|
import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
|
|
10
11
|
import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
|
|
@@ -14,7 +15,7 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
14
15
|
draft: 0,
|
|
15
16
|
active: 0,
|
|
16
17
|
completed: 0,
|
|
17
|
-
|
|
18
|
+
cancelled: 0,
|
|
18
19
|
archived: 0
|
|
19
20
|
};
|
|
20
21
|
const projectNames = new Map(reader.listProjects().map((project) => [project.id, project.name]));
|
|
@@ -31,8 +32,8 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
31
32
|
attention.push({ taskId: task.id, taskTitle: task.title, request });
|
|
32
33
|
}
|
|
33
34
|
const events = reader.listEvents?.(task.id) ?? [];
|
|
34
|
-
const needsAttentionCount = reader.
|
|
35
|
-
.filter((
|
|
35
|
+
const needsAttentionCount = reader.listRuns(task.id)
|
|
36
|
+
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
36
37
|
.length;
|
|
37
38
|
const execution = buildTaskExecutionProjection(reader, task.id, task, now);
|
|
38
39
|
const names = task.projectBindings.flatMap(({ projectId }) => {
|
|
@@ -69,41 +70,51 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
69
70
|
const name = projectNamesById.get(projectId);
|
|
70
71
|
return name === undefined ? [] : [name];
|
|
71
72
|
});
|
|
72
|
-
const
|
|
73
|
+
const runs = reader.listRuns(taskId);
|
|
73
74
|
const events = reader.listEvents?.(taskId) ?? [];
|
|
74
75
|
const retiredMessageIds = retiredTaskRecordIds(events, "message");
|
|
75
|
-
const
|
|
76
|
-
.filter((
|
|
77
|
-
.map((
|
|
78
|
-
|
|
79
|
-
roleName:
|
|
80
|
-
progressAt: latestStallProgress(events,
|
|
81
|
-
kind: latestStallField(events,
|
|
82
|
-
classification: latestStallField(events,
|
|
76
|
+
const needsAttentionRuns = runs
|
|
77
|
+
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
78
|
+
.map((run) => ({
|
|
79
|
+
runId: run.id,
|
|
80
|
+
roleName: run.roleName,
|
|
81
|
+
progressAt: latestStallProgress(events, run.id),
|
|
82
|
+
kind: latestStallField(events, run.id, "kind") ?? "workflow-not-progressing",
|
|
83
|
+
classification: latestStallField(events, run.id, "classification") ?? "truly-stalled"
|
|
83
84
|
}));
|
|
84
|
-
const
|
|
85
|
-
.filter((
|
|
86
|
-
.map((
|
|
87
|
-
const
|
|
88
|
-
.filter((
|
|
89
|
-
.map((
|
|
85
|
+
const activeRuns = new Map(runs
|
|
86
|
+
.filter((run) => run.status === "active")
|
|
87
|
+
.map((run) => [run.roleName, run]));
|
|
88
|
+
const activeRunHealth = runs
|
|
89
|
+
.filter((run) => run.status === "active")
|
|
90
|
+
.map((run) => projectWebRunRuntimeHealth(reader, taskId, run, events, now, resolveRuntimeHealth(reader.getConfig().runtimeHealth)));
|
|
90
91
|
const roles = reader.listRoles(taskId).map((role) => {
|
|
91
|
-
const
|
|
92
|
+
const activeRun = activeRuns.get(role.name);
|
|
92
93
|
const sessions = reader.getTaskRoleSessionSet(taskId, role.name);
|
|
93
|
-
const activeSession = sessions?.sessions[sessions.activeAgentId];
|
|
94
|
-
const effectiveLaunch =
|
|
94
|
+
const activeSession = sessions?.sessions[activeRun?.effective.agentId ?? sessions.activeAgentId];
|
|
95
|
+
const effectiveLaunch = activeRun?.effective ?? activeSession?.effective ?? null;
|
|
96
|
+
const delivery = sessions?.providerBinding?.run?.status;
|
|
95
97
|
return {
|
|
96
98
|
...role,
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
// An open record is not proof that its Agent is running.
|
|
100
|
+
status: delivery === "accepted" ? "running"
|
|
101
|
+
: delivery === "submitting" || delivery === "deferred" ? "waiting"
|
|
102
|
+
: activeSession === undefined && activeRun === undefined ? "idle" : "unknown",
|
|
100
103
|
sessionTokens: projectSessionTokenMetrics(events, resolveSessionTokenIdentity(activeSession === undefined
|
|
101
104
|
? null
|
|
102
105
|
: { taskId, roleName: role.name, ...activeSession })),
|
|
103
106
|
effectiveLaunch,
|
|
104
|
-
|
|
107
|
+
observedAt: now.toISOString(),
|
|
108
|
+
runtimeSession: activeSession === undefined ? null : {
|
|
109
|
+
agentId: activeSession.agentId,
|
|
110
|
+
adapterId: activeSession.adapterId,
|
|
111
|
+
nativeSessionId: activeSession.nativeSessionId ?? null,
|
|
112
|
+
status: activeSession.status,
|
|
113
|
+
endpointImplementation: activeSession.endpointImplementation ?? null
|
|
114
|
+
},
|
|
115
|
+
effectiveLaunchSource: activeRun === undefined
|
|
105
116
|
? activeSession === undefined ? null : "session"
|
|
106
|
-
: "
|
|
117
|
+
: "run",
|
|
107
118
|
launchDrift: effectiveLaunch !== null
|
|
108
119
|
&& effectiveLaunch.sourceDesiredRevision !== role.launchRevision
|
|
109
120
|
};
|
|
@@ -128,10 +139,10 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
128
139
|
workItems: workItems.map((item) => ({
|
|
129
140
|
...item,
|
|
130
141
|
observability: workItemObservability.get(item.id),
|
|
131
|
-
execution: projectWorkItemExecution(item,
|
|
142
|
+
execution: projectWorkItemExecution(item, runs, roleSessionSets, reader)
|
|
132
143
|
})),
|
|
133
|
-
|
|
134
|
-
runtimeHealth: {
|
|
144
|
+
runs: runs.map((run) => ({ ...run, execution: runExecutionObservation(run, reader.getTaskRoleSessionSet(taskId, run.roleName)?.providerBinding, events) })),
|
|
145
|
+
runtimeHealth: { needsAttentionRuns, activeRuns: activeRunHealth },
|
|
135
146
|
reviewRounds: reader.listReviewRounds(taskId),
|
|
136
147
|
openInputs: inputs.filter((request) => request.status === "open"),
|
|
137
148
|
messages: reader.listMessages(taskId).map((message) => ({
|
|
@@ -149,69 +160,68 @@ function webRemoteDelivery(reader, task) {
|
|
|
149
160
|
events: reader.listEvents?.(task.id) ?? [],
|
|
150
161
|
publications: reader.listPublicationReferences(task.id),
|
|
151
162
|
managedWorkspaces: reader.listManagedWorkspaces(task.id),
|
|
152
|
-
|
|
163
|
+
runs: reader.listRuns(task.id),
|
|
153
164
|
currentCandidate: null
|
|
154
165
|
});
|
|
155
166
|
}
|
|
156
|
-
function latestStallProgress(events,
|
|
157
|
-
return latestStallField(events,
|
|
167
|
+
function latestStallProgress(events, runId) {
|
|
168
|
+
return latestStallField(events, runId, "progressAt");
|
|
158
169
|
}
|
|
159
170
|
/**
|
|
160
|
-
* Layered runtime health for one active
|
|
171
|
+
* Layered runtime health for one active AgentRun, computed from the same stored
|
|
161
172
|
* observations and durable semantic fold as the CLI status projection. The
|
|
162
173
|
* Web snapshot has no live tmux pane, so host state stays "unknown"; the
|
|
163
174
|
* classifier still surfaces session/turn/operation/observer layers and the
|
|
164
175
|
* scheduler's durable `turn.stalled` episode is surfaced as
|
|
165
176
|
* `stalled-candidate`.
|
|
166
177
|
*/
|
|
167
|
-
function
|
|
168
|
-
const stalled =
|
|
169
|
-
const sessions = reader.getTaskRoleSessionSet(taskId,
|
|
170
|
-
const session = sessions?.sessions[
|
|
171
|
-
const stallReason = "the live active
|
|
172
|
-
if (session?.
|
|
178
|
+
function projectWebRunRuntimeHealth(reader, taskId, run, events, now, policy) {
|
|
179
|
+
const stalled = isRoleRunStalled(events, run.id);
|
|
180
|
+
const sessions = reader.getTaskRoleSessionSet(taskId, run.roleName);
|
|
181
|
+
const session = sessions?.sessions[run.effective.agentId];
|
|
182
|
+
const stallReason = "the live active AgentRun has no durable progress in the configured stall window";
|
|
183
|
+
if (session?.nativeSessionId === undefined) {
|
|
173
184
|
return {
|
|
174
|
-
|
|
175
|
-
roleName:
|
|
185
|
+
runId: run.id,
|
|
186
|
+
roleName: run.roleName,
|
|
176
187
|
layer: stalled ? "stalled-candidate" : "awaiting-provider-acceptance",
|
|
177
|
-
reason: stalled ? stallReason : "the active
|
|
188
|
+
reason: stalled ? stallReason : "the active AgentRun is awaiting a Provider Session",
|
|
178
189
|
stalled,
|
|
179
|
-
lastSemanticProgressAt:
|
|
190
|
+
lastSemanticProgressAt: run.createdAt
|
|
180
191
|
};
|
|
181
192
|
}
|
|
182
193
|
let driverId;
|
|
183
194
|
try {
|
|
184
|
-
driverId = builtinDriverIdForAdapter(
|
|
195
|
+
driverId = builtinDriverIdForAdapter(run.effective.adapterId);
|
|
185
196
|
}
|
|
186
197
|
catch {
|
|
187
198
|
return {
|
|
188
|
-
|
|
189
|
-
roleName:
|
|
199
|
+
runId: run.id,
|
|
200
|
+
roleName: run.roleName,
|
|
190
201
|
layer: stalled ? "stalled-candidate" : "runtime-unobservable",
|
|
191
202
|
reason: stalled ? stallReason : "the Agent Driver is not a built-in driver",
|
|
192
203
|
stalled,
|
|
193
|
-
lastSemanticProgressAt:
|
|
204
|
+
lastSemanticProgressAt: run.createdAt
|
|
194
205
|
};
|
|
195
206
|
}
|
|
196
|
-
const providerTurn = sessions?.providerBinding?.
|
|
207
|
+
const providerTurn = sessions?.providerBinding?.run;
|
|
197
208
|
const fence = {
|
|
198
209
|
taskId,
|
|
199
|
-
roleName:
|
|
200
|
-
|
|
201
|
-
agentId:
|
|
210
|
+
roleName: run.roleName,
|
|
211
|
+
runId: run.id,
|
|
212
|
+
agentId: run.effective.agentId,
|
|
202
213
|
driverId,
|
|
203
|
-
runtimeGenerationId: session.runtimeGenerationId,
|
|
204
214
|
nativeSessionId: session.nativeSessionId,
|
|
205
|
-
nativeTurnId: providerTurn?.
|
|
206
|
-
? providerTurn.nativeTurnId ??
|
|
207
|
-
:
|
|
208
|
-
receiptId: providerTurn?.
|
|
215
|
+
nativeTurnId: providerTurn?.runId === run.id
|
|
216
|
+
? providerTurn.nativeTurnId ?? run.id
|
|
217
|
+
: run.id,
|
|
218
|
+
receiptId: providerTurn?.runId === run.id
|
|
209
219
|
? providerTurn.attemptId
|
|
210
|
-
:
|
|
220
|
+
: formatRunReceiptId(taskId, run.id)
|
|
211
221
|
};
|
|
212
|
-
const projection = projectRuntimeTaskEvents(fence,
|
|
222
|
+
const projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
|
|
213
223
|
const view = {
|
|
214
|
-
|
|
224
|
+
getRun: (taskId, runId) => reader.listRuns(taskId).find((candidate) => candidate.id === runId) ?? null,
|
|
215
225
|
listEvents: () => events,
|
|
216
226
|
getWorkItem: (workItemTaskId, workItemId) => reader.listWorkItems(workItemTaskId).find((item) => item.id === workItemId) ?? null,
|
|
217
227
|
listReviewRounds: (taskId) => reader.listReviewRounds(taskId),
|
|
@@ -219,8 +229,8 @@ function projectWebTurnRuntimeHealth(reader, taskId, turn, events, now, policy)
|
|
|
219
229
|
listIntegrationAttempts: (taskId) => reader.listIntegrationAttempts(taskId),
|
|
220
230
|
listInputRequests: (taskId) => reader.listInputRequests(taskId)
|
|
221
231
|
};
|
|
222
|
-
const semanticProgress =
|
|
223
|
-
?? { progressAt:
|
|
232
|
+
const semanticProgress = latestRunDurableProgressAt(view, taskId, run.roleName, run.id)
|
|
233
|
+
?? { progressAt: run.createdAt };
|
|
224
234
|
const classification = classifyRuntimeHealth({
|
|
225
235
|
projection,
|
|
226
236
|
semanticProgressAt: semanticProgress.progressAt,
|
|
@@ -228,8 +238,8 @@ function projectWebTurnRuntimeHealth(reader, taskId, turn, events, now, policy)
|
|
|
228
238
|
policy
|
|
229
239
|
});
|
|
230
240
|
return {
|
|
231
|
-
|
|
232
|
-
roleName:
|
|
241
|
+
runId: run.id,
|
|
242
|
+
roleName: run.roleName,
|
|
233
243
|
layer: stalled ? "stalled-candidate" : classification.layer,
|
|
234
244
|
reason: stalled ? stallReason : classification.reason,
|
|
235
245
|
stalled,
|
|
@@ -239,10 +249,10 @@ function projectWebTurnRuntimeHealth(reader, taskId, turn, events, now, policy)
|
|
|
239
249
|
lastSemanticProgressAt: classification.lastSemanticProgressAt
|
|
240
250
|
};
|
|
241
251
|
}
|
|
242
|
-
function latestStallField(events,
|
|
252
|
+
function latestStallField(events, runId, field) {
|
|
243
253
|
const stalled = events
|
|
244
|
-
.filter((event) => event.type === "
|
|
245
|
-
&& event.payload.
|
|
254
|
+
.filter((event) => event.type === "run.stalled"
|
|
255
|
+
&& event.payload.runId === runId
|
|
246
256
|
&& event.payload.status !== "diagnostic-only")
|
|
247
257
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
248
258
|
return stalled?.payload[field];
|
|
@@ -250,11 +260,8 @@ function latestStallField(events, turnId, field) {
|
|
|
250
260
|
function countWorkItems(items) {
|
|
251
261
|
const counts = {
|
|
252
262
|
total: items.length,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
awaiting_acceptance: 0,
|
|
256
|
-
completed: 0,
|
|
257
|
-
failed: 0,
|
|
263
|
+
open: 0,
|
|
264
|
+
accepted: 0,
|
|
258
265
|
retired: 0
|
|
259
266
|
};
|
|
260
267
|
const mutable = counts;
|
|
@@ -268,7 +275,7 @@ function compareDashboardTasks(left, right) {
|
|
|
268
275
|
active: 0,
|
|
269
276
|
draft: 1,
|
|
270
277
|
completed: 2,
|
|
271
|
-
|
|
278
|
+
cancelled: 3,
|
|
272
279
|
archived: 4
|
|
273
280
|
};
|
|
274
281
|
return statusOrder[left.status] - statusOrder[right.status]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readTaskContext, readTaskContextDelta, inspectTaskContext, withContextObservations } from "../context/taskContext.js";
|
|
2
|
+
import { BUILTIN_CAPABILITIES } from "../kernel/builtinCapabilities.js";
|
|
3
|
+
import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
|
|
4
|
+
import { updateTaskMetadataCommand, sendTaskMessageCommand } from "../commands/taskCommands.js";
|
|
5
|
+
import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
|
|
6
|
+
import { runTaskInputCommand } from "../commands/taskInputCommands.js";
|
|
7
|
+
/** Installed only by the local-user Web composition root. HTTP authenticates
|
|
8
|
+
* its token before using this port; input never supplies a caller or Role.
|
|
9
|
+
* Managed capability RPC keeps its own Session authentication unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export function createWebTaskSurface(store, options = {}, observations = []) {
|
|
12
|
+
const environment = {};
|
|
13
|
+
const commandOptions = { ...options, environment, runtime: undefined };
|
|
14
|
+
// Notifications are after the outer transaction. Their failure must not be
|
|
15
|
+
// reclassified as a rejection of a mutation that already committed.
|
|
16
|
+
const notify = (taskId, leader = false) => {
|
|
17
|
+
if (options.runtime?.notifyMailboxChanged) {
|
|
18
|
+
void options.runtime.notifyMailboxChanged(leader
|
|
19
|
+
? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
options.runtime?.notifyStateChanged(taskId);
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
message: (taskId, body) => {
|
|
26
|
+
// `queuedForLeader` is the fact the shared transaction actually committed,
|
|
27
|
+
// not a re-derivation from Task status. A Draft queues its Leader exactly
|
|
28
|
+
// like an active Task (planning is a Leader conversation), so reporting
|
|
29
|
+
// "saved" here would have understated a wake that really did happen.
|
|
30
|
+
const { message, task, queuedForLeader } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, "leader", commandOptions));
|
|
31
|
+
notify(taskId, queuedForLeader);
|
|
32
|
+
return { record: message, revision: message.createdAt,
|
|
33
|
+
disposition: queuedForLeader ? "queued" : "saved",
|
|
34
|
+
planning: task.status === "draft",
|
|
35
|
+
target: { scope: "task", taskId, roleName: "leader" } };
|
|
36
|
+
},
|
|
37
|
+
read: async (taskId) => withContextObservations(readTaskContext(store, taskId, environment), observations),
|
|
38
|
+
delta: (taskId, input) => readTaskContextDelta(store, taskId, input, environment),
|
|
39
|
+
inspect: (taskId, input) => inspectTaskContext(store, taskId, input, environment),
|
|
40
|
+
update: (taskId, input) => {
|
|
41
|
+
const descriptor = BUILTIN_CAPABILITIES.find((entry) => entry.name === "task.update");
|
|
42
|
+
const error = capabilitySchemaError(descriptor.inputSchema, { taskId, patch: input });
|
|
43
|
+
if (error)
|
|
44
|
+
throw new WebRequestRejected(error);
|
|
45
|
+
const patch = input;
|
|
46
|
+
const record = webLocalMutation(store, (tx) => updateTaskMetadataCommand(tx, taskId, {
|
|
47
|
+
...patch,
|
|
48
|
+
...(patch.description === "" ? { description: null } : {}),
|
|
49
|
+
...(patch.tags?.length === 0 ? { tags: null } : {})
|
|
50
|
+
}, commandOptions));
|
|
51
|
+
notify(taskId);
|
|
52
|
+
return { record, revision: record.updatedAt };
|
|
53
|
+
},
|
|
54
|
+
answer: (taskId, inputId, answer) => {
|
|
55
|
+
const result = webLocalMutation(store, (tx) => runTaskInputCommand([
|
|
56
|
+
"answer", inputId, "--task", taskId,
|
|
57
|
+
...("choiceKey" in answer ? ["--choice", answer.choiceKey] : ["--text", answer.text])
|
|
58
|
+
], tx, commandOptions));
|
|
59
|
+
const data = result.data;
|
|
60
|
+
notify(taskId, true);
|
|
61
|
+
return data.request;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -21,7 +21,7 @@ export class WorkItemDependencyGateError extends Error {
|
|
|
21
21
|
export function assertWorkItemDependenciesCompleted(store, item) {
|
|
22
22
|
const unmetDependencies = item.dependsOn.flatMap((dependencyId) => {
|
|
23
23
|
const dependency = store.getWorkItem(item.taskId, dependencyId);
|
|
24
|
-
if (dependency?.status === "
|
|
24
|
+
if (dependency?.status === "accepted")
|
|
25
25
|
return [];
|
|
26
26
|
return [{
|
|
27
27
|
id: dependencyId,
|