@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.11
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/AGENTS.md +1 -1
- package/CHANGELOG.md +114 -109
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/executors/dag-pi-executor.js +69 -7
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +107 -20
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/governance/checks.js +1 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +10 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +133 -7
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +312 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +583 -172
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +29 -9
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +174 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +533 -79
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +6 -4
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +186 -8
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +168 -16
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +28 -17
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +239 -52
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +62 -12
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +267 -238
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -3,10 +3,12 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { dispatchOperatorAction } from "./operator-actions.js";
|
|
5
5
|
import { openSseResponse, parseLastEventId, writeSseEvent, } from "./operation-sse.js";
|
|
6
|
+
import { probePiReadiness } from "./pi-readiness.js";
|
|
6
7
|
import { buildOperatorSurfaceHealthV1 } from "./operator-surface-health.js";
|
|
7
8
|
import { ROUTES } from "../observe/routes.js";
|
|
8
9
|
import { evaluateMutationGate, injectConfirmationTokenScript, isMutationMethod, } from "./security.js";
|
|
9
10
|
import { listDagRunsForRecovery } from "./resolve-dag-run-for-task.js";
|
|
11
|
+
import { pickNativeWorkspaceDirectory } from "./native-directory-picker.js";
|
|
10
12
|
export function sendJson(res, status, body, extraHeaders) {
|
|
11
13
|
const payload = JSON.stringify(body);
|
|
12
14
|
res.writeHead(status, {
|
|
@@ -38,6 +40,10 @@ function contentTypeFor(pathname) {
|
|
|
38
40
|
return "application/octet-stream";
|
|
39
41
|
}
|
|
40
42
|
async function readJsonBody(req, maxBytes = 10 * 1024 * 1024) {
|
|
43
|
+
// Body pre-read by the multi-workspace dispatcher (workspaceId resolution).
|
|
44
|
+
const preRead = req.__preReadBody;
|
|
45
|
+
if (preRead !== undefined)
|
|
46
|
+
return preRead;
|
|
41
47
|
const chunks = [];
|
|
42
48
|
let total = 0;
|
|
43
49
|
for await (const chunk of req) {
|
|
@@ -67,6 +73,7 @@ export async function handleConsoleHealthRequest(_req, res, ctx) {
|
|
|
67
73
|
piReadiness: ctx.operator?.getReadiness().state ?? "setup-required",
|
|
68
74
|
inspectReady: ctx.inspect.ready,
|
|
69
75
|
routes: ROUTES,
|
|
76
|
+
consoleInstanceId: ctx.consoleInstanceId,
|
|
70
77
|
}));
|
|
71
78
|
}
|
|
72
79
|
catch {
|
|
@@ -77,6 +84,7 @@ export async function handleConsoleHealthRequest(_req, res, ctx) {
|
|
|
77
84
|
repoFingerprint: "unavailable",
|
|
78
85
|
packageName: "unknown",
|
|
79
86
|
packageVersion: "0.0.0",
|
|
87
|
+
consoleInstanceId: ctx.consoleInstanceId,
|
|
80
88
|
piReadiness: ctx.operator?.getReadiness().state ?? "setup-required",
|
|
81
89
|
inspect: {
|
|
82
90
|
ready: ctx.inspect.ready,
|
|
@@ -217,6 +225,7 @@ async function handleCreateOperation(req, res, ctx) {
|
|
|
217
225
|
action,
|
|
218
226
|
actionParams,
|
|
219
227
|
clientRequestId,
|
|
228
|
+
source: "human-ui",
|
|
220
229
|
});
|
|
221
230
|
sendJson(res, result.status, result.body);
|
|
222
231
|
}
|
|
@@ -242,7 +251,35 @@ async function handleGetOperation(_req, res, ctx, operationId) {
|
|
|
242
251
|
});
|
|
243
252
|
return;
|
|
244
253
|
}
|
|
245
|
-
sendJson(res, 200, {
|
|
254
|
+
sendJson(res, 200, {
|
|
255
|
+
ok: true,
|
|
256
|
+
workspaceId: ctx.workspace?.workspaceId,
|
|
257
|
+
operation: { ...op, workspaceId: ctx.workspace?.workspaceId },
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* GET /api/operator/v1/operations?workspaceId= — NEW per-workspace list route
|
|
262
|
+
* (multi-workspace foundation). Returns only the resolved Context's Operations
|
|
263
|
+
* and injects the owning workspaceId. operation-store.ts is unchanged.
|
|
264
|
+
*/
|
|
265
|
+
async function handleListOperations(_req, res, ctx) {
|
|
266
|
+
if (!ctx.operator) {
|
|
267
|
+
sendJson(res, 503, {
|
|
268
|
+
ok: false,
|
|
269
|
+
error: {
|
|
270
|
+
code: "INTERNAL_ERROR",
|
|
271
|
+
message: "operator runtime unavailable",
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const operations = await ctx.operator.operations.list();
|
|
277
|
+
sendJson(res, 200, {
|
|
278
|
+
ok: true,
|
|
279
|
+
schemaVersion: 1,
|
|
280
|
+
workspaceId: ctx.workspace?.workspaceId,
|
|
281
|
+
ops: operations.map((op) => ({ ...op, workspaceId: ctx.workspace?.workspaceId })),
|
|
282
|
+
});
|
|
246
283
|
}
|
|
247
284
|
async function handleOperationEvents(req, res, ctx, operationId) {
|
|
248
285
|
if (!ctx.operator) {
|
|
@@ -450,6 +487,10 @@ export async function handleConsoleOperatorRequest(req, res, ctx) {
|
|
|
450
487
|
await handleCreateOperation(req, res, ctx);
|
|
451
488
|
return true;
|
|
452
489
|
}
|
|
490
|
+
if (method === "GET" && pathname === "/api/operator/v1/operations") {
|
|
491
|
+
await handleListOperations(req, res, ctx);
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
453
494
|
if (method === "GET" && pathname === "/api/operator/v1/night-aux-ticker") {
|
|
454
495
|
await handleNightAuxTickerGet(req, res, ctx);
|
|
455
496
|
return true;
|
|
@@ -514,3 +555,273 @@ export async function handleConsoleRequest(req, res, ctx) {
|
|
|
514
555
|
}
|
|
515
556
|
return false;
|
|
516
557
|
}
|
|
558
|
+
export function createWorkspaceReadinessCache() {
|
|
559
|
+
const entries = new Map();
|
|
560
|
+
return {
|
|
561
|
+
get: (repoRoot) => entries.get(repoRoot),
|
|
562
|
+
set: (repoRoot, entry) => {
|
|
563
|
+
entries.set(repoRoot, entry);
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const defaultWorkspaceReadinessCache = createWorkspaceReadinessCache();
|
|
568
|
+
const READY_STATE_TTL_MS = 60_000;
|
|
569
|
+
const NON_READY_STATE_TTL_MS = 15_000;
|
|
570
|
+
function isCacheFresh(entry, now) {
|
|
571
|
+
const ttl = entry.state === "ready" ? READY_STATE_TTL_MS : NON_READY_STATE_TTL_MS;
|
|
572
|
+
return now - entry.probedAt < ttl;
|
|
573
|
+
}
|
|
574
|
+
/** Readiness placeholder for a never-probed workspace (AC-006): the list is
|
|
575
|
+
* still returned immediately with a pending marker, refreshed by the
|
|
576
|
+
* background probe. */
|
|
577
|
+
function pendingReadinessSummary(inspectReady) {
|
|
578
|
+
return {
|
|
579
|
+
state: "pending",
|
|
580
|
+
summary: `pi=pending; inspect=${inspectReady ? "ready" : "unavailable"}`,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Process-level Workspace Registry HTTP surface (2026-08-22 multi-workspace
|
|
585
|
+
* foundation):
|
|
586
|
+
* GET /api/operator/v1/workspaces — visible list
|
|
587
|
+
* POST /api/operator/v1/workspaces/pick — host native folder chooser
|
|
588
|
+
* POST /api/operator/v1/workspaces — body { "name": "<label>",
|
|
589
|
+
* "directoryPath": "<dir>" }
|
|
590
|
+
* DELETE /api/operator/v1/workspaces/:workspaceId — soft remove (restore on re-add)
|
|
591
|
+
*
|
|
592
|
+
* POST/DELETE are mutations and pass the Console mutation gate. GET carries no
|
|
593
|
+
* workspace selection parameter.
|
|
594
|
+
*
|
|
595
|
+
* AC-006 (2026-08-23): GET is NON-BLOCKING — registry + process-level
|
|
596
|
+
* readiness cache answer immediately (first sight may be a pending
|
|
597
|
+
* placeholder); probes refresh the cache in the background for subsequent
|
|
598
|
+
* requests. A hanging probe can never delay the 200 list.
|
|
599
|
+
*/
|
|
600
|
+
export async function handleWorkspaceRoutes(req, res, registry, deps, url, pathname, method, readinessDeps) {
|
|
601
|
+
if (method === "GET" && pathname === "/api/operator/v1/workspaces") {
|
|
602
|
+
const records = await registry.listVisible();
|
|
603
|
+
const inspectReady = readinessDeps.inspectReady;
|
|
604
|
+
const readinessCache = readinessDeps.readinessCache ?? defaultWorkspaceReadinessCache;
|
|
605
|
+
const workspaces = records.map((record) => {
|
|
606
|
+
const cached = readinessCache.get(record.repoRoot);
|
|
607
|
+
return {
|
|
608
|
+
workspaceId: record.workspaceId,
|
|
609
|
+
name: record.name,
|
|
610
|
+
displayPath: record.displayPath,
|
|
611
|
+
repoFingerprint: record.repoFingerprint,
|
|
612
|
+
createdAt: record.createdAt,
|
|
613
|
+
lastAccessedAt: record.lastAccessedAt,
|
|
614
|
+
readiness: cached
|
|
615
|
+
? {
|
|
616
|
+
state: cached.state,
|
|
617
|
+
summary: `pi=${cached.state}; inspect=${inspectReady ? "ready" : "unavailable"}`,
|
|
618
|
+
}
|
|
619
|
+
: pendingReadinessSummary(inspectReady),
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
// Respond FIRST, then refresh readiness in the background (AC-006).
|
|
623
|
+
sendJson(res, 200, {
|
|
624
|
+
ok: true,
|
|
625
|
+
schemaVersion: 1,
|
|
626
|
+
workspaces,
|
|
627
|
+
});
|
|
628
|
+
void Promise.all(records.map(async (record) => {
|
|
629
|
+
const cached = readinessCache.get(record.repoRoot);
|
|
630
|
+
if (cached && isCacheFresh(cached, Date.now()))
|
|
631
|
+
return;
|
|
632
|
+
let state;
|
|
633
|
+
try {
|
|
634
|
+
const report = await probePiReadiness({
|
|
635
|
+
override: deps.piReadinessProbe,
|
|
636
|
+
repoRoot: record.repoRoot,
|
|
637
|
+
});
|
|
638
|
+
state = report.state;
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
state = "setup-required";
|
|
642
|
+
}
|
|
643
|
+
readinessCache.set(record.repoRoot, {
|
|
644
|
+
state,
|
|
645
|
+
probedAt: Date.now(),
|
|
646
|
+
});
|
|
647
|
+
})).catch(() => undefined);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (method === "POST" && pathname === "/api/operator/v1/workspaces/pick") {
|
|
651
|
+
const failure = evaluateMutationGate(req, {
|
|
652
|
+
cookieName: deps.bootToken.cookieName,
|
|
653
|
+
bootToken: deps.bootToken.value,
|
|
654
|
+
confirmationToken: deps.bootToken.confirmationToken,
|
|
655
|
+
consoleOrigin: deps.getConsoleOrigin(),
|
|
656
|
+
allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
|
|
657
|
+
});
|
|
658
|
+
if (failure) {
|
|
659
|
+
sendJson(res, failure.status, {
|
|
660
|
+
ok: false,
|
|
661
|
+
error: { code: failure.code, message: failure.message },
|
|
662
|
+
});
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (deps.allowNonLoopbackAccess) {
|
|
666
|
+
sendJson(res, 409, {
|
|
667
|
+
ok: false,
|
|
668
|
+
error: {
|
|
669
|
+
code: "NATIVE_PICKER_UNAVAILABLE",
|
|
670
|
+
message: "原生目录选择仅在本机回环地址启动的 Console 中可用。",
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const abortController = new AbortController();
|
|
676
|
+
const abort = () => abortController.abort();
|
|
677
|
+
req.once("aborted", abort);
|
|
678
|
+
res.once("close", abort);
|
|
679
|
+
try {
|
|
680
|
+
const directoryPath = await (deps.pickDirectory ?? pickNativeWorkspaceDirectory)(abortController.signal);
|
|
681
|
+
if (!res.writableEnded && !abortController.signal.aborted) {
|
|
682
|
+
sendJson(res, 200, { ok: true, directoryPath });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
if (!res.writableEnded && !abortController.signal.aborted) {
|
|
687
|
+
sendJson(res, 500, {
|
|
688
|
+
ok: false,
|
|
689
|
+
error: {
|
|
690
|
+
code: "DIRECTORY_PICKER_FAILED",
|
|
691
|
+
message: error instanceof Error ? error.message : String(error),
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
finally {
|
|
697
|
+
req.off("aborted", abort);
|
|
698
|
+
res.off("close", abort);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (method === "POST" && pathname === "/api/operator/v1/workspaces") {
|
|
703
|
+
const failure = evaluateMutationGate(req, {
|
|
704
|
+
cookieName: deps.bootToken.cookieName,
|
|
705
|
+
bootToken: deps.bootToken.value,
|
|
706
|
+
confirmationToken: deps.bootToken.confirmationToken,
|
|
707
|
+
consoleOrigin: deps.getConsoleOrigin(),
|
|
708
|
+
allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
|
|
709
|
+
});
|
|
710
|
+
if (failure) {
|
|
711
|
+
sendJson(res, failure.status, {
|
|
712
|
+
ok: false,
|
|
713
|
+
error: { code: failure.code, message: failure.message },
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
let body;
|
|
718
|
+
try {
|
|
719
|
+
body = (await readJsonBody(req));
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
sendJson(res, 400, {
|
|
723
|
+
ok: false,
|
|
724
|
+
error: {
|
|
725
|
+
code: "INVALID_INPUT",
|
|
726
|
+
message: error instanceof Error ? error.message : String(error),
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const directoryPath = typeof body.directoryPath === "string"
|
|
732
|
+
? body.directoryPath
|
|
733
|
+
: typeof body.path === "string"
|
|
734
|
+
? body.path
|
|
735
|
+
: "";
|
|
736
|
+
if (!directoryPath.trim()) {
|
|
737
|
+
sendJson(res, 400, {
|
|
738
|
+
ok: false,
|
|
739
|
+
error: {
|
|
740
|
+
code: "INVALID_INPUT",
|
|
741
|
+
message: "directoryPath (string) is required",
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (body.name !== undefined &&
|
|
747
|
+
(typeof body.name !== "string" || !body.name.trim())) {
|
|
748
|
+
sendJson(res, 400, {
|
|
749
|
+
ok: false,
|
|
750
|
+
error: {
|
|
751
|
+
code: "INVALID_INPUT",
|
|
752
|
+
message: "name must be a non-empty string when provided",
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const name = typeof body.name === "string" ? body.name : undefined;
|
|
758
|
+
const result = await registry.add(directoryPath, name ? { name } : {});
|
|
759
|
+
if (!result.ok) {
|
|
760
|
+
const status = result.code === "DUPLICATE"
|
|
761
|
+
? 409
|
|
762
|
+
: result.code === "INVALID_PATH"
|
|
763
|
+
? 400
|
|
764
|
+
: 400;
|
|
765
|
+
sendJson(res, status, {
|
|
766
|
+
ok: false,
|
|
767
|
+
error: {
|
|
768
|
+
code: result.code,
|
|
769
|
+
message: result.message,
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const record = result.record;
|
|
775
|
+
sendJson(res, 201, {
|
|
776
|
+
ok: true,
|
|
777
|
+
workspace: {
|
|
778
|
+
workspaceId: record.workspaceId,
|
|
779
|
+
name: record.name,
|
|
780
|
+
displayPath: record.displayPath,
|
|
781
|
+
repoFingerprint: record.repoFingerprint,
|
|
782
|
+
createdAt: record.createdAt,
|
|
783
|
+
lastAccessedAt: record.lastAccessedAt,
|
|
784
|
+
readiness: {
|
|
785
|
+
state: "setup-required",
|
|
786
|
+
summary: `pi=unknown; inspect=${readinessDeps.inspectReady ? "ready" : "unavailable"}`,
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const removeMatch = pathname.match(/^\/api\/operator\/v1\/workspaces\/([^/]+)$/);
|
|
793
|
+
if (method === "DELETE" && removeMatch) {
|
|
794
|
+
const failure = evaluateMutationGate(req, {
|
|
795
|
+
cookieName: deps.bootToken.cookieName,
|
|
796
|
+
bootToken: deps.bootToken.value,
|
|
797
|
+
confirmationToken: deps.bootToken.confirmationToken,
|
|
798
|
+
consoleOrigin: deps.getConsoleOrigin(),
|
|
799
|
+
allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
|
|
800
|
+
});
|
|
801
|
+
if (failure) {
|
|
802
|
+
sendJson(res, failure.status, {
|
|
803
|
+
ok: false,
|
|
804
|
+
error: { code: failure.code, message: failure.message },
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const workspaceId = decodeURIComponent(removeMatch[1]);
|
|
809
|
+
const removed = await registry.remove(workspaceId);
|
|
810
|
+
if (!removed) {
|
|
811
|
+
sendJson(res, 404, {
|
|
812
|
+
ok: false,
|
|
813
|
+
error: {
|
|
814
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
815
|
+
message: `unknown workspace: ${workspaceId}`,
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
sendJson(res, 200, { ok: true, workspaceId, removed: true });
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
sendJson(res, 404, {
|
|
824
|
+
ok: false,
|
|
825
|
+
error: { code: "NOT_FOUND", message: `unknown workspace route: ${pathname}` },
|
|
826
|
+
});
|
|
827
|
+
}
|
|
@@ -119,12 +119,12 @@ function safeEqualToken(a, b) {
|
|
|
119
119
|
function loopbackOriginMatches(origin, consoleOrigin) {
|
|
120
120
|
if (origin === consoleOrigin)
|
|
121
121
|
return true;
|
|
122
|
-
const
|
|
122
|
+
const parseLoopbackPort = (value) => {
|
|
123
123
|
const match = /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.exec(value);
|
|
124
|
-
return match ?
|
|
124
|
+
return match ? (match[2] ?? ":80") : null;
|
|
125
125
|
};
|
|
126
|
-
const originKey =
|
|
127
|
-
const consoleKey =
|
|
126
|
+
const originKey = parseLoopbackPort(origin);
|
|
127
|
+
const consoleKey = parseLoopbackPort(consoleOrigin);
|
|
128
128
|
return originKey !== null && consoleKey !== null && originKey === consoleKey;
|
|
129
129
|
}
|
|
130
130
|
export function evaluateMutationGate(req, ctx) {
|