@tea-agent/loop-agent 0.40.0-next.12 → 0.40.0-next.14
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 +38 -0
- package/README.md +2 -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/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/shared/operator/capabilities.js +9 -1
- 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 +140 -4
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +482 -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/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-C_hkwt2J.js → abnfDiagram-N423BO3Z-B0gRLXJs.js} +1 -1
- package/dist/worker/console/static/assets/{arc-s2Q6r96B.js → arc-DldY0Hsk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CtgsrwU1.js → architectureDiagram-T3A2C74G-4xHhMRM9.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DZ-A7EiJ.js → blockDiagram-VBNYF7ZC-DPym7n0M.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D2qv0Nyo.js → c4Diagram-5PPSVZJV-cSFzY5yS.js} +1 -1
- package/dist/worker/console/static/assets/channel-4xBA5kwn.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D-UYpSaW.js → chunk-2GRJ4B5K-w-64RMuc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DAS3jrrt.js → chunk-2Q5K7J3B-Bvue--jh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CSbuRcGB.js → chunk-5RXB4S5H-DxqkQnCh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BDH1ZO40.js → chunk-5VM5RSS4-C83gOKLK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dc2cqQIb.js → chunk-6Q2QTUOP-C_V6tKAV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CkZuemIW.js → chunk-GF5L2VYU-CPvZQuL9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-URwjQnte.js → chunk-JWPE2WC7-w31Z_ATM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-04RHylJK.js → chunk-KBJHAD2P-DermQC9f.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CvQX56Hh.js → chunk-RYQCIY6F-CmwaYcnB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-dUF7qura.js → chunk-XXDRQBXY-DLwZqUBf.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CkefOueI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CkefOueI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-COwbfxJE.js → cose-bilkent-JH36ORCC-Dzh7bT73.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CaBEJekQ.js → cynefin-VYW2F7L2-CfOcBHtp.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-COWUBdet.js → cynefinDiagram-MW4NZA55-D0EbCVDZ.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BLxVqTn4.js → dagre-VZM6K2ZE-MfOFA6PZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-WL0yKfEQ.js → diagram-7IWD3JNH-BOa5-9PN.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DaVkKCle.js → diagram-B4RE2ZJO-B6zvJ-aL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DdTgnRGf.js → diagram-LBJQPF4R-BKwr5bRF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-PG2Axo6A.js → diagram-Q27KOJAE-PLI5QLpX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-6vuQzlzI.js → diagram-UB23O5K3-BgYYME3o.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BpXeBz-g.js → ebnfDiagram-BXEA7PRR-D-SpjXk2.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-PxtUnaa1.js → erDiagram-JOGREHBK--JHpEOka.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B--Qgvs1.js → flowDiagram-UKHOOZJN-CbkQgWUz.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BIfQCgtx.js → ganttDiagram-PKOTCBZU-CTZMAMVE.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BRaX_E4j.js → gitGraphDiagram-DS77QQ5N-BaUGSDig.js} +1 -1
- package/dist/worker/console/static/assets/index-C4mO0i6P.js +437 -0
- package/dist/worker/console/static/assets/index-C86Y55Or.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Cd93r4Ab.js → infoDiagram-6WML65LV-DA3WbWjf.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C4c80iHt.js → ishikawaDiagram-WSZJBQD7-DbhI82U4.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Ch5ajf3s.js → journeyDiagram-NVQOT4AX-CJKA3OBG.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DbpyW1Ut.js → kanban-definition-27J2QSJJ-CF6dIVRa.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BHVjBnTW.js → linear-D4dEtOUL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DO70kJ34.js → mermaid.core-ClhMic6O.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-_P44Zpvs.js → mindmap-definition-FAOFIHXS-NfRin7S2.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bgi9FmEd.js → pegDiagram-VL7TDLO6-DlBtBwW-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-C9InDfJn.js → pieDiagram-7S7Q4E2Y-DdIkNHZO.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKrUouv.js → quadrantDiagram-CIZ2JOQS-YD8T98WX.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-zgAD_6Yo.js → railroadDiagram-AXF67PYL-CS4e5zGK.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQNO6kj4.js → requirementDiagram-LRYGKXZP-BpbWoL9r.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-knKu111P.js → sankeyDiagram-W5VNT64P-DvWy-Mc9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-D6tshiQ6.js → sequenceDiagram-SI44F4Z6-DmoXH5pJ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BMIWJcir.js → sizeCapture-X5ZJPWSS-BY89V1SW.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BP9wRDqc.js → stateDiagram-OKZ733FA-DaK2Iuqr.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DLc5WD-z.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DEkM5Dhe.js → swimlanes-SLNWSIFB-VVcozat-.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DhjxpI7d.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ByBKsL35.js → timeline-definition-Z64GVDOM-D4l1E4Mu.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-waFbNCUy.js → vennDiagram-T6HMQDX7-CJmewqcq.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-1ljiY0eZ.js → wardleyDiagram-T6FBY63Y-CsCCYorj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B4ZLHr_L.js → xychartDiagram-ELKLHX3M-CwjILi0N.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 +78 -25
- package/dist/worker/observe/static/views/dag-inspector.js +2 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/workflows/dag/init-hybrid.js +3 -3
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-BluaMT3-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-H-ZpSafr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-H-ZpSafr.js +0 -1
- package/dist/worker/console/static/assets/index-Crrgl8Mx.css +0 -1
- package/dist/worker/console/static/assets/index-vPsp-idE.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-ClEhytQG.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPg5IMuu.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,6 +823,7 @@ 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,
|
|
@@ -770,6 +839,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
770
839
|
eventId: event.eventId,
|
|
771
840
|
kind: event.kind,
|
|
772
841
|
at: event.at,
|
|
842
|
+
seq: event.seq,
|
|
843
|
+
turnId: event.turnId,
|
|
773
844
|
data: event.data,
|
|
774
845
|
})),
|
|
775
846
|
lastEventId: lastEvent?.eventId ?? null,
|
|
@@ -1073,9 +1144,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
1073
1144
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1074
1145
|
pendingQuestion: pendingQuestion ?? null,
|
|
1075
1146
|
todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
|
|
1147
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
1076
1148
|
},
|
|
1077
1149
|
});
|
|
1078
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
|
+
}
|
|
1079
1303
|
export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1080
1304
|
const gate = gateMutation(req, deps);
|
|
1081
1305
|
if (!gate.ok) {
|
|
@@ -1124,27 +1348,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
|
|
|
1124
1348
|
}
|
|
1125
1349
|
}
|
|
1126
1350
|
export async function handleListChatSessions(_req, res, deps) {
|
|
1127
|
-
const sessions = await deps
|
|
1351
|
+
const sessions = await listChatSessionSummaries(deps);
|
|
1128
1352
|
sendJson(res, 200, {
|
|
1129
1353
|
ok: true,
|
|
1130
1354
|
schemaVersion: 2,
|
|
1131
|
-
sessions
|
|
1132
|
-
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1133
|
-
return {
|
|
1134
|
-
...session,
|
|
1135
|
-
activeTurnId: active?.turnId,
|
|
1136
|
-
turnPhase: active?.abortRequestedAt
|
|
1137
|
-
? "stopping"
|
|
1138
|
-
: active
|
|
1139
|
-
? "running"
|
|
1140
|
-
: "idle",
|
|
1141
|
-
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1142
|
-
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1143
|
-
};
|
|
1144
|
-
}),
|
|
1355
|
+
sessions,
|
|
1145
1356
|
hasBash: true,
|
|
1146
1357
|
});
|
|
1147
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
|
+
}
|
|
1148
1398
|
export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
1149
1399
|
const gate = gateMutation(req, deps);
|
|
1150
1400
|
if (!gate.ok) {
|
|
@@ -1647,6 +1897,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1647
1897
|
});
|
|
1648
1898
|
return;
|
|
1649
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
|
+
}
|
|
1650
1916
|
const controller = new AbortController();
|
|
1651
1917
|
const execution = deps.turnExecutions?.register({
|
|
1652
1918
|
sessionId,
|
|
@@ -1696,6 +1962,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1696
1962
|
if (placeholder)
|
|
1697
1963
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1698
1964
|
},
|
|
1965
|
+
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1699
1966
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1700
1967
|
sessionId,
|
|
1701
1968
|
turnId: turn.turnId,
|
|
@@ -1758,16 +2025,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1758
2025
|
}
|
|
1759
2026
|
}
|
|
1760
2027
|
finally {
|
|
2028
|
+
if (terminalState !== "settled") {
|
|
2029
|
+
pauseGoalForSafety(deps, sessionId, turn.turnId);
|
|
2030
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2031
|
+
}
|
|
1761
2032
|
execution?.resolveCompletion({
|
|
1762
2033
|
ok: terminalState === "settled",
|
|
1763
2034
|
state: terminalState,
|
|
1764
2035
|
error: terminalError,
|
|
1765
2036
|
});
|
|
1766
2037
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1767
|
-
void dispatchDeferredTurn(deps, sessionId)
|
|
2038
|
+
void dispatchDeferredTurn(deps, sessionId).finally(() => {
|
|
2039
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
2040
|
+
});
|
|
1768
2041
|
}
|
|
1769
2042
|
})();
|
|
1770
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
|
+
}
|
|
1771
2161
|
async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
|
|
1772
2162
|
const record = await deps.store.get(sessionId);
|
|
1773
2163
|
if (!record)
|
|
@@ -1988,6 +2378,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
|
1988
2378
|
});
|
|
1989
2379
|
return;
|
|
1990
2380
|
}
|
|
2381
|
+
pauseGoalForSafety(deps, sessionId, turnId);
|
|
2382
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1991
2383
|
if (isTerminalTurnState(turn.state)) {
|
|
1992
2384
|
sendJson(res, 200, {
|
|
1993
2385
|
ok: true,
|
|
@@ -2158,6 +2550,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2158
2550
|
}
|
|
2159
2551
|
await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
|
|
2160
2552
|
deps.todoRegistry?.clear(sessionId);
|
|
2553
|
+
deps.goalRegistry?.clearRuntime(sessionId);
|
|
2554
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2161
2555
|
deps.runtime.dispose(sessionId);
|
|
2162
2556
|
deps.operationLinker.clearSession(sessionId);
|
|
2163
2557
|
deps.events.clear(sessionId);
|
|
@@ -2173,6 +2567,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2173
2567
|
});
|
|
2174
2568
|
return;
|
|
2175
2569
|
}
|
|
2570
|
+
await deps.detachSession?.(sessionId).catch(() => undefined);
|
|
2176
2571
|
sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
|
|
2177
2572
|
}
|
|
2178
2573
|
export async function handleGetTaskContext(_res, res, deps, sessionId) {
|
|
@@ -2232,6 +2627,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
|
|
|
2232
2627
|
});
|
|
2233
2628
|
}
|
|
2234
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
|
+
}
|
|
2235
2670
|
async function handlePiConfig(req, res, deps, resource) {
|
|
2236
2671
|
try {
|
|
2237
2672
|
if (resource === "models") {
|
|
@@ -2977,7 +3412,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
|
|
|
2977
3412
|
taskId,
|
|
2978
3413
|
confirmationId,
|
|
2979
3414
|
expiresAt: confirmation.expiresAt,
|
|
2980
|
-
inspectHref:
|
|
3415
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
2981
3416
|
humanGateToken: confirmation.humanGateToken,
|
|
2982
3417
|
displayOnlyChallenges: [
|
|
2983
3418
|
"source",
|
|
@@ -3003,7 +3438,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
|
|
|
3003
3438
|
receiptId: receipt.receiptId,
|
|
3004
3439
|
expiresAt: receipt.expiresAt,
|
|
3005
3440
|
humanGateToken: receipt.humanGateToken,
|
|
3006
|
-
inspectHref:
|
|
3441
|
+
inspectHref: `/#/inspect`,
|
|
3007
3442
|
}));
|
|
3008
3443
|
}
|
|
3009
3444
|
function actionResultBody(result) {
|
|
@@ -3092,7 +3527,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3092
3527
|
humanGateToken,
|
|
3093
3528
|
contractRevision: binding.revision,
|
|
3094
3529
|
contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
|
|
3095
|
-
inspectHref:
|
|
3530
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3096
3531
|
});
|
|
3097
3532
|
appendHumanGateCard(deps, sessionId, card);
|
|
3098
3533
|
sendJson(res, 201, { ok: true, card });
|
|
@@ -3146,8 +3581,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3146
3581
|
contractRevision: receipt.revision,
|
|
3147
3582
|
operationId,
|
|
3148
3583
|
inspectHref: operationId
|
|
3149
|
-
?
|
|
3150
|
-
:
|
|
3584
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3585
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3151
3586
|
});
|
|
3152
3587
|
appendHumanGateCard(deps, sessionId, card);
|
|
3153
3588
|
sendJson(res, 202, {
|
|
@@ -3193,8 +3628,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3193
3628
|
contractRevision: receipt.revision,
|
|
3194
3629
|
operationId,
|
|
3195
3630
|
inspectHref: operationId
|
|
3196
|
-
?
|
|
3197
|
-
:
|
|
3631
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3632
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3198
3633
|
});
|
|
3199
3634
|
appendHumanGateCard(deps, sessionId, card);
|
|
3200
3635
|
if (operationId)
|
|
@@ -3295,8 +3730,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
|
|
|
3295
3730
|
confirmationId,
|
|
3296
3731
|
expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
|
|
3297
3732
|
inspectHref: operationId
|
|
3298
|
-
?
|
|
3299
|
-
:
|
|
3733
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3734
|
+
: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3300
3735
|
...(confirmation?.humanGateToken
|
|
3301
3736
|
? { humanGateToken: confirmation.humanGateToken }
|
|
3302
3737
|
: {}),
|
|
@@ -3390,8 +3825,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
|
|
|
3390
3825
|
expiresAt: receipt.expiresAt,
|
|
3391
3826
|
operationId,
|
|
3392
3827
|
inspectHref: operationId
|
|
3393
|
-
?
|
|
3394
|
-
:
|
|
3828
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3829
|
+
: `/#/inspect`,
|
|
3395
3830
|
});
|
|
3396
3831
|
appendHumanGateCard(deps, sessionId, card);
|
|
3397
3832
|
if (operationId) {
|
|
@@ -3515,7 +3950,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3515
3950
|
}
|
|
3516
3951
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3517
3952
|
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
3518
|
-
const
|
|
3953
|
+
const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
3954
|
+
const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
|
|
3519
3955
|
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
3520
3956
|
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
3521
3957
|
deps.events.append(sessionId, turnId, {
|
|
@@ -3652,6 +4088,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3652
4088
|
await handleChatCapabilities(req, res, deps);
|
|
3653
4089
|
return true;
|
|
3654
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
|
+
}
|
|
3655
4102
|
if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
|
|
3656
4103
|
await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
|
|
3657
4104
|
return true;
|
|
@@ -3783,6 +4230,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3783
4230
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3784
4231
|
return true;
|
|
3785
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
|
+
}
|
|
3786
4239
|
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3787
4240
|
if (method === "PUT" && deferredTurn) {
|
|
3788
4241
|
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|