@tea-agent/loop-agent 0.44.0-next.5 → 0.44.0-next.7
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 +299 -445
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +7 -7
- package/dist/infrastructure/console/operation-store.js +46 -14
- package/dist/shared/git-workspace-status.js +2 -0
- package/dist/shared/wizard-local-telemetry.js +2 -1
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +27 -10
- package/dist/worker/console/chat/db-connections.js +72 -8
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +52 -24
- package/dist/worker/console/chat/pi-runtime.js +251 -18
- package/dist/worker/console/chat/repo-browser.js +49 -0
- package/dist/worker/console/chat/routes.js +215 -66
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/turn-process.js +69 -23
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BHuhGM1e.js → abnfDiagram-N423BO3Z-DTuku_-7.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CKh5E_sr.js → arc-CQbCsSqb.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DizhiBEv.js → architectureDiagram-T3A2C74G-C-xUntTR.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-3Neea-Z-.js → blockDiagram-VBNYF7ZC-DhNlwGAl.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCP_Qrgi.js → c4Diagram-5PPSVZJV-B7j3nx8o.js} +1 -1
- package/dist/worker/console/static/assets/channel-5_os7t_x.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C_wydftK.js → chunk-2GRJ4B5K-BCrcxEh2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-jRPXZbzx.js → chunk-2Q5K7J3B-CsaePDb3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BLQf2OYO.js → chunk-5RXB4S5H--SRQc1rN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-0qRq64rI.js → chunk-5VM5RSS4-C5BFY2Po.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-ClMg8xmV.js → chunk-6Q2QTUOP-Caso6uN7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Coop2FlL.js → chunk-GF5L2VYU-AY2ZSAx2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CDdO-jPJ.js → chunk-JWPE2WC7-DiMqZQ_8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D8Gkppjl.js → chunk-KBJHAD2P-gpvvriOp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DA9HIySf.js → chunk-RYQCIY6F-CUdyoJhI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DZNDRX1k.js → chunk-XXDRQBXY-Bp1xW3T4.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-loz0n18W.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-loz0n18W.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CZ1zGyjq.js → cose-bilkent-JH36ORCC-D-JIcOIT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B8u1_FtM.js → cynefin-VYW2F7L2-C-NKFflx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CPBB6lfo.js → cynefinDiagram-MW4NZA55-CSOcvOAA.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CG3Eov3E.js → dagre-VZM6K2ZE-BQSBltMy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ycI-dOjh.js → diagram-7IWD3JNH-B0LVC45G.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bx_GyQ7Y.js → diagram-B4RE2ZJO-Cypg3nqy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-m0G6mP4d.js → diagram-LBJQPF4R-BBDU88XJ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DTDJkqcJ.js → diagram-Q27KOJAE-BZusLqSb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BCfFrG-p.js → diagram-UB23O5K3-Cfolotcr.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BIkTNPpp.js → ebnfDiagram-BXEA7PRR-CjfyUy0m.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DTcMWrd2.js → erDiagram-JOGREHBK-DJdz7txE.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqzDaNQ7.js → flowDiagram-UKHOOZJN-CQqgPO6y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BSl_vGFe.js → ganttDiagram-PKOTCBZU-BOpGfS2m.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCA5HXM6.js → gitGraphDiagram-DS77QQ5N-C6UCu0qx.js} +1 -1
- package/dist/worker/console/static/assets/index-CUT440A8.css +1 -0
- package/dist/worker/console/static/assets/index-DWKejNVJ.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DvqT18ge.js → infoDiagram-6WML65LV-BWeaVEUB.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js → ishikawaDiagram-WSZJBQD7-C8gmlBbk.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BpLQPI9C.js → journeyDiagram-NVQOT4AX-DF3bm8Bp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CzIz_-Lt.js → kanban-definition-27J2QSJJ-D2fG2Vxi.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CMQ8Jpp-.js → linear-BGKgMc2T.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BdAMrTf7.js → mermaid.core-DF6mMPJ9.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-hSwF6sOq.js → mindmap-definition-FAOFIHXS-AMhePheC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DZdv509B.js → pegDiagram-VL7TDLO6-DN1Cl0W0.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CmD4-b0K.js → pieDiagram-7S7Q4E2Y-Cx9ngUeN.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKxNxlS.js → quadrantDiagram-CIZ2JOQS-BcYPTQJb.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-D48iIoiS.js → railroadDiagram-AXF67PYL-BqHUZWnH.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C3N0K-2p.js → requirementDiagram-LRYGKXZP-CmqWZ0-B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CACWR-Xs.js → sankeyDiagram-W5VNT64P--4YetH1y.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-g_QgugvZ.js → sequenceDiagram-SI44F4Z6-DGD4VMfD.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DqLoRPGK.js → sizeCapture-X5ZJPWSS-QVw9q7rh.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-ES59VgMG.js → stateDiagram-OKZ733FA-CuemV9pe.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BdlGdIJw.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-fjyLVNWo.js → swimlanes-SLNWSIFB-C7oKzZKs.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DjjbHbde.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cd5-biKv.js → timeline-definition-Z64GVDOM-C2Of60nT.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CZTu6mPf.js → vennDiagram-T6HMQDX7-DHtpQHyL.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DR01jjbe.js → wardleyDiagram-T6FBY63Y-COi9OhSy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-iZcuGVGa.js → xychartDiagram-ELKLHX3M-DPHQfRrt.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +58 -3
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +19 -6
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +202 -41
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +55 -18
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +6 -1
- package/docs/templates/backend-test-dag.json +1 -0
- package/package.json +4 -4
- package/dist/worker/console/static/assets/channel-DxgzA1GF.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/index-BAEDjTbD.css +0 -1
- package/dist/worker/console/static/assets/index-wKEJUaP5.js +0 -482
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +0 -8
|
@@ -19,14 +19,14 @@ import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_
|
|
|
19
19
|
import { InvalidChatSessionModeError, SESSION_MODE_ACTION_DENIED_CODE, SESSION_MODE_ACTION_DENIED_MESSAGE, SESSION_MODE_IMMUTABLE_CODE, UnknownChatSessionModeError, isWorkflowSessionMode, parseCreateSessionMode, readStoredSessionMode, shouldAcceptOperatorSnapshot, } from "./session-mode.js";
|
|
20
20
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
21
21
|
import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
|
|
22
|
+
import { projectUserTurnOutline } from "./user-turn-outline.js";
|
|
22
23
|
import { hashChatTurnPayload, projectChatToolEventPayload, projectUnknownChatEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
|
|
23
24
|
import { projectDeferredTurnSummary, projectDeferredTurnSummaries, } from "./deferred-turn.js";
|
|
24
25
|
import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
|
|
25
26
|
import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./resource-preferences-store.js";
|
|
26
27
|
import { ComposerDraftStore } from "./composer-draft-store.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { listRepoDirectory, matchRepoHtmlPreviewRoute, readRepoHtmlAsset, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
|
|
28
|
+
import { scrubSecrets } from "./explore-tools.js";
|
|
29
|
+
import { listChatFilePalette, listRepoDirectory, matchRepoHtmlPreviewRoute, readRepoHtmlAsset, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
|
|
30
30
|
import { projectRuntimeContext } from "./runtime-context.js";
|
|
31
31
|
import { patchModelsConfig, readModelsConfig, readPackageInventory, } from "./pi-console-config.js";
|
|
32
32
|
import { ChatInterviewAdapter, projectChatInterviewState, } from "./interview-adapter.js";
|
|
@@ -66,6 +66,14 @@ function reopenInitFromRecord(record) {
|
|
|
66
66
|
sessionMode: record.sessionMode,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
function workspaceProjectionFromRecord(record) {
|
|
70
|
+
return {
|
|
71
|
+
sessionMode: record.sessionMode,
|
|
72
|
+
...(record.modelProvider ? { modelProvider: record.modelProvider } : {}),
|
|
73
|
+
...(record.modelId ? { modelId: record.modelId } : {}),
|
|
74
|
+
...(record.thinkingLevel ? { thinkingLevel: record.thinkingLevel } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
69
77
|
async function readChatSessionMode(deps, sessionId) {
|
|
70
78
|
let record;
|
|
71
79
|
try {
|
|
@@ -224,6 +232,43 @@ function turnSummary(turn) {
|
|
|
224
232
|
abortRequestedAt: turn.abortRequestedAt ?? null,
|
|
225
233
|
};
|
|
226
234
|
}
|
|
235
|
+
function turnStateSummary(turn) {
|
|
236
|
+
return {
|
|
237
|
+
turnId: turn.turnId,
|
|
238
|
+
state: turn.state === "queued" ? "running" : turn.state,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function turnStatesForIds(events, sessionId, turnIds) {
|
|
242
|
+
const projected = new Map();
|
|
243
|
+
for (const turnId of turnIds) {
|
|
244
|
+
if (!turnId || projected.has(turnId))
|
|
245
|
+
continue;
|
|
246
|
+
const turn = events.getTurn(sessionId, turnId);
|
|
247
|
+
if (turn)
|
|
248
|
+
projected.set(turnId, turnStateSummary(turn));
|
|
249
|
+
}
|
|
250
|
+
return [...projected.values()];
|
|
251
|
+
}
|
|
252
|
+
const MAX_STATE_QUERY_TURN_IDS = 8;
|
|
253
|
+
const MAX_STATE_QUERY_TURN_ID_CHARS = 240;
|
|
254
|
+
function requestedStateTurnIds(req) {
|
|
255
|
+
const result = [];
|
|
256
|
+
const seen = new Set();
|
|
257
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
258
|
+
for (const raw of url.searchParams.getAll("turnId")) {
|
|
259
|
+
const turnId = raw.trim();
|
|
260
|
+
if (!turnId ||
|
|
261
|
+
turnId.length > MAX_STATE_QUERY_TURN_ID_CHARS ||
|
|
262
|
+
seen.has(turnId)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
seen.add(turnId);
|
|
266
|
+
result.push(turnId);
|
|
267
|
+
if (result.length >= MAX_STATE_QUERY_TURN_IDS)
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
227
272
|
function isTerminalTurnState(state) {
|
|
228
273
|
return state === "settled" || state === "aborted" || state === "failed";
|
|
229
274
|
}
|
|
@@ -414,6 +459,20 @@ export function turnEventToStorePartial(event) {
|
|
|
414
459
|
: {}),
|
|
415
460
|
}),
|
|
416
461
|
};
|
|
462
|
+
case "tool_update":
|
|
463
|
+
return {
|
|
464
|
+
kind: "tool_update",
|
|
465
|
+
data: projectChatToolEventPayload({
|
|
466
|
+
kind: "tool_update",
|
|
467
|
+
toolCallId: String(event.toolCallId ?? ""),
|
|
468
|
+
toolName: String(event.toolName ?? ""),
|
|
469
|
+
value: event.partialResult,
|
|
470
|
+
...(typeof event.step === "number" ? { step: event.step } : {}),
|
|
471
|
+
...(typeof event.parentToolCallId === "string"
|
|
472
|
+
? { parentToolCallId: event.parentToolCallId }
|
|
473
|
+
: {}),
|
|
474
|
+
}),
|
|
475
|
+
};
|
|
417
476
|
case "tool_result": {
|
|
418
477
|
// Plan A (2026-08-16): surface the accepted-operation link on the live
|
|
419
478
|
// SSE frame so the frontend semantic event can resolve operation:<id>.
|
|
@@ -917,25 +976,47 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
|
|
|
917
976
|
});
|
|
918
977
|
return;
|
|
919
978
|
}
|
|
979
|
+
let forkedHandle;
|
|
980
|
+
let forkPersisted = false;
|
|
920
981
|
try {
|
|
982
|
+
const body = await readJsonBody(req);
|
|
983
|
+
const throughMessageId = typeof body.throughMessageId === "string" && body.throughMessageId.trim()
|
|
984
|
+
? body.throughMessageId.trim()
|
|
985
|
+
: undefined;
|
|
986
|
+
const throughUserMessageIndex = throughMessageId
|
|
987
|
+
? source.messages
|
|
988
|
+
.filter((message) => message.role === "user")
|
|
989
|
+
.findIndex((message) => message.id === throughMessageId)
|
|
990
|
+
: undefined;
|
|
991
|
+
if (throughMessageId && throughUserMessageIndex === -1) {
|
|
992
|
+
throw new Error("FORK_MESSAGE_NOT_FOUND");
|
|
993
|
+
}
|
|
921
994
|
const handle = await deps.runtime.forkSession({
|
|
922
995
|
sourceSessionFile: source.sessionFile ?? "",
|
|
923
996
|
sessionMode: source.sessionMode,
|
|
924
997
|
...(source.modelProvider && source.modelId
|
|
925
998
|
? { model: { provider: source.modelProvider, modelId: source.modelId } }
|
|
926
999
|
: {}),
|
|
1000
|
+
...(throughUserMessageIndex !== undefined
|
|
1001
|
+
? { throughUserMessageIndex }
|
|
1002
|
+
: {}),
|
|
927
1003
|
});
|
|
1004
|
+
forkedHandle = handle;
|
|
928
1005
|
await deps.store.forkFrom({
|
|
929
1006
|
sourceSessionId,
|
|
930
1007
|
targetSessionId: handle.sessionId,
|
|
931
1008
|
targetSessionFile: handle.sessionFile,
|
|
1009
|
+
...(throughMessageId ? { throughMessageId } : {}),
|
|
932
1010
|
});
|
|
1011
|
+
forkPersisted = true;
|
|
933
1012
|
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
934
|
-
const copied =
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1013
|
+
const copied = throughMessageId
|
|
1014
|
+
? { copiedCount: 0 }
|
|
1015
|
+
: deps.events.forkContextRefs({
|
|
1016
|
+
sourceSessionId,
|
|
1017
|
+
targetSessionId: handle.sessionId,
|
|
1018
|
+
targetTurnId: `${handle.sessionId}:fork`,
|
|
1019
|
+
});
|
|
939
1020
|
await deps.operationLinker.recoverSession(handle.sessionId);
|
|
940
1021
|
sendJson(res, 201, {
|
|
941
1022
|
ok: true,
|
|
@@ -949,6 +1030,12 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
|
|
|
949
1030
|
});
|
|
950
1031
|
}
|
|
951
1032
|
catch (error) {
|
|
1033
|
+
if (forkedHandle && !forkPersisted) {
|
|
1034
|
+
deps.runtime.dispose(forkedHandle.sessionId);
|
|
1035
|
+
await deps.store
|
|
1036
|
+
.discardUnownedSession(forkedHandle.sessionId, forkedHandle.sessionFile)
|
|
1037
|
+
.catch(() => undefined);
|
|
1038
|
+
}
|
|
952
1039
|
sendJson(res, 409, {
|
|
953
1040
|
ok: false,
|
|
954
1041
|
error: {
|
|
@@ -1098,12 +1185,18 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
1098
1185
|
const sddSessionIdentity = deps.sddDataAlignment && (await deps.sddDataAlignment.isEligible())
|
|
1099
1186
|
? { telemetrySessionId: record.telemetrySessionId ?? null }
|
|
1100
1187
|
: undefined;
|
|
1188
|
+
const projectedMessages = projectStoredMessagesWithEventOrder(messagePage.messages, snapshot);
|
|
1189
|
+
const turnStates = turnStatesForIds(deps.events, sessionId, [
|
|
1190
|
+
...projectedMessages.map((message) => message.turnId),
|
|
1191
|
+
...snapshot.map((event) => event.turnId),
|
|
1192
|
+
]);
|
|
1101
1193
|
sendJson(res, 200, {
|
|
1102
1194
|
ok: true,
|
|
1103
1195
|
eventEpoch: deps.events.epoch(sessionId),
|
|
1104
1196
|
session: {
|
|
1105
1197
|
...projectBrowserChatSession(record),
|
|
1106
|
-
messages:
|
|
1198
|
+
messages: projectedMessages,
|
|
1199
|
+
turnStates,
|
|
1107
1200
|
composerDraft,
|
|
1108
1201
|
activeTurnId: activeTurn?.turnId,
|
|
1109
1202
|
turnPhase: activeTurn
|
|
@@ -1123,6 +1216,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
1123
1216
|
sessionStats: telemetry.stats,
|
|
1124
1217
|
usage: telemetry.usage,
|
|
1125
1218
|
turnTelemetry: deriveChatTurnTelemetry(snapshot),
|
|
1219
|
+
userTurnOutline: projectUserTurnOutline(record.messages ?? []),
|
|
1126
1220
|
...(sddSessionIdentity
|
|
1127
1221
|
? { sddDataAlignment: sddSessionIdentity }
|
|
1128
1222
|
: {}),
|
|
@@ -1401,7 +1495,14 @@ function emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientReques
|
|
|
1401
1495
|
data: {
|
|
1402
1496
|
...(clientRequestId ? { clientRequestId } : {}),
|
|
1403
1497
|
...(steering ? { steering: true } : {}),
|
|
1404
|
-
message: {
|
|
1498
|
+
message: {
|
|
1499
|
+
id: userMessage.id,
|
|
1500
|
+
text: userMessage.text,
|
|
1501
|
+
at: userMessage.at,
|
|
1502
|
+
...(userMessage.attachments?.length
|
|
1503
|
+
? { attachments: userMessage.attachments }
|
|
1504
|
+
: {}),
|
|
1505
|
+
},
|
|
1405
1506
|
},
|
|
1406
1507
|
});
|
|
1407
1508
|
}
|
|
@@ -1409,7 +1510,7 @@ function emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientReques
|
|
|
1409
1510
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
1410
1511
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
1411
1512
|
* interval / visibility / online without paying for session detail. */
|
|
1412
|
-
export async function handleGetChatSessionState(
|
|
1513
|
+
export async function handleGetChatSessionState(req, res, deps, sessionId) {
|
|
1413
1514
|
const record = await deps.store.get(sessionId);
|
|
1414
1515
|
if (!record) {
|
|
1415
1516
|
sendJson(res, 404, {
|
|
@@ -1440,9 +1541,15 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
1440
1541
|
: runtimeBusy
|
|
1441
1542
|
? "running"
|
|
1442
1543
|
: "idle";
|
|
1544
|
+
const turnStates = turnStatesForIds(deps.events, sessionId, [
|
|
1545
|
+
active?.turnId,
|
|
1546
|
+
latest?.turnId,
|
|
1547
|
+
...requestedStateTurnIds(req),
|
|
1548
|
+
]);
|
|
1443
1549
|
sendJson(res, 200, {
|
|
1444
1550
|
ok: true,
|
|
1445
1551
|
state: {
|
|
1552
|
+
turnStates,
|
|
1446
1553
|
// Legacy flat fields retained for older clients.
|
|
1447
1554
|
activeTurnId: active?.turnId ?? null,
|
|
1448
1555
|
activeTurnState: active?.state ?? null,
|
|
@@ -3021,28 +3128,8 @@ async function handlePiConfig(req, res, deps, resource) {
|
|
|
3021
3128
|
async function handleChatFiles(res, deps, sessionId, query) {
|
|
3022
3129
|
if (!(await requireChatSession(res, deps, sessionId)))
|
|
3023
3130
|
return;
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3026
|
-
skipSensitive: true,
|
|
3027
|
-
maxFiles: 5000,
|
|
3028
|
-
includeDirs: true,
|
|
3029
|
-
});
|
|
3030
|
-
const entries = walked
|
|
3031
|
-
.filter((entry) => !isSensitivePath(entry.repoRelative) &&
|
|
3032
|
-
(!q || entry.repoRelative.toLowerCase().includes(q)))
|
|
3033
|
-
.sort((a, b) => {
|
|
3034
|
-
const aDir = a.repoRelative.endsWith("/");
|
|
3035
|
-
const bDir = b.repoRelative.endsWith("/");
|
|
3036
|
-
if (aDir !== bDir)
|
|
3037
|
-
return aDir ? -1 : 1;
|
|
3038
|
-
return a.repoRelative.localeCompare(b.repoRelative);
|
|
3039
|
-
})
|
|
3040
|
-
.slice(0, 50)
|
|
3041
|
-
.map((entry) => ({
|
|
3042
|
-
path: entry.repoRelative,
|
|
3043
|
-
isDir: entry.repoRelative.endsWith("/"),
|
|
3044
|
-
}));
|
|
3045
|
-
sendJson(res, 200, { ok: true, files: entries });
|
|
3131
|
+
const files = await listChatFilePalette(deps.appData.repoRoot, query);
|
|
3132
|
+
sendJson(res, 200, { ok: true, files });
|
|
3046
3133
|
}
|
|
3047
3134
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
3048
3135
|
try {
|
|
@@ -3146,20 +3233,25 @@ async function handleSddBingo(req, res, deps, sessionId) {
|
|
|
3146
3233
|
}
|
|
3147
3234
|
}
|
|
3148
3235
|
export async function handleRuntimeContext(res, deps, sessionId) {
|
|
3149
|
-
const
|
|
3150
|
-
if (!
|
|
3151
|
-
sendJson(res,
|
|
3236
|
+
const record = await deps.store.get(sessionId);
|
|
3237
|
+
if (!record) {
|
|
3238
|
+
sendJson(res, 404, {
|
|
3239
|
+
ok: false,
|
|
3240
|
+
error: {
|
|
3241
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
3242
|
+
message: `chat session not found: ${sessionId}`,
|
|
3243
|
+
},
|
|
3244
|
+
});
|
|
3152
3245
|
return;
|
|
3153
3246
|
}
|
|
3154
|
-
//
|
|
3155
|
-
//
|
|
3156
|
-
//
|
|
3157
|
-
// length-limited projection. RF-02: a critical projection failure is 503
|
|
3158
|
-
// PI_RUNTIME_UNAVAILABLE — never a 500 leak or a fake-empty context.
|
|
3247
|
+
// Live Agent is preferred. Cold inspect uses workspace ResourceLoader /
|
|
3248
|
+
// ModelRuntime without SessionManager.open(jsonl); prompt still reopens.
|
|
3249
|
+
// RF-02: a critical projection failure is 503 PI_RUNTIME_UNAVAILABLE.
|
|
3159
3250
|
let snapshot;
|
|
3160
3251
|
try {
|
|
3161
3252
|
snapshot = await deps.runtime.getRuntimeSnapshot(sessionId, {
|
|
3162
3253
|
includeRaw: true,
|
|
3254
|
+
workspaceProjection: workspaceProjectionFromRecord(record),
|
|
3163
3255
|
});
|
|
3164
3256
|
}
|
|
3165
3257
|
catch (error) {
|
|
@@ -3234,6 +3326,7 @@ export async function handleContextInsights(res, deps, sessionId) {
|
|
|
3234
3326
|
try {
|
|
3235
3327
|
const runtimeSnapshot = await deps.runtime.getRuntimeSnapshot(sessionId, {
|
|
3236
3328
|
includeRaw: true,
|
|
3329
|
+
workspaceProjection: workspaceProjectionFromRecord(record),
|
|
3237
3330
|
});
|
|
3238
3331
|
if (runtimeSnapshot) {
|
|
3239
3332
|
systemPromptChars = runtimeSnapshot.systemPrompt.characters;
|
|
@@ -3279,16 +3372,23 @@ export async function handleContextInsights(res, deps, sessionId) {
|
|
|
3279
3372
|
// Exported for the direct handler-boundary tests (trace binding for
|
|
3280
3373
|
// vt-route-model-errors); router dispatch behavior is unchanged.
|
|
3281
3374
|
export async function handleModels(req, res, deps, sessionId) {
|
|
3282
|
-
// AC-01/AC-06: models (and model/thinking mutation) requests reuse the same
|
|
3283
|
-
// activation path. A missing/reopen-failed runtime is a STRUCTURED error —
|
|
3284
|
-
// never a fabricated empty model list.
|
|
3285
|
-
const ensured = await ensureActiveSession(deps, sessionId);
|
|
3286
|
-
if (!ensured.ok) {
|
|
3287
|
-
sendJson(res, ensured.status, { ok: false, error: { code: ensured.code, message: ensured.message } });
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
3375
|
try {
|
|
3291
3376
|
if ((req.method ?? "GET").toUpperCase() === "GET") {
|
|
3377
|
+
// Catalog is workspace-scoped (models.json / ModelRuntime). Do not wait
|
|
3378
|
+
// for Pi conversation reopen and do not start it here — prompt / reload
|
|
3379
|
+
// / model mutation still reopen, and concurrent reopen would stall this
|
|
3380
|
+
// cheap list on the single Node thread.
|
|
3381
|
+
const record = await deps.store.get(sessionId);
|
|
3382
|
+
if (!record) {
|
|
3383
|
+
sendJson(res, 404, {
|
|
3384
|
+
ok: false,
|
|
3385
|
+
error: {
|
|
3386
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
3387
|
+
message: `chat session not found: ${sessionId}`,
|
|
3388
|
+
},
|
|
3389
|
+
});
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3292
3392
|
let models;
|
|
3293
3393
|
try {
|
|
3294
3394
|
models = await deps.runtime.listModels(sessionId);
|
|
@@ -3317,6 +3417,11 @@ export async function handleModels(req, res, deps, sessionId) {
|
|
|
3317
3417
|
});
|
|
3318
3418
|
return;
|
|
3319
3419
|
}
|
|
3420
|
+
const ensured = await ensureActiveSession(deps, sessionId);
|
|
3421
|
+
if (!ensured.ok) {
|
|
3422
|
+
sendJson(res, ensured.status, { ok: false, error: { code: ensured.code, message: ensured.message } });
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3320
3425
|
const gate = gateMutation(req, deps);
|
|
3321
3426
|
if (!gate.ok) {
|
|
3322
3427
|
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
@@ -3372,18 +3477,24 @@ async function requireChatSession(res, deps, sessionId) {
|
|
|
3372
3477
|
});
|
|
3373
3478
|
return false;
|
|
3374
3479
|
}
|
|
3375
|
-
|
|
3376
|
-
* Unified session activation (design §2.1 / AC-01): models, runtime snapshot,
|
|
3377
|
-
* reload and model/thinking mutation requests all flow through this path.
|
|
3378
|
-
*
|
|
3379
|
-
* - runtime already holds the session → ok;
|
|
3380
|
-
* - only a persisted record exists → reopen from record.sessionFile;
|
|
3381
|
-
* - no record / missing session file → 404 PI_SESSION_NOT_FOUND;
|
|
3382
|
-
* - SDK reopen failure → 503 PI_RUNTIME_UNAVAILABLE.
|
|
3383
|
-
*
|
|
3384
|
-
* Never fabricates a fresh session or an empty model list.
|
|
3385
|
-
*/
|
|
3480
|
+
const ensureActiveSessionInFlight = new WeakMap();
|
|
3386
3481
|
export async function ensureActiveSession(deps, sessionId) {
|
|
3482
|
+
let bySession = ensureActiveSessionInFlight.get(deps);
|
|
3483
|
+
if (!bySession) {
|
|
3484
|
+
bySession = new Map();
|
|
3485
|
+
ensureActiveSessionInFlight.set(deps, bySession);
|
|
3486
|
+
}
|
|
3487
|
+
const existing = bySession.get(sessionId);
|
|
3488
|
+
if (existing)
|
|
3489
|
+
return existing;
|
|
3490
|
+
const pending = runEnsureActiveSession(deps, sessionId).finally(() => {
|
|
3491
|
+
if (bySession.get(sessionId) === pending)
|
|
3492
|
+
bySession.delete(sessionId);
|
|
3493
|
+
});
|
|
3494
|
+
bySession.set(sessionId, pending);
|
|
3495
|
+
return pending;
|
|
3496
|
+
}
|
|
3497
|
+
async function runEnsureActiveSession(deps, sessionId) {
|
|
3387
3498
|
// Wait for create-path materialization before treating the session as missing.
|
|
3388
3499
|
if (typeof deps.runtime.ensureSessionReady === "function") {
|
|
3389
3500
|
try {
|
|
@@ -3629,19 +3740,28 @@ export async function handleReloadSession(req, res, deps, sessionId) {
|
|
|
3629
3740
|
});
|
|
3630
3741
|
}
|
|
3631
3742
|
/**
|
|
3632
|
-
* GET /sessions/:id/runtime-snapshot (AC-01):
|
|
3743
|
+
* GET /sessions/:id/runtime-snapshot (AC-01): live Agent when present,
|
|
3744
|
+
* otherwise workspace ResourceLoader projection (no JSONL reopen).
|
|
3633
3745
|
*/
|
|
3634
3746
|
export async function handleRuntimeSnapshot(_res, res, deps, sessionId) {
|
|
3635
|
-
const
|
|
3636
|
-
if (!
|
|
3637
|
-
sendJson(res,
|
|
3747
|
+
const record = await deps.store.get(sessionId);
|
|
3748
|
+
if (!record) {
|
|
3749
|
+
sendJson(res, 404, {
|
|
3750
|
+
ok: false,
|
|
3751
|
+
error: {
|
|
3752
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
3753
|
+
message: `chat session not found: ${sessionId}`,
|
|
3754
|
+
},
|
|
3755
|
+
});
|
|
3638
3756
|
return;
|
|
3639
3757
|
}
|
|
3640
3758
|
// RF-02: a critical snapshot projection failure is 503 PI_RUNTIME_UNAVAILABLE
|
|
3641
3759
|
// — never a 200 fake-empty snapshot.
|
|
3642
3760
|
let snapshot;
|
|
3643
3761
|
try {
|
|
3644
|
-
snapshot = await deps.runtime.getRuntimeSnapshot(sessionId
|
|
3762
|
+
snapshot = await deps.runtime.getRuntimeSnapshot(sessionId, {
|
|
3763
|
+
workspaceProjection: workspaceProjectionFromRecord(record),
|
|
3764
|
+
});
|
|
3645
3765
|
}
|
|
3646
3766
|
catch (error) {
|
|
3647
3767
|
if (sendRuntimeUnavailable(res, error))
|
|
@@ -4464,7 +4584,14 @@ export async function handleChatMessages(res, deps, sessionId, url) {
|
|
|
4464
4584
|
limit: Number.isFinite(limit) ? limit : 30,
|
|
4465
4585
|
beforeId: url.searchParams.get("beforeId") ?? undefined,
|
|
4466
4586
|
});
|
|
4467
|
-
|
|
4587
|
+
const snapshot = deps.events.snapshot(sessionId);
|
|
4588
|
+
const messages = projectStoredMessagesWithEventOrder(page.messages, snapshot);
|
|
4589
|
+
sendJson(res, 200, {
|
|
4590
|
+
ok: true,
|
|
4591
|
+
...page,
|
|
4592
|
+
messages,
|
|
4593
|
+
turnStates: turnStatesForIds(deps.events, sessionId, messages.map((message) => message.turnId)),
|
|
4594
|
+
});
|
|
4468
4595
|
}
|
|
4469
4596
|
catch (error) {
|
|
4470
4597
|
sendJson(res, 404, {
|
|
@@ -4476,6 +4603,23 @@ export async function handleChatMessages(res, deps, sessionId, url) {
|
|
|
4476
4603
|
});
|
|
4477
4604
|
}
|
|
4478
4605
|
}
|
|
4606
|
+
export async function handleChatAttachment(res, deps, sessionId, attachmentId) {
|
|
4607
|
+
const attachment = await deps.store.readAttachment(sessionId, attachmentId);
|
|
4608
|
+
if (!attachment) {
|
|
4609
|
+
sendJson(res, 404, {
|
|
4610
|
+
ok: false,
|
|
4611
|
+
error: { code: "NOT_FOUND", message: "chat attachment not found" },
|
|
4612
|
+
});
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
res.writeHead(200, {
|
|
4616
|
+
"content-type": attachment.metadata.mimeType,
|
|
4617
|
+
"content-length": String(attachment.bytes.length),
|
|
4618
|
+
"cache-control": "private, max-age=31536000, immutable",
|
|
4619
|
+
"x-content-type-options": "nosniff",
|
|
4620
|
+
});
|
|
4621
|
+
res.end(attachment.bytes);
|
|
4622
|
+
}
|
|
4479
4623
|
/**
|
|
4480
4624
|
* Dispatch a chat HTTP request. Returns true if handled.
|
|
4481
4625
|
* Mounted under /api/operator/v1/chat/*.
|
|
@@ -4913,6 +5057,11 @@ async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
|
|
|
4913
5057
|
await handleChatMessages(res, deps, decodeURIComponent(sessionMessages[1]), url);
|
|
4914
5058
|
return true;
|
|
4915
5059
|
}
|
|
5060
|
+
const sessionAttachment = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/attachments\/([^/]+)$/);
|
|
5061
|
+
if (method === "GET" && sessionAttachment) {
|
|
5062
|
+
await handleChatAttachment(res, deps, decodeURIComponent(sessionAttachment[1]), decodeURIComponent(sessionAttachment[2]));
|
|
5063
|
+
return true;
|
|
5064
|
+
}
|
|
4916
5065
|
const sessionPrompt = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/prompt$/);
|
|
4917
5066
|
if (method === "POST" && sessionPrompt) {
|
|
4918
5067
|
await handleChatPrompt(req, res, deps, decodeURIComponent(sessionPrompt[1]));
|