@tea-agent/loop-agent 0.40.0-next.4 → 0.40.0-next.5
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/CHANGELOG.md +20 -0
- package/dist/build-stamp.json +3 -3
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- 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 +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +292 -29
- 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 +14 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/routes.js +309 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +529 -186
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CGZrpj0K.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-CwEyA8iq.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-DLTkrEQ3.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-4w12zR3t.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-BppqLAjk.js} +1 -1
- package/dist/worker/console/static/assets/channel-TzuwQha5.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DO6U26Pm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-DC7YEdOy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtMYVQBw.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-C5Ed-57N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-BUY4oIzT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-VnWHALIV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-COV-RQk4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-dSnLtUiz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-CX6FDzWh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D3mEgb6W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DCu69b3S.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DCu69b3S.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-QQBMUnAf.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-BxSD-LVV.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-DxHNiNbk.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-C83bYy8E.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-BhtZu2YY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-K0P_ntRE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-CXYy1Nib.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-D-CtIFkU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BEK9-_7c.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-L0EX-9wn.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-9pt0fc_5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-CZVeM_n1.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-DqUKkAZP.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-BYUuXEgp.js} +1 -1
- package/dist/worker/console/static/assets/index-B0jGzQj6.js +407 -0
- package/dist/worker/console/static/assets/index-BiYbPiTw.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-DNzKk7ah.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-Clbyg5IP.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-zDO_gZNO.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-x3LpRYek.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CTpwZqKR.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-D_WXK6C7.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-CWK-OYP5.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-B9gtUPsM.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-Dj2lzceR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-Nwljut7m.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-BY-KNt93.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Vx0wvpPy.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-B6grNXUk.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-CteidBvh.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Du4W8mFK.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-jyI6Yh81.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyQblISl.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-DYWs_fRv.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DiygmgYP.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DO5LLy47.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CLF2p1_L.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-AwTaYBxA.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-DzbblQRw.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -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 +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- 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/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -17
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- 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 +337 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +26 -6
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/index.html +1 -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/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 +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +3 -2
- package/dist/worker/observe/static/views/dag.js +2 -1
- 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 +1 -1
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/docs/README.md +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/harness.json +3 -3
- package/package.json +3 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
|
@@ -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) {
|
|
@@ -245,7 +251,35 @@ async function handleGetOperation(_req, res, ctx, operationId) {
|
|
|
245
251
|
});
|
|
246
252
|
return;
|
|
247
253
|
}
|
|
248
|
-
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
|
+
});
|
|
249
283
|
}
|
|
250
284
|
async function handleOperationEvents(req, res, ctx, operationId) {
|
|
251
285
|
if (!ctx.operator) {
|
|
@@ -453,6 +487,10 @@ export async function handleConsoleOperatorRequest(req, res, ctx) {
|
|
|
453
487
|
await handleCreateOperation(req, res, ctx);
|
|
454
488
|
return true;
|
|
455
489
|
}
|
|
490
|
+
if (method === "GET" && pathname === "/api/operator/v1/operations") {
|
|
491
|
+
await handleListOperations(req, res, ctx);
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
456
494
|
if (method === "GET" && pathname === "/api/operator/v1/night-aux-ticker") {
|
|
457
495
|
await handleNightAuxTickerGet(req, res, ctx);
|
|
458
496
|
return true;
|
|
@@ -517,3 +555,273 @@ export async function handleConsoleRequest(req, res, ctx) {
|
|
|
517
555
|
}
|
|
518
556
|
return false;
|
|
519
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) {
|