@tea-agent/loop-agent 0.40.0-next.8 → 0.40.0
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 +21 -20
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +3 -0
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +79 -6
- package/dist/executors/shell-executor.js +94 -20
- package/dist/governance/checks.js +1 -0
- package/dist/shared/operator/capabilities.js +9 -1
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +82 -11
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +495 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +27 -18
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +42 -5
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +20 -5
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +246 -6
- package/dist/worker/console/server.js +173 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BYBmDfPW.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BKMmxpGF.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DJpwxr1d.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BaPj_ZlY.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8iGmHlm.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
- package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DaxXQayc.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DqQKGA7q.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CgMKpJBB.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BpToUlaW.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BsEWOHxv.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CKGSCe71.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Bm1U5299.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BI0ov870.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BmnQo0iJ.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJBqa6Xc.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BuN9-7AR.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bw22jc2k.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BHnSH8Dr.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BoXXIegj.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CU8qyPhT.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-lrjhF0S8.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DL0QDEpo.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DxVMJMBH.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CFg9FzvG.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CQgP9fTG.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-A5vQXx9U.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-ytXnLyXD.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CYuksfnS.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-f_jW3zgU.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
- package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
- package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C3HLE-xV.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DwpgejS6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ll0YWX1y.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Hq3rchvU.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BaGjJx8z.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CKGQHWX_.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DM1FA6Dr.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Xa9fHkE2.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-pCd4PDqJ.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-KqjpEBLk.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DgvR7LZD.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bys1UCWA.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Ms9npnZt.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DEIWrtQZ.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DELJ0AhZ.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BpHt9wKh.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-GKRsBluT.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cwsq1DxL.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CfaqtKx0.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BlEUtK_g.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CHSRXPvO.js → xychartDiagram-ELKLHX3M-nniBEGv2.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +28 -3
- package/dist/worker/console/workspace-registry.js +333 -60
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/styles.css +87 -25
- package/dist/worker/observe/static/views/dag-inspector.js +42 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +5 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +18 -12
- package/dist/workflows/dag/init-hybrid.js +32 -9
- package/dist/workflows/dag/types.js +13 -0
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +27 -9
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-CrzAa3FA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/index-BB6KxwT_.css +0 -1
- package/dist/worker/console/static/assets/index-D_wxbadT.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BEeSswlv.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B2m_wca8.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
|
@@ -24,7 +24,7 @@ import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./r
|
|
|
24
24
|
import { ComposerDraftStore } from "./composer-draft-store.js";
|
|
25
25
|
import { walkRepoFiles } from "./repo-walk.js";
|
|
26
26
|
import { isSensitivePath, scrubSecrets } from "./explore-tools.js";
|
|
27
|
-
import { listRepoDirectory, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
|
|
27
|
+
import { listRepoDirectory, matchRepoHtmlPreviewRoute, readRepoHtmlAsset, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
|
|
28
28
|
import { projectRuntimeContext } from "./runtime-context.js";
|
|
29
29
|
import { patchModelsConfig, readModelsConfig, readPackageInventory, } from "./pi-console-config.js";
|
|
30
30
|
import { ChatInterviewAdapter, projectChatInterviewState, } from "./interview-adapter.js";
|
|
@@ -34,11 +34,84 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
34
34
|
import { makeHumanGateCard, } from "./human-gate-card.js";
|
|
35
35
|
import { ContractApplyReceiptStore } from "./contract-apply-receipt-store.js";
|
|
36
36
|
import { projectTaskContext } from "./context-panel.js";
|
|
37
|
-
import { deriveChatSessionTelemetry } from "./session-stats.js";
|
|
37
|
+
import { deriveChatSessionTelemetry, deriveChatTurnTelemetry } from "./session-stats.js";
|
|
38
38
|
import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, } from "../human-gate-token.js";
|
|
39
39
|
import { sendJson } from "../routes.js";
|
|
40
40
|
import { openSseResponse, writeSseEvent } from "../operation-sse.js";
|
|
41
41
|
import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
|
|
42
|
+
import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
|
|
43
|
+
import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
|
|
44
|
+
import { isTerminalOperationState } from "../operation-store.js";
|
|
45
|
+
/**
|
|
46
|
+
* Reattach the durable event-ring order to session-store rows. The session
|
|
47
|
+
* store remains the content authority; the ring only supplies chronology.
|
|
48
|
+
* This makes refresh hydration use the same turnId/seq ordering as live SSE.
|
|
49
|
+
*/
|
|
50
|
+
export function projectStoredMessagesWithEventOrder(messages, events) {
|
|
51
|
+
let assistantCursor = 0;
|
|
52
|
+
const nextAssistantEvent = (message) => {
|
|
53
|
+
const expectedKind = message.kind === "provider-error" ? "error" : "message_end";
|
|
54
|
+
const messageAt = Date.parse(message.at);
|
|
55
|
+
for (let index = assistantCursor; index < events.length; index += 1) {
|
|
56
|
+
const event = events[index];
|
|
57
|
+
if (event.kind !== expectedKind)
|
|
58
|
+
continue;
|
|
59
|
+
const data = event.data;
|
|
60
|
+
const eventText = typeof data.text === "string"
|
|
61
|
+
? data.text
|
|
62
|
+
: typeof data.message === "string"
|
|
63
|
+
? data.message
|
|
64
|
+
: undefined;
|
|
65
|
+
const closeInTime = Number.isFinite(messageAt) && Number.isFinite(Date.parse(event.at))
|
|
66
|
+
? Math.abs(Date.parse(event.at) - messageAt) <= 30_000
|
|
67
|
+
: true;
|
|
68
|
+
if (eventText === message.text && closeInTime) {
|
|
69
|
+
assistantCursor = index + 1;
|
|
70
|
+
return event;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// The bounded ring may no longer contain this old message. Leave it on the
|
|
74
|
+
// legacy timestamp fallback; never steal a newer event just to fill a seq.
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
77
|
+
return messages.map((message) => {
|
|
78
|
+
let start;
|
|
79
|
+
let finish;
|
|
80
|
+
if (message.role === "user") {
|
|
81
|
+
start = events.find((event) => {
|
|
82
|
+
if (event.kind !== "user_message")
|
|
83
|
+
return false;
|
|
84
|
+
const payload = event.data;
|
|
85
|
+
return payload.message?.id === message.id;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else if (message.role === "assistant") {
|
|
89
|
+
start = nextAssistantEvent(message);
|
|
90
|
+
}
|
|
91
|
+
else if (message.role === "tool" && message.toolCallId) {
|
|
92
|
+
start = events.find((event) => event.kind === "tool_call" &&
|
|
93
|
+
event.data.toolCallId ===
|
|
94
|
+
message.toolCallId);
|
|
95
|
+
finish = events.find((event) => event.kind === "tool_result" &&
|
|
96
|
+
event.data.toolCallId ===
|
|
97
|
+
message.toolCallId);
|
|
98
|
+
start ??= finish;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
...message,
|
|
102
|
+
...(start
|
|
103
|
+
? {
|
|
104
|
+
eventSeq: start.seq,
|
|
105
|
+
turnId: start.turnId,
|
|
106
|
+
startedAt: start.at,
|
|
107
|
+
}
|
|
108
|
+
: {}),
|
|
109
|
+
...(finish
|
|
110
|
+
? { finishedSeq: finish.seq, finishedAt: finish.at }
|
|
111
|
+
: {}),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
42
115
|
function turnSummary(turn) {
|
|
43
116
|
return {
|
|
44
117
|
turnId: turn.turnId,
|
|
@@ -514,6 +587,7 @@ workspaceId) {
|
|
|
514
587
|
// a fallback for direct (non-dispatcher) handler tests.
|
|
515
588
|
workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
|
|
516
589
|
});
|
|
590
|
+
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
517
591
|
if (snapshot)
|
|
518
592
|
deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
|
|
519
593
|
kind: "compact",
|
|
@@ -595,6 +669,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
|
|
|
595
669
|
targetSessionId: handle.sessionId,
|
|
596
670
|
targetSessionFile: handle.sessionFile,
|
|
597
671
|
});
|
|
672
|
+
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
598
673
|
const copied = deps.events.forkContextRefs({
|
|
599
674
|
sourceSessionId,
|
|
600
675
|
targetSessionId: handle.sessionId,
|
|
@@ -746,6 +821,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
746
821
|
eventEpoch: deps.events.epoch(sessionId),
|
|
747
822
|
session: {
|
|
748
823
|
...record,
|
|
824
|
+
messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
|
|
749
825
|
composerDraft,
|
|
750
826
|
activeTurnId: activeTurn?.turnId,
|
|
751
827
|
turnPhase: activeTurn
|
|
@@ -755,10 +831,15 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
755
831
|
: "idle",
|
|
756
832
|
pendingQuestion: pendingQuestion ?? null,
|
|
757
833
|
todos: projectCurrentTodos(snapshot) ?? [],
|
|
834
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
758
835
|
},
|
|
759
836
|
hasBash: true,
|
|
760
837
|
sessionStats: telemetry.stats,
|
|
761
838
|
usage: telemetry.usage,
|
|
839
|
+
turnTelemetry: deriveChatTurnTelemetry(snapshot),
|
|
840
|
+
contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
|
|
841
|
+
? deps.runtime.getContextPressureTelemetry(sessionId)
|
|
842
|
+
: undefined,
|
|
762
843
|
compactions: snapshot
|
|
763
844
|
.filter((event) => event.kind === "compact" ||
|
|
764
845
|
event.kind === "compaction_start" ||
|
|
@@ -767,6 +848,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
767
848
|
eventId: event.eventId,
|
|
768
849
|
kind: event.kind,
|
|
769
850
|
at: event.at,
|
|
851
|
+
seq: event.seq,
|
|
852
|
+
turnId: event.turnId,
|
|
770
853
|
data: event.data,
|
|
771
854
|
})),
|
|
772
855
|
lastEventId: lastEvent?.eventId ?? null,
|
|
@@ -1070,9 +1153,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
1070
1153
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1071
1154
|
pendingQuestion: pendingQuestion ?? null,
|
|
1072
1155
|
todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
|
|
1156
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
1073
1157
|
},
|
|
1074
1158
|
});
|
|
1075
1159
|
}
|
|
1160
|
+
function goalHttpStatus(code) {
|
|
1161
|
+
if (code === "GOAL_NOT_FOUND")
|
|
1162
|
+
return 404;
|
|
1163
|
+
if (code === "GOAL_INVALID_INPUT")
|
|
1164
|
+
return 400;
|
|
1165
|
+
return 409;
|
|
1166
|
+
}
|
|
1167
|
+
export async function handleChatGoal(req, res, deps, sessionId) {
|
|
1168
|
+
const registry = deps.goalRegistry;
|
|
1169
|
+
if (!registry) {
|
|
1170
|
+
sendJson(res, 503, {
|
|
1171
|
+
ok: false,
|
|
1172
|
+
error: { code: "GOAL_UNAVAILABLE", message: "Goal runtime is unavailable" },
|
|
1173
|
+
});
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (!(await deps.store.get(sessionId))) {
|
|
1177
|
+
sendJson(res, 404, {
|
|
1178
|
+
ok: false,
|
|
1179
|
+
error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
|
|
1180
|
+
});
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (req.method === "GET") {
|
|
1184
|
+
sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
const gate = gateMutation(req, deps);
|
|
1188
|
+
if (!gate.ok) {
|
|
1189
|
+
sendJson(res, gate.status, {
|
|
1190
|
+
ok: false,
|
|
1191
|
+
error: { code: gate.code, message: gate.message },
|
|
1192
|
+
});
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
let body = {};
|
|
1196
|
+
try {
|
|
1197
|
+
body = await readJsonBody(req);
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
sendJson(res, 400, {
|
|
1201
|
+
ok: false,
|
|
1202
|
+
error: {
|
|
1203
|
+
code: "GOAL_INVALID_INPUT",
|
|
1204
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (req.method === "POST") {
|
|
1210
|
+
const result = registry.create({
|
|
1211
|
+
sessionId,
|
|
1212
|
+
objective: body.objective,
|
|
1213
|
+
maxRounds: typeof body.maxRounds === "number" ? body.maxRounds : undefined,
|
|
1214
|
+
arm: true,
|
|
1215
|
+
});
|
|
1216
|
+
if (!result.ok) {
|
|
1217
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1218
|
+
ok: false,
|
|
1219
|
+
error: { code: result.code, message: result.message },
|
|
1220
|
+
goal: result.goal,
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1225
|
+
sendJson(res, 201, { ok: true, goal: result.goal });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const current = registry.get(sessionId);
|
|
1229
|
+
const goalId = typeof body.goalId === "string" ? body.goalId : "";
|
|
1230
|
+
const revision = typeof body.revision === "number" ? body.revision : Number.NaN;
|
|
1231
|
+
if (!current || !goalId || !Number.isSafeInteger(revision)) {
|
|
1232
|
+
sendJson(res, current ? 400 : 404, {
|
|
1233
|
+
ok: false,
|
|
1234
|
+
error: {
|
|
1235
|
+
code: current ? "GOAL_INVALID_INPUT" : "GOAL_NOT_FOUND",
|
|
1236
|
+
message: current
|
|
1237
|
+
? "goalId and integer revision are required"
|
|
1238
|
+
: "Goal not found",
|
|
1239
|
+
},
|
|
1240
|
+
});
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (req.method === "DELETE") {
|
|
1244
|
+
const result = registry.clear({
|
|
1245
|
+
sessionId,
|
|
1246
|
+
goalId,
|
|
1247
|
+
expectedRevision: revision,
|
|
1248
|
+
});
|
|
1249
|
+
if (!result.ok) {
|
|
1250
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1251
|
+
ok: false,
|
|
1252
|
+
error: { code: result.code, message: result.message },
|
|
1253
|
+
goal: result.goal,
|
|
1254
|
+
});
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1258
|
+
sendJson(res, 200, { ok: true, goal: null });
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (req.method === "PATCH") {
|
|
1262
|
+
const action = typeof body.action === "string" ? body.action : "edit";
|
|
1263
|
+
let phase;
|
|
1264
|
+
let arm;
|
|
1265
|
+
if (action === "pause") {
|
|
1266
|
+
phase = "paused";
|
|
1267
|
+
arm = false;
|
|
1268
|
+
}
|
|
1269
|
+
else if (action === "resume") {
|
|
1270
|
+
phase = "active";
|
|
1271
|
+
arm = true;
|
|
1272
|
+
}
|
|
1273
|
+
else if (action === "edit") {
|
|
1274
|
+
phase = current.phase;
|
|
1275
|
+
arm = current.armed;
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
sendJson(res, 400, {
|
|
1279
|
+
ok: false,
|
|
1280
|
+
error: { code: "GOAL_INVALID_INPUT", message: "unsupported Goal action" },
|
|
1281
|
+
});
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const result = registry.update({
|
|
1285
|
+
sessionId,
|
|
1286
|
+
goalId,
|
|
1287
|
+
expectedRevision: revision,
|
|
1288
|
+
phase,
|
|
1289
|
+
arm,
|
|
1290
|
+
...(body.objective !== undefined ? { objective: body.objective } : {}),
|
|
1291
|
+
});
|
|
1292
|
+
if (!result.ok) {
|
|
1293
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1294
|
+
ok: false,
|
|
1295
|
+
error: { code: result.code, message: result.message },
|
|
1296
|
+
goal: result.goal,
|
|
1297
|
+
});
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (action === "resume")
|
|
1301
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1302
|
+
else if (action === "pause")
|
|
1303
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1304
|
+
sendJson(res, 200, { ok: true, goal: result.goal });
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
sendJson(res, 405, {
|
|
1308
|
+
ok: false,
|
|
1309
|
+
error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" },
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1076
1312
|
export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1077
1313
|
const gate = gateMutation(req, deps);
|
|
1078
1314
|
if (!gate.ok) {
|
|
@@ -1121,27 +1357,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
|
|
|
1121
1357
|
}
|
|
1122
1358
|
}
|
|
1123
1359
|
export async function handleListChatSessions(_req, res, deps) {
|
|
1124
|
-
const sessions = await deps
|
|
1360
|
+
const sessions = await listChatSessionSummaries(deps);
|
|
1125
1361
|
sendJson(res, 200, {
|
|
1126
1362
|
ok: true,
|
|
1127
1363
|
schemaVersion: 2,
|
|
1128
|
-
sessions
|
|
1129
|
-
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1130
|
-
return {
|
|
1131
|
-
...session,
|
|
1132
|
-
activeTurnId: active?.turnId,
|
|
1133
|
-
turnPhase: active?.abortRequestedAt
|
|
1134
|
-
? "stopping"
|
|
1135
|
-
: active
|
|
1136
|
-
? "running"
|
|
1137
|
-
: "idle",
|
|
1138
|
-
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1139
|
-
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1140
|
-
};
|
|
1141
|
-
}),
|
|
1364
|
+
sessions,
|
|
1142
1365
|
hasBash: true,
|
|
1143
1366
|
});
|
|
1144
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Build sidebar-only summaries from one Workspace store without opening a
|
|
1370
|
+
* runtime. Message bodies, Pi session paths and tool audit rows belong to the
|
|
1371
|
+
* detail endpoint; returning them here makes the global multi-Workspace index
|
|
1372
|
+
* grow with the entire chat history.
|
|
1373
|
+
*/
|
|
1374
|
+
export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
1375
|
+
const sessions = await deps.store.list({ includeArchived: true });
|
|
1376
|
+
return sessions.map((session) => {
|
|
1377
|
+
const active = deps.events?.getActiveTurn(session.sessionId);
|
|
1378
|
+
const workspaceId = ownerWorkspaceId ?? session.workspaceId;
|
|
1379
|
+
return {
|
|
1380
|
+
sessionId: session.sessionId,
|
|
1381
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1382
|
+
state: session.state,
|
|
1383
|
+
createdAt: session.createdAt,
|
|
1384
|
+
updatedAt: session.updatedAt,
|
|
1385
|
+
...(session.title !== undefined ? { title: session.title } : {}),
|
|
1386
|
+
...(session.displayTitle !== undefined
|
|
1387
|
+
? { displayTitle: session.displayTitle }
|
|
1388
|
+
: {}),
|
|
1389
|
+
...(session.modelProvider !== undefined
|
|
1390
|
+
? { modelProvider: session.modelProvider }
|
|
1391
|
+
: {}),
|
|
1392
|
+
...(session.modelId !== undefined ? { modelId: session.modelId } : {}),
|
|
1393
|
+
...(session.thinkingLevel !== undefined
|
|
1394
|
+
? { thinkingLevel: session.thinkingLevel }
|
|
1395
|
+
: {}),
|
|
1396
|
+
activeTurnId: active?.turnId,
|
|
1397
|
+
turnPhase: active?.abortRequestedAt
|
|
1398
|
+
? "stopping"
|
|
1399
|
+
: active
|
|
1400
|
+
? "running"
|
|
1401
|
+
: "idle",
|
|
1402
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1403
|
+
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1404
|
+
};
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1145
1407
|
export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
1146
1408
|
const gate = gateMutation(req, deps);
|
|
1147
1409
|
if (!gate.ok) {
|
|
@@ -1644,6 +1906,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1644
1906
|
});
|
|
1645
1907
|
return;
|
|
1646
1908
|
}
|
|
1909
|
+
try {
|
|
1910
|
+
trustedPayload?.onTurnCreated?.(turn.turnId);
|
|
1911
|
+
}
|
|
1912
|
+
catch (error) {
|
|
1913
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1914
|
+
deps.events.setTurnState(sessionId, turn.turnId, "failed", {
|
|
1915
|
+
code: "GOAL_ADMISSION_STALE",
|
|
1916
|
+
message,
|
|
1917
|
+
});
|
|
1918
|
+
sendJson(res, 409, {
|
|
1919
|
+
ok: false,
|
|
1920
|
+
accepted: false,
|
|
1921
|
+
error: { code: "GOAL_ADMISSION_STALE", message },
|
|
1922
|
+
});
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1647
1925
|
const controller = new AbortController();
|
|
1648
1926
|
const execution = deps.turnExecutions?.register({
|
|
1649
1927
|
sessionId,
|
|
@@ -1693,6 +1971,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1693
1971
|
if (placeholder)
|
|
1694
1972
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1695
1973
|
},
|
|
1974
|
+
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1696
1975
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1697
1976
|
sessionId,
|
|
1698
1977
|
turnId: turn.turnId,
|
|
@@ -1755,16 +2034,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1755
2034
|
}
|
|
1756
2035
|
}
|
|
1757
2036
|
finally {
|
|
2037
|
+
if (terminalState !== "settled") {
|
|
2038
|
+
pauseGoalForSafety(deps, sessionId, turn.turnId);
|
|
2039
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2040
|
+
}
|
|
1758
2041
|
execution?.resolveCompletion({
|
|
1759
2042
|
ok: terminalState === "settled",
|
|
1760
2043
|
state: terminalState,
|
|
1761
2044
|
error: terminalError,
|
|
1762
2045
|
});
|
|
1763
2046
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1764
|
-
void dispatchDeferredTurn(deps, sessionId)
|
|
2047
|
+
void dispatchDeferredTurn(deps, sessionId).finally(() => {
|
|
2048
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
2049
|
+
});
|
|
1765
2050
|
}
|
|
1766
2051
|
})();
|
|
1767
2052
|
}
|
|
2053
|
+
function buildGoalRoundPrompt(goal) {
|
|
2054
|
+
const remaining = Math.max(0, goal.maxRounds - goal.round);
|
|
2055
|
+
return [
|
|
2056
|
+
buildGoalContinuationContext(goal),
|
|
2057
|
+
`This is autonomous Goal round ${goal.round + 1} of at most ${goal.maxRounds} (${remaining} rounds remain before this one).`,
|
|
2058
|
+
"Continue from the current session state. Prefer the user's queued or newly supplied instructions when present. Use todo_write for multi-step work.",
|
|
2059
|
+
"Before ending this round, call get_goal. If the objective is fully achieved, call update_goal(status=complete). If no safe progress remains, call update_goal(status=blocked) with the concrete blocker. Otherwise leave it active for the next bounded round.",
|
|
2060
|
+
].filter(Boolean).join("\n\n");
|
|
2061
|
+
}
|
|
2062
|
+
function pauseGoalForSafety(deps, sessionId, turnId) {
|
|
2063
|
+
const current = deps.goalRegistry?.get(sessionId);
|
|
2064
|
+
if (!current || current.phase !== "active") {
|
|
2065
|
+
deps.goalRegistry?.disarm(sessionId);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
deps.goalRegistry?.update({
|
|
2069
|
+
sessionId,
|
|
2070
|
+
goalId: current.goalId,
|
|
2071
|
+
expectedRevision: current.revision,
|
|
2072
|
+
phase: "paused",
|
|
2073
|
+
arm: false,
|
|
2074
|
+
turnId,
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
export function linkedOperationIdsForGoal(events) {
|
|
2078
|
+
const linked = new Set();
|
|
2079
|
+
for (const event of events) {
|
|
2080
|
+
const operationId = event.data && typeof event.data === "object"
|
|
2081
|
+
? event.data.operationId
|
|
2082
|
+
: undefined;
|
|
2083
|
+
if (typeof operationId !== "string" || !operationId)
|
|
2084
|
+
continue;
|
|
2085
|
+
if (event.kind === "operation-ref" || event.kind === "operation-status")
|
|
2086
|
+
linked.add(operationId);
|
|
2087
|
+
if (event.kind === "operation-unlinked")
|
|
2088
|
+
linked.delete(operationId);
|
|
2089
|
+
}
|
|
2090
|
+
return [...linked];
|
|
2091
|
+
}
|
|
2092
|
+
/** One admission attempt used by the process-local GoalRoundDriver. */
|
|
2093
|
+
export async function driveGoalRound(deps, sessionId) {
|
|
2094
|
+
const registry = deps.goalRegistry;
|
|
2095
|
+
if (!registry || !registry.isArmed(sessionId))
|
|
2096
|
+
return "idle";
|
|
2097
|
+
const goal = registry.get(sessionId);
|
|
2098
|
+
if (!goal || goal.phase !== "active" || !goal.armed)
|
|
2099
|
+
return "idle";
|
|
2100
|
+
if (goal.round >= goal.maxRounds) {
|
|
2101
|
+
registry.update({
|
|
2102
|
+
sessionId,
|
|
2103
|
+
goalId: goal.goalId,
|
|
2104
|
+
expectedRevision: goal.revision,
|
|
2105
|
+
phase: "paused",
|
|
2106
|
+
arm: false,
|
|
2107
|
+
});
|
|
2108
|
+
return "idle";
|
|
2109
|
+
}
|
|
2110
|
+
const record = await deps.store.get(sessionId);
|
|
2111
|
+
if (!record) {
|
|
2112
|
+
registry.disarm(sessionId);
|
|
2113
|
+
return "idle";
|
|
2114
|
+
}
|
|
2115
|
+
const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
|
|
2116
|
+
const operationStore = deps.runtime.actionContext?.operations;
|
|
2117
|
+
const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
|
|
2118
|
+
const operations = operationStore
|
|
2119
|
+
? await Promise.all(linkedOperationIds.map((operationId) => operationStore.get(operationId)))
|
|
2120
|
+
: [];
|
|
2121
|
+
const admission = classifyGoalRoundAdmission({
|
|
2122
|
+
phase: goal.phase,
|
|
2123
|
+
armed: goal.armed,
|
|
2124
|
+
round: goal.round,
|
|
2125
|
+
maxRounds: goal.maxRounds,
|
|
2126
|
+
hasQueuedUserTurn: (record.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"),
|
|
2127
|
+
hasActiveTurn: Boolean(deps.events.getActiveTurn(sessionId)),
|
|
2128
|
+
runtimeBusy: Object.values(runtimeFacts).some((value) => value === true),
|
|
2129
|
+
operationBusy: operations.some((operation) => Boolean(operation) && !isTerminalOperationState(operation.state)),
|
|
2130
|
+
});
|
|
2131
|
+
if (admission.action === "wait")
|
|
2132
|
+
return "wait";
|
|
2133
|
+
if (admission.action === "idle")
|
|
2134
|
+
return "idle";
|
|
2135
|
+
const expectedRound = goal.round + 1;
|
|
2136
|
+
const clientRequestId = `goal:${goal.goalId}:${expectedRound}`;
|
|
2137
|
+
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
2138
|
+
clientRequestId,
|
|
2139
|
+
text: buildGoalRoundPrompt(goal),
|
|
2140
|
+
images: [],
|
|
2141
|
+
persistUserMessage: false,
|
|
2142
|
+
onTurnCreated: (turnId) => {
|
|
2143
|
+
const advanced = registry.beginRound({
|
|
2144
|
+
sessionId,
|
|
2145
|
+
goalId: goal.goalId,
|
|
2146
|
+
expectedRevision: goal.revision,
|
|
2147
|
+
turnId,
|
|
2148
|
+
});
|
|
2149
|
+
if (!advanced.ok) {
|
|
2150
|
+
registry.disarm(sessionId);
|
|
2151
|
+
throw new Error(advanced.message);
|
|
2152
|
+
}
|
|
2153
|
+
},
|
|
2154
|
+
});
|
|
2155
|
+
const accepted = started.body.accepted === true;
|
|
2156
|
+
const turnId = started.body.turn && typeof started.body.turn === "object"
|
|
2157
|
+
? started.body.turn.turnId
|
|
2158
|
+
: undefined;
|
|
2159
|
+
if (!accepted || !turnId) {
|
|
2160
|
+
const code = started.body.error && typeof started.body.error === "object"
|
|
2161
|
+
? String(started.body.error.code ?? "")
|
|
2162
|
+
: "";
|
|
2163
|
+
if (code === "TURN_ACTIVE")
|
|
2164
|
+
return "wait";
|
|
2165
|
+
registry.disarm(sessionId);
|
|
2166
|
+
return "idle";
|
|
2167
|
+
}
|
|
2168
|
+
return "started";
|
|
2169
|
+
}
|
|
1768
2170
|
async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
|
|
1769
2171
|
const record = await deps.store.get(sessionId);
|
|
1770
2172
|
if (!record)
|
|
@@ -1985,6 +2387,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
|
1985
2387
|
});
|
|
1986
2388
|
return;
|
|
1987
2389
|
}
|
|
2390
|
+
pauseGoalForSafety(deps, sessionId, turnId);
|
|
2391
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1988
2392
|
if (isTerminalTurnState(turn.state)) {
|
|
1989
2393
|
sendJson(res, 200, {
|
|
1990
2394
|
ok: true,
|
|
@@ -2155,6 +2559,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2155
2559
|
}
|
|
2156
2560
|
await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
|
|
2157
2561
|
deps.todoRegistry?.clear(sessionId);
|
|
2562
|
+
deps.goalRegistry?.clearRuntime(sessionId);
|
|
2563
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2158
2564
|
deps.runtime.dispose(sessionId);
|
|
2159
2565
|
deps.operationLinker.clearSession(sessionId);
|
|
2160
2566
|
deps.events.clear(sessionId);
|
|
@@ -2170,6 +2576,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2170
2576
|
});
|
|
2171
2577
|
return;
|
|
2172
2578
|
}
|
|
2579
|
+
await deps.detachSession?.(sessionId).catch(() => undefined);
|
|
2173
2580
|
sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
|
|
2174
2581
|
}
|
|
2175
2582
|
export async function handleGetTaskContext(_res, res, deps, sessionId) {
|
|
@@ -2229,6 +2636,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
|
|
|
2229
2636
|
});
|
|
2230
2637
|
}
|
|
2231
2638
|
}
|
|
2639
|
+
async function handleRepoHtmlPreview(res, deps, rawPath) {
|
|
2640
|
+
try {
|
|
2641
|
+
const asset = await readRepoHtmlAsset(deps.appData.repoRoot, rawPath);
|
|
2642
|
+
const origin = deps.getConsoleOrigin();
|
|
2643
|
+
res.writeHead(200, {
|
|
2644
|
+
"Content-Type": asset.contentType,
|
|
2645
|
+
"Content-Length": asset.body.byteLength,
|
|
2646
|
+
"Cache-Control": "no-store",
|
|
2647
|
+
"X-Content-Type-Options": "nosniff",
|
|
2648
|
+
"Referrer-Policy": "no-referrer",
|
|
2649
|
+
// The iframe also has an empty sandbox attribute. This response-level
|
|
2650
|
+
// sandbox keeps direct/popup loads isolated, while explicit local-origin
|
|
2651
|
+
// style/image/font/media sources let relative project assets render.
|
|
2652
|
+
"Content-Security-Policy": [
|
|
2653
|
+
"sandbox",
|
|
2654
|
+
"default-src 'none'",
|
|
2655
|
+
"script-src 'none'",
|
|
2656
|
+
"object-src 'none'",
|
|
2657
|
+
"frame-src 'none'",
|
|
2658
|
+
"form-action 'none'",
|
|
2659
|
+
"base-uri 'none'",
|
|
2660
|
+
"connect-src 'none'",
|
|
2661
|
+
`style-src 'unsafe-inline' ${origin}`,
|
|
2662
|
+
`img-src data: blob: ${origin}`,
|
|
2663
|
+
`font-src data: ${origin}`,
|
|
2664
|
+
`media-src blob: ${origin}`,
|
|
2665
|
+
].join("; "),
|
|
2666
|
+
});
|
|
2667
|
+
res.end(asset.body);
|
|
2668
|
+
}
|
|
2669
|
+
catch (error) {
|
|
2670
|
+
const failure = error instanceof RepoBrowserError
|
|
2671
|
+
? error
|
|
2672
|
+
: new RepoBrowserError(500, "READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
2673
|
+
sendJson(res, failure.status, {
|
|
2674
|
+
ok: false,
|
|
2675
|
+
error: { code: failure.code, message: failure.message },
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2232
2679
|
async function handlePiConfig(req, res, deps, resource) {
|
|
2233
2680
|
try {
|
|
2234
2681
|
if (resource === "models") {
|
|
@@ -2974,7 +3421,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
|
|
|
2974
3421
|
taskId,
|
|
2975
3422
|
confirmationId,
|
|
2976
3423
|
expiresAt: confirmation.expiresAt,
|
|
2977
|
-
inspectHref:
|
|
3424
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
2978
3425
|
humanGateToken: confirmation.humanGateToken,
|
|
2979
3426
|
displayOnlyChallenges: [
|
|
2980
3427
|
"source",
|
|
@@ -3000,7 +3447,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
|
|
|
3000
3447
|
receiptId: receipt.receiptId,
|
|
3001
3448
|
expiresAt: receipt.expiresAt,
|
|
3002
3449
|
humanGateToken: receipt.humanGateToken,
|
|
3003
|
-
inspectHref:
|
|
3450
|
+
inspectHref: `/#/inspect`,
|
|
3004
3451
|
}));
|
|
3005
3452
|
}
|
|
3006
3453
|
function actionResultBody(result) {
|
|
@@ -3089,7 +3536,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3089
3536
|
humanGateToken,
|
|
3090
3537
|
contractRevision: binding.revision,
|
|
3091
3538
|
contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
|
|
3092
|
-
inspectHref:
|
|
3539
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3093
3540
|
});
|
|
3094
3541
|
appendHumanGateCard(deps, sessionId, card);
|
|
3095
3542
|
sendJson(res, 201, { ok: true, card });
|
|
@@ -3143,8 +3590,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3143
3590
|
contractRevision: receipt.revision,
|
|
3144
3591
|
operationId,
|
|
3145
3592
|
inspectHref: operationId
|
|
3146
|
-
?
|
|
3147
|
-
:
|
|
3593
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3594
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3148
3595
|
});
|
|
3149
3596
|
appendHumanGateCard(deps, sessionId, card);
|
|
3150
3597
|
sendJson(res, 202, {
|
|
@@ -3190,8 +3637,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3190
3637
|
contractRevision: receipt.revision,
|
|
3191
3638
|
operationId,
|
|
3192
3639
|
inspectHref: operationId
|
|
3193
|
-
?
|
|
3194
|
-
:
|
|
3640
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3641
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3195
3642
|
});
|
|
3196
3643
|
appendHumanGateCard(deps, sessionId, card);
|
|
3197
3644
|
if (operationId)
|
|
@@ -3292,8 +3739,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
|
|
|
3292
3739
|
confirmationId,
|
|
3293
3740
|
expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
|
|
3294
3741
|
inspectHref: operationId
|
|
3295
|
-
?
|
|
3296
|
-
:
|
|
3742
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3743
|
+
: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3297
3744
|
...(confirmation?.humanGateToken
|
|
3298
3745
|
? { humanGateToken: confirmation.humanGateToken }
|
|
3299
3746
|
: {}),
|
|
@@ -3387,8 +3834,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
|
|
|
3387
3834
|
expiresAt: receipt.expiresAt,
|
|
3388
3835
|
operationId,
|
|
3389
3836
|
inspectHref: operationId
|
|
3390
|
-
?
|
|
3391
|
-
:
|
|
3837
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3838
|
+
: `/#/inspect`,
|
|
3392
3839
|
});
|
|
3393
3840
|
appendHumanGateCard(deps, sessionId, card);
|
|
3394
3841
|
if (operationId) {
|
|
@@ -3512,7 +3959,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3512
3959
|
}
|
|
3513
3960
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3514
3961
|
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
3515
|
-
const
|
|
3962
|
+
const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
3963
|
+
const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
|
|
3516
3964
|
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
3517
3965
|
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
3518
3966
|
deps.events.append(sessionId, turnId, {
|
|
@@ -3649,6 +4097,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3649
4097
|
await handleChatCapabilities(req, res, deps);
|
|
3650
4098
|
return true;
|
|
3651
4099
|
}
|
|
4100
|
+
const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
|
|
4101
|
+
if (method === "GET" && htmlPreviewRoute) {
|
|
4102
|
+
if (!htmlPreviewRoute.rawPath)
|
|
4103
|
+
sendJson(res, 400, {
|
|
4104
|
+
ok: false,
|
|
4105
|
+
error: { code: "INVALID_PATH", message: "path is required" },
|
|
4106
|
+
});
|
|
4107
|
+
else
|
|
4108
|
+
await handleRepoHtmlPreview(res, deps, htmlPreviewRoute.rawPath);
|
|
4109
|
+
return true;
|
|
4110
|
+
}
|
|
3652
4111
|
if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
|
|
3653
4112
|
await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
|
|
3654
4113
|
return true;
|
|
@@ -3780,6 +4239,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3780
4239
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3781
4240
|
return true;
|
|
3782
4241
|
}
|
|
4242
|
+
const sessionGoal = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/goal$/);
|
|
4243
|
+
if (sessionGoal &&
|
|
4244
|
+
(method === "GET" || method === "POST" || method === "PATCH" || method === "DELETE")) {
|
|
4245
|
+
await handleChatGoal(req, res, deps, decodeURIComponent(sessionGoal[1]));
|
|
4246
|
+
return true;
|
|
4247
|
+
}
|
|
3783
4248
|
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3784
4249
|
if (method === "PUT" && deferredTurn) {
|
|
3785
4250
|
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|