@tea-agent/loop-agent 0.40.0-next.11 → 0.40.0-next.13
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 +45 -0
- package/README.md +1 -1
- 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/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/pi-sdk-executor.js +33 -5
- 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 +485 -29
- package/dist/worker/console/chat/session-catalog.js +48 -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 +32 -3
- package/dist/worker/console/operator-actions.js +16 -2
- 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 +100 -0
- package/dist/worker/console/routes.js +221 -6
- package/dist/worker/console/server.js +172 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-Wj-fsQlk.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-CkXT-vzQ.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-xlfnojWU.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BilahvuB.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-DqtAVhjz.js} +1 -1
- package/dist/worker/console/static/assets/channel-SnQ_u-Td.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-pAqQXZ5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BEWxeq2r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-CvlbGDTp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-D0uGd9-f.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-DSdWIteg.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-Dweg7f0v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-BpZKI_k7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-IPLknC4m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-E93k9lVI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-BrESwkPq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-SGM51NVY.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-SGM51NVY.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-DmFHvJkB.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CDigXiSL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-B0zhrbaA.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DrKybVxm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-CeUrnbFk.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-BJyunUMi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-a5TBP73u.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-ChRgBURJ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-CqCnPRSk.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BI-UD9_S.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-CDLvvkYZ.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-Heoo4afj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-kFpOitHd.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-FvyZ9WAY.js} +1 -1
- package/dist/worker/console/static/assets/index-C80SO8y0.js +437 -0
- package/dist/worker/console/static/assets/index-CmJCHmJP.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-HBzlwtgI.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C9Iim5sm.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-CJwJTg0S.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-CZNJ7S4q.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-RZ0AnGi8.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-EYSdm-nd.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-D0OLECFN.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CPTvGbh7.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-Ddc1aDea.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CWCTCdAt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-CfsaBL8E.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-BH2rqGf6.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-BZgJ1Til.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-DmPr-o-y.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-CF6HzDh_.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-DhS8N_B9.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-N56zxwsk.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-CYNzoiaD.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-o47kGgVt.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-QBICF5qH.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-fquBvWcf.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-BYaJLlSd.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M--ePe_JCJ.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/useChatSessions.js +24 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +8 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- 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/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 +6 -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/init-hybrid.js +3 -3
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/backend-test-dag.json +3 -3
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +14 -0
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
|
@@ -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";
|
|
@@ -39,6 +39,71 @@ import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, }
|
|
|
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 ? { eventSeq: start.seq, turnId: start.turnId } : {}),
|
|
103
|
+
...(finish ? { finishedSeq: finish.seq } : {}),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
42
107
|
function turnSummary(turn) {
|
|
43
108
|
return {
|
|
44
109
|
turnId: turn.turnId,
|
|
@@ -514,6 +579,7 @@ workspaceId) {
|
|
|
514
579
|
// a fallback for direct (non-dispatcher) handler tests.
|
|
515
580
|
workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
|
|
516
581
|
});
|
|
582
|
+
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
517
583
|
if (snapshot)
|
|
518
584
|
deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
|
|
519
585
|
kind: "compact",
|
|
@@ -595,6 +661,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
|
|
|
595
661
|
targetSessionId: handle.sessionId,
|
|
596
662
|
targetSessionFile: handle.sessionFile,
|
|
597
663
|
});
|
|
664
|
+
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
598
665
|
const copied = deps.events.forkContextRefs({
|
|
599
666
|
sourceSessionId,
|
|
600
667
|
targetSessionId: handle.sessionId,
|
|
@@ -746,6 +813,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
746
813
|
eventEpoch: deps.events.epoch(sessionId),
|
|
747
814
|
session: {
|
|
748
815
|
...record,
|
|
816
|
+
messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
|
|
749
817
|
composerDraft,
|
|
750
818
|
activeTurnId: activeTurn?.turnId,
|
|
751
819
|
turnPhase: activeTurn
|
|
@@ -755,10 +823,14 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
755
823
|
: "idle",
|
|
756
824
|
pendingQuestion: pendingQuestion ?? null,
|
|
757
825
|
todos: projectCurrentTodos(snapshot) ?? [],
|
|
826
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
758
827
|
},
|
|
759
828
|
hasBash: true,
|
|
760
829
|
sessionStats: telemetry.stats,
|
|
761
830
|
usage: telemetry.usage,
|
|
831
|
+
contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
|
|
832
|
+
? deps.runtime.getContextPressureTelemetry(sessionId)
|
|
833
|
+
: undefined,
|
|
762
834
|
compactions: snapshot
|
|
763
835
|
.filter((event) => event.kind === "compact" ||
|
|
764
836
|
event.kind === "compaction_start" ||
|
|
@@ -767,6 +839,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
767
839
|
eventId: event.eventId,
|
|
768
840
|
kind: event.kind,
|
|
769
841
|
at: event.at,
|
|
842
|
+
seq: event.seq,
|
|
843
|
+
turnId: event.turnId,
|
|
770
844
|
data: event.data,
|
|
771
845
|
})),
|
|
772
846
|
lastEventId: lastEvent?.eventId ?? null,
|
|
@@ -1070,9 +1144,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
1070
1144
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1071
1145
|
pendingQuestion: pendingQuestion ?? null,
|
|
1072
1146
|
todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
|
|
1147
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
1073
1148
|
},
|
|
1074
1149
|
});
|
|
1075
1150
|
}
|
|
1151
|
+
function goalHttpStatus(code) {
|
|
1152
|
+
if (code === "GOAL_NOT_FOUND")
|
|
1153
|
+
return 404;
|
|
1154
|
+
if (code === "GOAL_INVALID_INPUT")
|
|
1155
|
+
return 400;
|
|
1156
|
+
return 409;
|
|
1157
|
+
}
|
|
1158
|
+
export async function handleChatGoal(req, res, deps, sessionId) {
|
|
1159
|
+
const registry = deps.goalRegistry;
|
|
1160
|
+
if (!registry) {
|
|
1161
|
+
sendJson(res, 503, {
|
|
1162
|
+
ok: false,
|
|
1163
|
+
error: { code: "GOAL_UNAVAILABLE", message: "Goal runtime is unavailable" },
|
|
1164
|
+
});
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (!(await deps.store.get(sessionId))) {
|
|
1168
|
+
sendJson(res, 404, {
|
|
1169
|
+
ok: false,
|
|
1170
|
+
error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
|
|
1171
|
+
});
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (req.method === "GET") {
|
|
1175
|
+
sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const gate = gateMutation(req, deps);
|
|
1179
|
+
if (!gate.ok) {
|
|
1180
|
+
sendJson(res, gate.status, {
|
|
1181
|
+
ok: false,
|
|
1182
|
+
error: { code: gate.code, message: gate.message },
|
|
1183
|
+
});
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
let body = {};
|
|
1187
|
+
try {
|
|
1188
|
+
body = await readJsonBody(req);
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
sendJson(res, 400, {
|
|
1192
|
+
ok: false,
|
|
1193
|
+
error: {
|
|
1194
|
+
code: "GOAL_INVALID_INPUT",
|
|
1195
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (req.method === "POST") {
|
|
1201
|
+
const result = registry.create({
|
|
1202
|
+
sessionId,
|
|
1203
|
+
objective: body.objective,
|
|
1204
|
+
maxRounds: typeof body.maxRounds === "number" ? body.maxRounds : undefined,
|
|
1205
|
+
arm: true,
|
|
1206
|
+
});
|
|
1207
|
+
if (!result.ok) {
|
|
1208
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1209
|
+
ok: false,
|
|
1210
|
+
error: { code: result.code, message: result.message },
|
|
1211
|
+
goal: result.goal,
|
|
1212
|
+
});
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1216
|
+
sendJson(res, 201, { ok: true, goal: result.goal });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
const current = registry.get(sessionId);
|
|
1220
|
+
const goalId = typeof body.goalId === "string" ? body.goalId : "";
|
|
1221
|
+
const revision = typeof body.revision === "number" ? body.revision : Number.NaN;
|
|
1222
|
+
if (!current || !goalId || !Number.isSafeInteger(revision)) {
|
|
1223
|
+
sendJson(res, current ? 400 : 404, {
|
|
1224
|
+
ok: false,
|
|
1225
|
+
error: {
|
|
1226
|
+
code: current ? "GOAL_INVALID_INPUT" : "GOAL_NOT_FOUND",
|
|
1227
|
+
message: current
|
|
1228
|
+
? "goalId and integer revision are required"
|
|
1229
|
+
: "Goal not found",
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (req.method === "DELETE") {
|
|
1235
|
+
const result = registry.clear({
|
|
1236
|
+
sessionId,
|
|
1237
|
+
goalId,
|
|
1238
|
+
expectedRevision: revision,
|
|
1239
|
+
});
|
|
1240
|
+
if (!result.ok) {
|
|
1241
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
error: { code: result.code, message: result.message },
|
|
1244
|
+
goal: result.goal,
|
|
1245
|
+
});
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1249
|
+
sendJson(res, 200, { ok: true, goal: null });
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (req.method === "PATCH") {
|
|
1253
|
+
const action = typeof body.action === "string" ? body.action : "edit";
|
|
1254
|
+
let phase;
|
|
1255
|
+
let arm;
|
|
1256
|
+
if (action === "pause") {
|
|
1257
|
+
phase = "paused";
|
|
1258
|
+
arm = false;
|
|
1259
|
+
}
|
|
1260
|
+
else if (action === "resume") {
|
|
1261
|
+
phase = "active";
|
|
1262
|
+
arm = true;
|
|
1263
|
+
}
|
|
1264
|
+
else if (action === "edit") {
|
|
1265
|
+
phase = current.phase;
|
|
1266
|
+
arm = current.armed;
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
sendJson(res, 400, {
|
|
1270
|
+
ok: false,
|
|
1271
|
+
error: { code: "GOAL_INVALID_INPUT", message: "unsupported Goal action" },
|
|
1272
|
+
});
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const result = registry.update({
|
|
1276
|
+
sessionId,
|
|
1277
|
+
goalId,
|
|
1278
|
+
expectedRevision: revision,
|
|
1279
|
+
phase,
|
|
1280
|
+
arm,
|
|
1281
|
+
...(body.objective !== undefined ? { objective: body.objective } : {}),
|
|
1282
|
+
});
|
|
1283
|
+
if (!result.ok) {
|
|
1284
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1285
|
+
ok: false,
|
|
1286
|
+
error: { code: result.code, message: result.message },
|
|
1287
|
+
goal: result.goal,
|
|
1288
|
+
});
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (action === "resume")
|
|
1292
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1293
|
+
else if (action === "pause")
|
|
1294
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1295
|
+
sendJson(res, 200, { ok: true, goal: result.goal });
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
sendJson(res, 405, {
|
|
1299
|
+
ok: false,
|
|
1300
|
+
error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" },
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1076
1303
|
export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1077
1304
|
const gate = gateMutation(req, deps);
|
|
1078
1305
|
if (!gate.ok) {
|
|
@@ -1121,27 +1348,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
|
|
|
1121
1348
|
}
|
|
1122
1349
|
}
|
|
1123
1350
|
export async function handleListChatSessions(_req, res, deps) {
|
|
1124
|
-
const sessions = await deps
|
|
1351
|
+
const sessions = await listChatSessionSummaries(deps);
|
|
1125
1352
|
sendJson(res, 200, {
|
|
1126
1353
|
ok: true,
|
|
1127
1354
|
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
|
-
}),
|
|
1355
|
+
sessions,
|
|
1142
1356
|
hasBash: true,
|
|
1143
1357
|
});
|
|
1144
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Build sidebar-only summaries from one Workspace store without opening a
|
|
1361
|
+
* runtime. Message bodies, Pi session paths and tool audit rows belong to the
|
|
1362
|
+
* detail endpoint; returning them here makes the global multi-Workspace index
|
|
1363
|
+
* grow with the entire chat history.
|
|
1364
|
+
*/
|
|
1365
|
+
export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
1366
|
+
const sessions = await deps.store.list({ includeArchived: true });
|
|
1367
|
+
return sessions.map((session) => {
|
|
1368
|
+
const active = deps.events?.getActiveTurn(session.sessionId);
|
|
1369
|
+
const workspaceId = ownerWorkspaceId ?? session.workspaceId;
|
|
1370
|
+
return {
|
|
1371
|
+
sessionId: session.sessionId,
|
|
1372
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1373
|
+
state: session.state,
|
|
1374
|
+
createdAt: session.createdAt,
|
|
1375
|
+
updatedAt: session.updatedAt,
|
|
1376
|
+
...(session.title !== undefined ? { title: session.title } : {}),
|
|
1377
|
+
...(session.displayTitle !== undefined
|
|
1378
|
+
? { displayTitle: session.displayTitle }
|
|
1379
|
+
: {}),
|
|
1380
|
+
...(session.modelProvider !== undefined
|
|
1381
|
+
? { modelProvider: session.modelProvider }
|
|
1382
|
+
: {}),
|
|
1383
|
+
...(session.modelId !== undefined ? { modelId: session.modelId } : {}),
|
|
1384
|
+
...(session.thinkingLevel !== undefined
|
|
1385
|
+
? { thinkingLevel: session.thinkingLevel }
|
|
1386
|
+
: {}),
|
|
1387
|
+
activeTurnId: active?.turnId,
|
|
1388
|
+
turnPhase: active?.abortRequestedAt
|
|
1389
|
+
? "stopping"
|
|
1390
|
+
: active
|
|
1391
|
+
? "running"
|
|
1392
|
+
: "idle",
|
|
1393
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1394
|
+
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1395
|
+
};
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1145
1398
|
export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
1146
1399
|
const gate = gateMutation(req, deps);
|
|
1147
1400
|
if (!gate.ok) {
|
|
@@ -1644,6 +1897,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1644
1897
|
});
|
|
1645
1898
|
return;
|
|
1646
1899
|
}
|
|
1900
|
+
try {
|
|
1901
|
+
trustedPayload?.onTurnCreated?.(turn.turnId);
|
|
1902
|
+
}
|
|
1903
|
+
catch (error) {
|
|
1904
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1905
|
+
deps.events.setTurnState(sessionId, turn.turnId, "failed", {
|
|
1906
|
+
code: "GOAL_ADMISSION_STALE",
|
|
1907
|
+
message,
|
|
1908
|
+
});
|
|
1909
|
+
sendJson(res, 409, {
|
|
1910
|
+
ok: false,
|
|
1911
|
+
accepted: false,
|
|
1912
|
+
error: { code: "GOAL_ADMISSION_STALE", message },
|
|
1913
|
+
});
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1647
1916
|
const controller = new AbortController();
|
|
1648
1917
|
const execution = deps.turnExecutions?.register({
|
|
1649
1918
|
sessionId,
|
|
@@ -1693,6 +1962,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1693
1962
|
if (placeholder)
|
|
1694
1963
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1695
1964
|
},
|
|
1965
|
+
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1696
1966
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1697
1967
|
sessionId,
|
|
1698
1968
|
turnId: turn.turnId,
|
|
@@ -1755,16 +2025,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1755
2025
|
}
|
|
1756
2026
|
}
|
|
1757
2027
|
finally {
|
|
2028
|
+
if (terminalState !== "settled") {
|
|
2029
|
+
pauseGoalForSafety(deps, sessionId, turn.turnId);
|
|
2030
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2031
|
+
}
|
|
1758
2032
|
execution?.resolveCompletion({
|
|
1759
2033
|
ok: terminalState === "settled",
|
|
1760
2034
|
state: terminalState,
|
|
1761
2035
|
error: terminalError,
|
|
1762
2036
|
});
|
|
1763
2037
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1764
|
-
void dispatchDeferredTurn(deps, sessionId)
|
|
2038
|
+
void dispatchDeferredTurn(deps, sessionId).finally(() => {
|
|
2039
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
2040
|
+
});
|
|
1765
2041
|
}
|
|
1766
2042
|
})();
|
|
1767
2043
|
}
|
|
2044
|
+
function buildGoalRoundPrompt(goal) {
|
|
2045
|
+
const remaining = Math.max(0, goal.maxRounds - goal.round);
|
|
2046
|
+
return [
|
|
2047
|
+
buildGoalContinuationContext(goal),
|
|
2048
|
+
`This is autonomous Goal round ${goal.round + 1} of at most ${goal.maxRounds} (${remaining} rounds remain before this one).`,
|
|
2049
|
+
"Continue from the current session state. Prefer the user's queued or newly supplied instructions when present. Use todo_write for multi-step work.",
|
|
2050
|
+
"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.",
|
|
2051
|
+
].filter(Boolean).join("\n\n");
|
|
2052
|
+
}
|
|
2053
|
+
function pauseGoalForSafety(deps, sessionId, turnId) {
|
|
2054
|
+
const current = deps.goalRegistry?.get(sessionId);
|
|
2055
|
+
if (!current || current.phase !== "active") {
|
|
2056
|
+
deps.goalRegistry?.disarm(sessionId);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
deps.goalRegistry?.update({
|
|
2060
|
+
sessionId,
|
|
2061
|
+
goalId: current.goalId,
|
|
2062
|
+
expectedRevision: current.revision,
|
|
2063
|
+
phase: "paused",
|
|
2064
|
+
arm: false,
|
|
2065
|
+
turnId,
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
export function linkedOperationIdsForGoal(events) {
|
|
2069
|
+
const linked = new Set();
|
|
2070
|
+
for (const event of events) {
|
|
2071
|
+
const operationId = event.data && typeof event.data === "object"
|
|
2072
|
+
? event.data.operationId
|
|
2073
|
+
: undefined;
|
|
2074
|
+
if (typeof operationId !== "string" || !operationId)
|
|
2075
|
+
continue;
|
|
2076
|
+
if (event.kind === "operation-ref" || event.kind === "operation-status")
|
|
2077
|
+
linked.add(operationId);
|
|
2078
|
+
if (event.kind === "operation-unlinked")
|
|
2079
|
+
linked.delete(operationId);
|
|
2080
|
+
}
|
|
2081
|
+
return [...linked];
|
|
2082
|
+
}
|
|
2083
|
+
/** One admission attempt used by the process-local GoalRoundDriver. */
|
|
2084
|
+
export async function driveGoalRound(deps, sessionId) {
|
|
2085
|
+
const registry = deps.goalRegistry;
|
|
2086
|
+
if (!registry || !registry.isArmed(sessionId))
|
|
2087
|
+
return "idle";
|
|
2088
|
+
const goal = registry.get(sessionId);
|
|
2089
|
+
if (!goal || goal.phase !== "active" || !goal.armed)
|
|
2090
|
+
return "idle";
|
|
2091
|
+
if (goal.round >= goal.maxRounds) {
|
|
2092
|
+
registry.update({
|
|
2093
|
+
sessionId,
|
|
2094
|
+
goalId: goal.goalId,
|
|
2095
|
+
expectedRevision: goal.revision,
|
|
2096
|
+
phase: "paused",
|
|
2097
|
+
arm: false,
|
|
2098
|
+
});
|
|
2099
|
+
return "idle";
|
|
2100
|
+
}
|
|
2101
|
+
const record = await deps.store.get(sessionId);
|
|
2102
|
+
if (!record) {
|
|
2103
|
+
registry.disarm(sessionId);
|
|
2104
|
+
return "idle";
|
|
2105
|
+
}
|
|
2106
|
+
const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
|
|
2107
|
+
const operationStore = deps.runtime.actionContext?.operations;
|
|
2108
|
+
const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
|
|
2109
|
+
const operations = operationStore
|
|
2110
|
+
? await Promise.all(linkedOperationIds.map((operationId) => operationStore.get(operationId)))
|
|
2111
|
+
: [];
|
|
2112
|
+
const admission = classifyGoalRoundAdmission({
|
|
2113
|
+
phase: goal.phase,
|
|
2114
|
+
armed: goal.armed,
|
|
2115
|
+
round: goal.round,
|
|
2116
|
+
maxRounds: goal.maxRounds,
|
|
2117
|
+
hasQueuedUserTurn: (record.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"),
|
|
2118
|
+
hasActiveTurn: Boolean(deps.events.getActiveTurn(sessionId)),
|
|
2119
|
+
runtimeBusy: Object.values(runtimeFacts).some((value) => value === true),
|
|
2120
|
+
operationBusy: operations.some((operation) => Boolean(operation) && !isTerminalOperationState(operation.state)),
|
|
2121
|
+
});
|
|
2122
|
+
if (admission.action === "wait")
|
|
2123
|
+
return "wait";
|
|
2124
|
+
if (admission.action === "idle")
|
|
2125
|
+
return "idle";
|
|
2126
|
+
const expectedRound = goal.round + 1;
|
|
2127
|
+
const clientRequestId = `goal:${goal.goalId}:${expectedRound}`;
|
|
2128
|
+
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
2129
|
+
clientRequestId,
|
|
2130
|
+
text: buildGoalRoundPrompt(goal),
|
|
2131
|
+
images: [],
|
|
2132
|
+
persistUserMessage: false,
|
|
2133
|
+
onTurnCreated: (turnId) => {
|
|
2134
|
+
const advanced = registry.beginRound({
|
|
2135
|
+
sessionId,
|
|
2136
|
+
goalId: goal.goalId,
|
|
2137
|
+
expectedRevision: goal.revision,
|
|
2138
|
+
turnId,
|
|
2139
|
+
});
|
|
2140
|
+
if (!advanced.ok) {
|
|
2141
|
+
registry.disarm(sessionId);
|
|
2142
|
+
throw new Error(advanced.message);
|
|
2143
|
+
}
|
|
2144
|
+
},
|
|
2145
|
+
});
|
|
2146
|
+
const accepted = started.body.accepted === true;
|
|
2147
|
+
const turnId = started.body.turn && typeof started.body.turn === "object"
|
|
2148
|
+
? started.body.turn.turnId
|
|
2149
|
+
: undefined;
|
|
2150
|
+
if (!accepted || !turnId) {
|
|
2151
|
+
const code = started.body.error && typeof started.body.error === "object"
|
|
2152
|
+
? String(started.body.error.code ?? "")
|
|
2153
|
+
: "";
|
|
2154
|
+
if (code === "TURN_ACTIVE")
|
|
2155
|
+
return "wait";
|
|
2156
|
+
registry.disarm(sessionId);
|
|
2157
|
+
return "idle";
|
|
2158
|
+
}
|
|
2159
|
+
return "started";
|
|
2160
|
+
}
|
|
1768
2161
|
async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
|
|
1769
2162
|
const record = await deps.store.get(sessionId);
|
|
1770
2163
|
if (!record)
|
|
@@ -1985,6 +2378,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
|
1985
2378
|
});
|
|
1986
2379
|
return;
|
|
1987
2380
|
}
|
|
2381
|
+
pauseGoalForSafety(deps, sessionId, turnId);
|
|
2382
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1988
2383
|
if (isTerminalTurnState(turn.state)) {
|
|
1989
2384
|
sendJson(res, 200, {
|
|
1990
2385
|
ok: true,
|
|
@@ -2155,6 +2550,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2155
2550
|
}
|
|
2156
2551
|
await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
|
|
2157
2552
|
deps.todoRegistry?.clear(sessionId);
|
|
2553
|
+
deps.goalRegistry?.clearRuntime(sessionId);
|
|
2554
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2158
2555
|
deps.runtime.dispose(sessionId);
|
|
2159
2556
|
deps.operationLinker.clearSession(sessionId);
|
|
2160
2557
|
deps.events.clear(sessionId);
|
|
@@ -2170,6 +2567,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2170
2567
|
});
|
|
2171
2568
|
return;
|
|
2172
2569
|
}
|
|
2570
|
+
await deps.detachSession?.(sessionId).catch(() => undefined);
|
|
2173
2571
|
sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
|
|
2174
2572
|
}
|
|
2175
2573
|
export async function handleGetTaskContext(_res, res, deps, sessionId) {
|
|
@@ -2229,6 +2627,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
|
|
|
2229
2627
|
});
|
|
2230
2628
|
}
|
|
2231
2629
|
}
|
|
2630
|
+
async function handleRepoHtmlPreview(res, deps, rawPath) {
|
|
2631
|
+
try {
|
|
2632
|
+
const asset = await readRepoHtmlAsset(deps.appData.repoRoot, rawPath);
|
|
2633
|
+
const origin = deps.getConsoleOrigin();
|
|
2634
|
+
res.writeHead(200, {
|
|
2635
|
+
"Content-Type": asset.contentType,
|
|
2636
|
+
"Content-Length": asset.body.byteLength,
|
|
2637
|
+
"Cache-Control": "no-store",
|
|
2638
|
+
"X-Content-Type-Options": "nosniff",
|
|
2639
|
+
"Referrer-Policy": "no-referrer",
|
|
2640
|
+
// The iframe also has an empty sandbox attribute. This response-level
|
|
2641
|
+
// sandbox keeps direct/popup loads isolated, while explicit local-origin
|
|
2642
|
+
// style/image/font/media sources let relative project assets render.
|
|
2643
|
+
"Content-Security-Policy": [
|
|
2644
|
+
"sandbox",
|
|
2645
|
+
"default-src 'none'",
|
|
2646
|
+
"script-src 'none'",
|
|
2647
|
+
"object-src 'none'",
|
|
2648
|
+
"frame-src 'none'",
|
|
2649
|
+
"form-action 'none'",
|
|
2650
|
+
"base-uri 'none'",
|
|
2651
|
+
"connect-src 'none'",
|
|
2652
|
+
`style-src 'unsafe-inline' ${origin}`,
|
|
2653
|
+
`img-src data: blob: ${origin}`,
|
|
2654
|
+
`font-src data: ${origin}`,
|
|
2655
|
+
`media-src blob: ${origin}`,
|
|
2656
|
+
].join("; "),
|
|
2657
|
+
});
|
|
2658
|
+
res.end(asset.body);
|
|
2659
|
+
}
|
|
2660
|
+
catch (error) {
|
|
2661
|
+
const failure = error instanceof RepoBrowserError
|
|
2662
|
+
? error
|
|
2663
|
+
: new RepoBrowserError(500, "READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
2664
|
+
sendJson(res, failure.status, {
|
|
2665
|
+
ok: false,
|
|
2666
|
+
error: { code: failure.code, message: failure.message },
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2232
2670
|
async function handlePiConfig(req, res, deps, resource) {
|
|
2233
2671
|
try {
|
|
2234
2672
|
if (resource === "models") {
|
|
@@ -2974,7 +3412,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
|
|
|
2974
3412
|
taskId,
|
|
2975
3413
|
confirmationId,
|
|
2976
3414
|
expiresAt: confirmation.expiresAt,
|
|
2977
|
-
inspectHref:
|
|
3415
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
2978
3416
|
humanGateToken: confirmation.humanGateToken,
|
|
2979
3417
|
displayOnlyChallenges: [
|
|
2980
3418
|
"source",
|
|
@@ -3000,7 +3438,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
|
|
|
3000
3438
|
receiptId: receipt.receiptId,
|
|
3001
3439
|
expiresAt: receipt.expiresAt,
|
|
3002
3440
|
humanGateToken: receipt.humanGateToken,
|
|
3003
|
-
inspectHref:
|
|
3441
|
+
inspectHref: `/#/inspect`,
|
|
3004
3442
|
}));
|
|
3005
3443
|
}
|
|
3006
3444
|
function actionResultBody(result) {
|
|
@@ -3089,7 +3527,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3089
3527
|
humanGateToken,
|
|
3090
3528
|
contractRevision: binding.revision,
|
|
3091
3529
|
contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
|
|
3092
|
-
inspectHref:
|
|
3530
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3093
3531
|
});
|
|
3094
3532
|
appendHumanGateCard(deps, sessionId, card);
|
|
3095
3533
|
sendJson(res, 201, { ok: true, card });
|
|
@@ -3143,8 +3581,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3143
3581
|
contractRevision: receipt.revision,
|
|
3144
3582
|
operationId,
|
|
3145
3583
|
inspectHref: operationId
|
|
3146
|
-
?
|
|
3147
|
-
:
|
|
3584
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3585
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3148
3586
|
});
|
|
3149
3587
|
appendHumanGateCard(deps, sessionId, card);
|
|
3150
3588
|
sendJson(res, 202, {
|
|
@@ -3190,8 +3628,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3190
3628
|
contractRevision: receipt.revision,
|
|
3191
3629
|
operationId,
|
|
3192
3630
|
inspectHref: operationId
|
|
3193
|
-
?
|
|
3194
|
-
:
|
|
3631
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3632
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3195
3633
|
});
|
|
3196
3634
|
appendHumanGateCard(deps, sessionId, card);
|
|
3197
3635
|
if (operationId)
|
|
@@ -3292,8 +3730,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
|
|
|
3292
3730
|
confirmationId,
|
|
3293
3731
|
expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
|
|
3294
3732
|
inspectHref: operationId
|
|
3295
|
-
?
|
|
3296
|
-
:
|
|
3733
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3734
|
+
: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3297
3735
|
...(confirmation?.humanGateToken
|
|
3298
3736
|
? { humanGateToken: confirmation.humanGateToken }
|
|
3299
3737
|
: {}),
|
|
@@ -3387,8 +3825,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
|
|
|
3387
3825
|
expiresAt: receipt.expiresAt,
|
|
3388
3826
|
operationId,
|
|
3389
3827
|
inspectHref: operationId
|
|
3390
|
-
?
|
|
3391
|
-
:
|
|
3828
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3829
|
+
: `/#/inspect`,
|
|
3392
3830
|
});
|
|
3393
3831
|
appendHumanGateCard(deps, sessionId, card);
|
|
3394
3832
|
if (operationId) {
|
|
@@ -3512,7 +3950,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3512
3950
|
}
|
|
3513
3951
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3514
3952
|
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
3515
|
-
const
|
|
3953
|
+
const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
3954
|
+
const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
|
|
3516
3955
|
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
3517
3956
|
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
3518
3957
|
deps.events.append(sessionId, turnId, {
|
|
@@ -3649,6 +4088,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3649
4088
|
await handleChatCapabilities(req, res, deps);
|
|
3650
4089
|
return true;
|
|
3651
4090
|
}
|
|
4091
|
+
const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
|
|
4092
|
+
if (method === "GET" && htmlPreviewRoute) {
|
|
4093
|
+
if (!htmlPreviewRoute.rawPath)
|
|
4094
|
+
sendJson(res, 400, {
|
|
4095
|
+
ok: false,
|
|
4096
|
+
error: { code: "INVALID_PATH", message: "path is required" },
|
|
4097
|
+
});
|
|
4098
|
+
else
|
|
4099
|
+
await handleRepoHtmlPreview(res, deps, htmlPreviewRoute.rawPath);
|
|
4100
|
+
return true;
|
|
4101
|
+
}
|
|
3652
4102
|
if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
|
|
3653
4103
|
await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
|
|
3654
4104
|
return true;
|
|
@@ -3780,6 +4230,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3780
4230
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3781
4231
|
return true;
|
|
3782
4232
|
}
|
|
4233
|
+
const sessionGoal = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/goal$/);
|
|
4234
|
+
if (sessionGoal &&
|
|
4235
|
+
(method === "GET" || method === "POST" || method === "PATCH" || method === "DELETE")) {
|
|
4236
|
+
await handleChatGoal(req, res, deps, decodeURIComponent(sessionGoal[1]));
|
|
4237
|
+
return true;
|
|
4238
|
+
}
|
|
3783
4239
|
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3784
4240
|
if (method === "PUT" && deferredTurn) {
|
|
3785
4241
|
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|