@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
|
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
|
|
3
3
|
import { hydrateSessionThread } from "../../chat/turn-process.js";
|
|
4
4
|
import { forgetActiveChatSession, readActiveChatSessionId, rememberActiveChatSession, } from "../../chat/workspace-landing.js";
|
|
5
|
-
import { CHAT_SESSION_LIST_LIMIT } from "../chat-view-types.js";
|
|
6
5
|
import { confirmationToken } from "./format.js";
|
|
7
6
|
import { upsertCompactionMessage } from "./compaction-message.js";
|
|
8
7
|
import { describeMutationGateFailure, isMutationGateFailureCode, isStaleTokenFailure, } from "./mutation-gate.js";
|
|
@@ -49,7 +48,7 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
49
48
|
const nextList = [
|
|
50
49
|
durable,
|
|
51
50
|
...input.sessionListRef.current.filter((item) => item.sessionId !== durable.sessionId),
|
|
52
|
-
]
|
|
51
|
+
];
|
|
53
52
|
// Commit ref → state → eligibility → deferred refresh. The ref assignment
|
|
54
53
|
// makes the durable record observable before React's state commit.
|
|
55
54
|
input.sessionListRef.current = nextList;
|
|
@@ -78,8 +77,8 @@ export function useChatSessions(params) {
|
|
|
78
77
|
const workspaceQuery = workspaceId
|
|
79
78
|
? `?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
80
79
|
: "";
|
|
81
|
-
/** AC-002 (2026-08-26): session requests follow the
|
|
82
|
-
* owning workspace, not the global active workspace. The sidebar list
|
|
80
|
+
/** AC-002 (2026-08-26): session detail and mutation requests follow the
|
|
81
|
+
* session's DURABLE owning workspace, not the global active workspace. The sidebar list
|
|
83
82
|
* items carry workspaceId; once a session is activated its owner is kept
|
|
84
83
|
* on the session record itself. Switching the Operate/Inspect active
|
|
85
84
|
* workspace therefore never retargets an open session's requests. */
|
|
@@ -147,26 +146,30 @@ export function useChatSessions(params) {
|
|
|
147
146
|
if (blocked)
|
|
148
147
|
return; // AC-R5-002: fail-closed, zero session requests
|
|
149
148
|
try {
|
|
150
|
-
|
|
149
|
+
// The sidebar is a multi-workspace index. Keep one unscoped summary
|
|
150
|
+
// collection so expanding or switching a workspace never projects the
|
|
151
|
+
// previous workspace's list as an empty result while a scoped refresh is
|
|
152
|
+
// in flight. Session detail and mutation requests remain owner-scoped.
|
|
153
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions`, {
|
|
151
154
|
credentials: "include",
|
|
152
155
|
});
|
|
153
|
-
|
|
156
|
+
const body = (await res.json().catch(() => ({})));
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
setError(body.error?.message ?? `会话列表加载失败(HTTP ${res.status})`);
|
|
154
159
|
return;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const keep = scoped.slice(0, CHAT_SESSION_LIST_LIMIT);
|
|
164
|
-
setSessionList(keep);
|
|
160
|
+
}
|
|
161
|
+
const sessions = body.sessions ?? [];
|
|
162
|
+
// Preserve every workspace's summaries. ChatWorkspaceSidebar applies the
|
|
163
|
+
// visible recent-item limit independently inside each workspace group.
|
|
164
|
+
setSessionList(sessions);
|
|
165
|
+
if (body.workspaceErrors?.length) {
|
|
166
|
+
setError(`部分工作区的会话加载失败(${body.workspaceErrors.length} 个)`);
|
|
167
|
+
}
|
|
165
168
|
}
|
|
166
|
-
catch {
|
|
167
|
-
|
|
169
|
+
catch (error) {
|
|
170
|
+
setError(`会话列表加载失败:${error instanceof Error ? error.message : String(error)}`);
|
|
168
171
|
}
|
|
169
|
-
}, [origin,
|
|
172
|
+
}, [origin, blocked, setError]);
|
|
170
173
|
const deleteSession = useCallback(async (sessionId) => {
|
|
171
174
|
try {
|
|
172
175
|
// AC-002: delete follows the session's durable owner, not the
|
|
@@ -268,6 +271,7 @@ export function useChatSessions(params) {
|
|
|
268
271
|
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
269
272
|
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
270
273
|
todos: body.session.todos ?? [],
|
|
274
|
+
goal: body.session.goal ?? null,
|
|
271
275
|
});
|
|
272
276
|
rememberActiveChatSession(window.localStorage, body.session.workspaceId ?? ownerWorkspaceId ?? workspaceId, sessionId);
|
|
273
277
|
setActivePreviewPath(null);
|
|
@@ -398,6 +402,7 @@ export function useChatSessions(params) {
|
|
|
398
402
|
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
399
403
|
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
400
404
|
todos: body.session.todos ?? [],
|
|
405
|
+
goal: body.session.goal ?? null,
|
|
401
406
|
});
|
|
402
407
|
}
|
|
403
408
|
catch {
|
|
@@ -404,14 +404,12 @@ export function useChatStream(params) {
|
|
|
404
404
|
const activeSession = refs.sessionRef.current;
|
|
405
405
|
if (!activeSession || (!input.text.trim() && input.images.length === 0))
|
|
406
406
|
return { action: "handled" };
|
|
407
|
-
showNotice("正在排队…");
|
|
408
407
|
const acceptDeferred = (deferredTurn, deferredTurns) => {
|
|
409
408
|
setDeferredTurn(deferredTurns?.[0] ?? deferredTurn);
|
|
410
409
|
setDeferredTurns(deferredTurns ?? [deferredTurn]);
|
|
411
410
|
setInput("");
|
|
412
411
|
setPendingImages([]);
|
|
413
412
|
setError(null);
|
|
414
|
-
showNotice("已排队,将在当前回合结束后自动发送");
|
|
415
413
|
};
|
|
416
414
|
const acceptDispatched = () => {
|
|
417
415
|
setDeferredTurn(null);
|
|
@@ -419,7 +417,6 @@ export function useChatStream(params) {
|
|
|
419
417
|
setInput("");
|
|
420
418
|
setPendingImages([]);
|
|
421
419
|
setError(null);
|
|
422
|
-
showNotice("消息已进入发送");
|
|
423
420
|
};
|
|
424
421
|
const putOnce = async () => {
|
|
425
422
|
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
|
|
@@ -472,7 +469,6 @@ export function useChatStream(params) {
|
|
|
472
469
|
if (decision.action === "retry-deferred" && attempt === 0)
|
|
473
470
|
continue;
|
|
474
471
|
if (decision.action === "send-now") {
|
|
475
|
-
showNotice("上一轮已停止,正在发送当前消息…");
|
|
476
472
|
return { action: "send-now" };
|
|
477
473
|
}
|
|
478
474
|
setError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
|
|
@@ -496,7 +492,6 @@ export function useChatStream(params) {
|
|
|
496
492
|
return { action: "handled" };
|
|
497
493
|
}
|
|
498
494
|
if (decision.action === "send-now") {
|
|
499
|
-
showNotice("上一轮已停止,正在发送当前消息…");
|
|
500
495
|
return { action: "send-now" };
|
|
501
496
|
}
|
|
502
497
|
setError(error instanceof Error
|
|
@@ -513,7 +508,6 @@ export function useChatStream(params) {
|
|
|
513
508
|
setInput,
|
|
514
509
|
setPendingImages,
|
|
515
510
|
setError,
|
|
516
|
-
showNotice,
|
|
517
511
|
fetchSessionState,
|
|
518
512
|
]);
|
|
519
513
|
/** DELETE the queued turn; "restore" moves its text back into the composer,
|
|
@@ -549,10 +543,6 @@ export function useChatStream(params) {
|
|
|
549
543
|
if (mode === "restore") {
|
|
550
544
|
setInput(deferred.text ?? body.deferredTurn?.text ?? "");
|
|
551
545
|
setPendingImages(deferred.images ?? body.deferredTurn?.images ?? []);
|
|
552
|
-
showNotice("待发送消息已移回输入框");
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
showNotice("已删除排队消息");
|
|
556
546
|
}
|
|
557
547
|
setError(null);
|
|
558
548
|
}
|
|
@@ -569,7 +559,6 @@ export function useChatStream(params) {
|
|
|
569
559
|
setInput,
|
|
570
560
|
setPendingImages,
|
|
571
561
|
setError,
|
|
572
|
-
showNotice,
|
|
573
562
|
]);
|
|
574
563
|
const cancelDeferredTurn = useCallback(() => requestDeferredTurnDelete("restore"), [requestDeferredTurnDelete]);
|
|
575
564
|
const discardDeferredTurn = useCallback((target) => requestDeferredTurnDelete("discard", target), [requestDeferredTurnDelete]);
|
|
@@ -44,7 +44,7 @@ function operationCardFromOperationFact(op) {
|
|
|
44
44
|
state,
|
|
45
45
|
createdAt,
|
|
46
46
|
updatedAt,
|
|
47
|
-
inspectHref:
|
|
47
|
+
inspectHref: `/#/inspect?operationId=${encodeURIComponent(operationId)}`,
|
|
48
48
|
...(str("taskId") ? { taskId: str("taskId") } : {}),
|
|
49
49
|
...(dagRunId ? { dagRunId } : {}),
|
|
50
50
|
...(str("errorCode") ? { errorCode: str("errorCode") } : {}),
|
|
@@ -131,6 +131,7 @@ export function useChatThread(params) {
|
|
|
131
131
|
const [deferredTurns, setDeferredTurns] = useState([]);
|
|
132
132
|
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
133
133
|
const [todos, setTodos] = useState([]);
|
|
134
|
+
const [goal, setGoal] = useState(null);
|
|
134
135
|
/** Assistant bubble id for the in-flight turn; only this row shows the streaming cursor. */
|
|
135
136
|
const [streamingAssistantId, setStreamingAssistantId] = useState(null);
|
|
136
137
|
const [showSystemInMessages] = useState(false);
|
|
@@ -169,6 +170,7 @@ export function useChatThread(params) {
|
|
|
169
170
|
setDeferredTurns,
|
|
170
171
|
setPendingQuestion,
|
|
171
172
|
setTodos,
|
|
173
|
+
setGoal,
|
|
172
174
|
setStreamingAssistantId,
|
|
173
175
|
setUnread,
|
|
174
176
|
setOperationCards,
|
|
@@ -337,6 +339,7 @@ export function useChatThread(params) {
|
|
|
337
339
|
setDeferredTurns([]);
|
|
338
340
|
setPendingQuestion(null);
|
|
339
341
|
setTodos([]);
|
|
342
|
+
setGoal(null);
|
|
340
343
|
setStreamingAssistantId(null);
|
|
341
344
|
setUnread(0);
|
|
342
345
|
setHistoryLimit(50);
|
|
@@ -363,6 +366,7 @@ export function useChatThread(params) {
|
|
|
363
366
|
setDeferredTurns(next.deferredTurns ?? (next.deferredTurn ? [next.deferredTurn] : []));
|
|
364
367
|
setPendingQuestion(next.pendingQuestion ?? null);
|
|
365
368
|
setTodos(next.todos ?? []);
|
|
369
|
+
setGoal(next.goal ?? null);
|
|
366
370
|
setStreamingAssistantId(null);
|
|
367
371
|
setUnread(0);
|
|
368
372
|
setHistoryLimit(50);
|
|
@@ -393,6 +397,7 @@ export function useChatThread(params) {
|
|
|
393
397
|
setDeferredTurns(body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []));
|
|
394
398
|
setPendingQuestion(body.session.pendingQuestion ?? null);
|
|
395
399
|
setTodos(body.session.todos ?? []);
|
|
400
|
+
setGoal(body.session.goal ?? null);
|
|
396
401
|
if (body.sessionStats)
|
|
397
402
|
setSessionStats(body.sessionStats);
|
|
398
403
|
if (body.usage)
|
|
@@ -455,6 +460,8 @@ export function useChatThread(params) {
|
|
|
455
460
|
pendingQuestion,
|
|
456
461
|
setPendingQuestion,
|
|
457
462
|
todos,
|
|
463
|
+
goal,
|
|
464
|
+
setGoal,
|
|
458
465
|
streamingAssistantId,
|
|
459
466
|
setStreamingAssistantId,
|
|
460
467
|
visibleMessages,
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
|
|
3
|
+
const REPO_HTML_PREVIEW_ROUTE_PREFIX = "/api/operator/v1/chat/repo/html/";
|
|
4
|
+
/** DSH-style path-encoded preview URL: relative HTML assets resolve under the
|
|
5
|
+
* same workspace-scoped route instead of losing a query-string workspaceId. */
|
|
6
|
+
export function repoHtmlPreviewUrl(origin, workspaceId, file) {
|
|
7
|
+
const id = workspaceId?.trim();
|
|
8
|
+
const segments = file
|
|
9
|
+
.replaceAll("\\", "/")
|
|
10
|
+
.split("/")
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
if (!id || segments.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const base = origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
15
|
+
return `${base}${REPO_HTML_PREVIEW_ROUTE_PREFIX}${encodeURIComponent(id)}/${segments.map(encodeURIComponent).join("/")}`;
|
|
16
|
+
}
|
|
17
|
+
function withHtmlPreviewUrl(preview, origin, workspaceId) {
|
|
18
|
+
const language = preview.language.trim().toLowerCase();
|
|
19
|
+
if (language !== "html" && language !== "htm")
|
|
20
|
+
return preview;
|
|
21
|
+
const htmlPreviewUrl = repoHtmlPreviewUrl(origin, workspaceId, preview.path);
|
|
22
|
+
if (!htmlPreviewUrl)
|
|
23
|
+
return preview;
|
|
24
|
+
return {
|
|
25
|
+
...preview,
|
|
26
|
+
htmlPreviewUrl,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
3
29
|
/** Details-rail repo tree + file preview tabs state. */
|
|
4
30
|
export function useRepoBrowser(params) {
|
|
5
31
|
const { origin, refs, sessionId, workspaceId, blocked, setError } = params;
|
|
@@ -77,17 +103,18 @@ export function useRepoBrowser(params) {
|
|
|
77
103
|
return;
|
|
78
104
|
if (!res.ok || !body.data)
|
|
79
105
|
throw new Error(body.error?.message ?? "无法预览文件");
|
|
106
|
+
const loaded = withHtmlPreviewUrl(body.data, origin, workspaceId);
|
|
80
107
|
setPreviews((current) => {
|
|
81
108
|
const next = [
|
|
82
|
-
...current.filter((item) => item.path !==
|
|
83
|
-
|
|
109
|
+
...current.filter((item) => item.path !== loaded.path),
|
|
110
|
+
loaded,
|
|
84
111
|
];
|
|
85
112
|
const activeSessionId = refs.sessionRef.current?.sessionId;
|
|
86
113
|
if (activeSessionId)
|
|
87
114
|
window.localStorage.setItem(`${ACTIVE_CHAT_SESSION_STORAGE_KEY}.previews.${activeSessionId}`, JSON.stringify(next.map((item) => item.path)));
|
|
88
115
|
return next;
|
|
89
116
|
});
|
|
90
|
-
setActivePreviewPath(
|
|
117
|
+
setActivePreviewPath(loaded.path);
|
|
91
118
|
}
|
|
92
119
|
catch (error) {
|
|
93
120
|
if (workspaceScopeRef.current !== requestWorkspaceId)
|
|
@@ -152,7 +179,10 @@ export function useRepoBrowser(params) {
|
|
|
152
179
|
const res = await fetch(`${origin}/api/operator/v1/chat/repo/file?path=${encodeURIComponent(file)}${workspaceQuery}`, { credentials: "include" });
|
|
153
180
|
if (!res.ok)
|
|
154
181
|
return undefined;
|
|
155
|
-
|
|
182
|
+
const restored = (await res.json()).data;
|
|
183
|
+
return restored
|
|
184
|
+
? withHtmlPreviewUrl(restored, origin, workspaceId)
|
|
185
|
+
: undefined;
|
|
156
186
|
})).then((restored) => {
|
|
157
187
|
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
158
188
|
return;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Resolve DSH-style half-row hover to an insert-before anchor; undefined is a no-op. */
|
|
2
|
+
export function resolveWorkspaceDropAnchor(workspaces, sourceId, targetWorkspaceId, half) {
|
|
3
|
+
const targetIndex = workspaces.findIndex((workspace) => workspace.workspaceId === targetWorkspaceId);
|
|
4
|
+
const sourceIndex = workspaces.findIndex((workspace) => workspace.workspaceId === sourceId);
|
|
5
|
+
if (targetIndex === -1 || sourceIndex === -1)
|
|
6
|
+
return undefined;
|
|
7
|
+
const beforeWorkspaceId = half === "before"
|
|
8
|
+
? targetWorkspaceId
|
|
9
|
+
: (workspaces[targetIndex + 1]?.workspaceId ?? null);
|
|
10
|
+
if (beforeWorkspaceId === sourceId)
|
|
11
|
+
return undefined;
|
|
12
|
+
const anchorIndex = beforeWorkspaceId
|
|
13
|
+
? workspaces.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId)
|
|
14
|
+
: workspaces.length;
|
|
15
|
+
return anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1
|
|
16
|
+
? undefined
|
|
17
|
+
: beforeWorkspaceId;
|
|
18
|
+
}
|
|
@@ -4,7 +4,7 @@ import { describeMutationGateFailure, isMutationGateFailureCode, } from "../oper
|
|
|
4
4
|
/**
|
|
5
5
|
* Multi-workspace registry state (2026-08-23, AC-001/AC-003).
|
|
6
6
|
*
|
|
7
|
-
* Consumes the process-level GET/POST/DELETE /api/operator/v1/workspaces
|
|
7
|
+
* Consumes the process-level GET/POST/PATCH/DELETE /api/operator/v1/workspaces
|
|
8
8
|
* surface. POST/DELETE pass the Console mutation-gate confirmation token.
|
|
9
9
|
* The selected workspaceId is remembered in localStorage and validated
|
|
10
10
|
* against the visible list. A stale local preference falls back to the first
|
|
@@ -64,6 +64,11 @@ export function createWorkspacesController(params = {}) {
|
|
|
64
64
|
const storage = params.storage;
|
|
65
65
|
const token = params.confirmationTokenFn ?? confirmationToken;
|
|
66
66
|
const listeners = new Set();
|
|
67
|
+
let appliedRevision = 0;
|
|
68
|
+
let refreshQueued = false;
|
|
69
|
+
let refreshPromise = null;
|
|
70
|
+
let eventStream;
|
|
71
|
+
let disposed = false;
|
|
67
72
|
let state = {
|
|
68
73
|
workspaces: [],
|
|
69
74
|
loading: true,
|
|
@@ -79,44 +84,90 @@ export function createWorkspacesController(params = {}) {
|
|
|
79
84
|
state = { ...state, ...patch };
|
|
80
85
|
emit();
|
|
81
86
|
};
|
|
82
|
-
const refresh =
|
|
87
|
+
const refresh = () => {
|
|
88
|
+
refreshQueued = true;
|
|
89
|
+
if (refreshPromise)
|
|
90
|
+
return refreshPromise;
|
|
91
|
+
refreshPromise = (async () => {
|
|
92
|
+
while (refreshQueued && !disposed) {
|
|
93
|
+
refreshQueued = false;
|
|
94
|
+
try {
|
|
95
|
+
const res = await fetchImpl(`${origin}/api/operator/v1/workspaces`, {
|
|
96
|
+
credentials: "include",
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok)
|
|
99
|
+
continue;
|
|
100
|
+
const body = (await res.json());
|
|
101
|
+
const revision = Number.isSafeInteger(body.revision)
|
|
102
|
+
? Math.max(0, body.revision ?? 0)
|
|
103
|
+
: undefined;
|
|
104
|
+
// Legacy/injected responses without revision remain usable. A
|
|
105
|
+
// revisioned response can never roll the projection backwards.
|
|
106
|
+
if (revision !== undefined && revision < appliedRevision)
|
|
107
|
+
continue;
|
|
108
|
+
if (revision !== undefined)
|
|
109
|
+
appliedRevision = revision;
|
|
110
|
+
const list = body.workspaces ?? [];
|
|
111
|
+
const pick = pickDefaultWorkspaceId(list, state.workspaceId);
|
|
112
|
+
if (pick.status === "unknown-workspace") {
|
|
113
|
+
setState({
|
|
114
|
+
workspaces: list,
|
|
115
|
+
unknownWorkspaceId: pick.workspaceId,
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const nextId = pick.workspaceId;
|
|
120
|
+
const previousId = state.workspaceId;
|
|
121
|
+
setState({
|
|
122
|
+
workspaces: list,
|
|
123
|
+
unknownWorkspaceId: null,
|
|
124
|
+
...(nextId !== previousId ? { workspaceId: nextId } : {}),
|
|
125
|
+
});
|
|
126
|
+
if (nextId !== previousId)
|
|
127
|
+
rememberWorkspaceId(nextId, storage);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* Registry unreachable: keep last known list. */
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (state.loading)
|
|
134
|
+
setState({ loading: false });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})().finally(() => {
|
|
138
|
+
refreshPromise = null;
|
|
139
|
+
// An event can arrive between the loop condition and finally.
|
|
140
|
+
if (refreshQueued && !disposed)
|
|
141
|
+
void refresh();
|
|
142
|
+
});
|
|
143
|
+
return refreshPromise;
|
|
144
|
+
};
|
|
145
|
+
const consumeRegistryEvent = (event) => {
|
|
83
146
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (!res.ok)
|
|
88
|
-
return;
|
|
89
|
-
const body = (await res.json());
|
|
90
|
-
const list = body.workspaces ?? [];
|
|
91
|
-
// A stale local preference falls back to the first server-visible
|
|
92
|
-
// Workspace. Explicit route ids are validated separately and remain
|
|
93
|
-
// fail-closed.
|
|
94
|
-
const pick = pickDefaultWorkspaceId(list, state.workspaceId);
|
|
95
|
-
if (pick.status === "unknown-workspace") {
|
|
96
|
-
setState({
|
|
97
|
-
workspaces: list,
|
|
98
|
-
unknownWorkspaceId: pick.workspaceId,
|
|
99
|
-
});
|
|
100
|
-
return;
|
|
147
|
+
const revision = Number(JSON.parse(event.data).revision);
|
|
148
|
+
if (Number.isSafeInteger(revision) && revision > appliedRevision) {
|
|
149
|
+
void refresh();
|
|
101
150
|
}
|
|
102
|
-
const nextId = pick.workspaceId;
|
|
103
|
-
const previousId = state.workspaceId;
|
|
104
|
-
setState({
|
|
105
|
-
workspaces: list,
|
|
106
|
-
unknownWorkspaceId: null,
|
|
107
|
-
...(nextId !== previousId ? { workspaceId: nextId } : {}),
|
|
108
|
-
});
|
|
109
|
-
if (nextId !== previousId)
|
|
110
|
-
rememberWorkspaceId(nextId, storage);
|
|
111
151
|
}
|
|
112
152
|
catch {
|
|
113
|
-
/*
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
if (state.loading)
|
|
117
|
-
setState({ loading: false });
|
|
153
|
+
/* Malformed frames are ignored; reconnect baseline remains authoritative. */
|
|
118
154
|
}
|
|
119
155
|
};
|
|
156
|
+
const startEventStream = () => {
|
|
157
|
+
if (eventStream || disposed)
|
|
158
|
+
return;
|
|
159
|
+
const factory = params.eventSourceFactory ??
|
|
160
|
+
(typeof EventSource !== "undefined"
|
|
161
|
+
? (url) => new EventSource(url, {
|
|
162
|
+
withCredentials: true,
|
|
163
|
+
})
|
|
164
|
+
: undefined);
|
|
165
|
+
if (!factory)
|
|
166
|
+
return;
|
|
167
|
+
eventStream = factory(`${origin}/api/operator/v1/workspaces/events`);
|
|
168
|
+
eventStream.addEventListener("workspace_registry_ready", consumeRegistryEvent);
|
|
169
|
+
eventStream.addEventListener("workspace_registry_changed", consumeRegistryEvent);
|
|
170
|
+
};
|
|
120
171
|
const addWorkspace = async (directoryPath, name) => {
|
|
121
172
|
setState({ error: null });
|
|
122
173
|
try {
|
|
@@ -214,12 +265,66 @@ export function createWorkspacesController(params = {}) {
|
|
|
214
265
|
return { ok: false, error: display };
|
|
215
266
|
}
|
|
216
267
|
};
|
|
268
|
+
const reorderWorkspace = async (workspaceId, beforeWorkspaceId) => {
|
|
269
|
+
const previous = state.workspaces;
|
|
270
|
+
const source = previous.find((workspace) => workspace.workspaceId === workspaceId);
|
|
271
|
+
const anchor = beforeWorkspaceId
|
|
272
|
+
? previous.find((workspace) => workspace.workspaceId === beforeWorkspaceId)
|
|
273
|
+
: undefined;
|
|
274
|
+
if (!source || (beforeWorkspaceId && !anchor)) {
|
|
275
|
+
return { ok: false, error: "工作区顺序已变化,请稍后重试。" };
|
|
276
|
+
}
|
|
277
|
+
const optimistic = previous.filter((workspace) => workspace.workspaceId !== workspaceId);
|
|
278
|
+
const insertAt = beforeWorkspaceId
|
|
279
|
+
? optimistic.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId)
|
|
280
|
+
: optimistic.length;
|
|
281
|
+
optimistic.splice(insertAt, 0, source);
|
|
282
|
+
if (optimistic.every((workspace, index) => workspace.workspaceId === previous[index]?.workspaceId)) {
|
|
283
|
+
return { ok: true };
|
|
284
|
+
}
|
|
285
|
+
setState({ workspaces: optimistic, error: null });
|
|
286
|
+
try {
|
|
287
|
+
const res = await fetchImpl(`${origin}/api/operator/v1/workspaces/${encodeURIComponent(workspaceId)}`, {
|
|
288
|
+
method: "PATCH",
|
|
289
|
+
credentials: "include",
|
|
290
|
+
headers: {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
"x-loop-console-confirmation": token(),
|
|
293
|
+
},
|
|
294
|
+
body: JSON.stringify({ beforeWorkspaceId }),
|
|
295
|
+
});
|
|
296
|
+
const body = (await res.json().catch(() => ({})));
|
|
297
|
+
if (!res.ok) {
|
|
298
|
+
const message = isMutationGateFailureCode(body.error?.code)
|
|
299
|
+
? describeMutationGateFailure(body.error?.code ?? "")
|
|
300
|
+
: (body.error?.message ?? `调整 Workspace 顺序失败(HTTP ${res.status})`);
|
|
301
|
+
setState({ workspaces: previous, error: message });
|
|
302
|
+
void refresh();
|
|
303
|
+
return { ok: false, error: message };
|
|
304
|
+
}
|
|
305
|
+
return { ok: true };
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
const message = `调整 Workspace 顺序失败:${error instanceof Error ? error.message : String(error)}`;
|
|
309
|
+
setState({ workspaces: previous, error: message });
|
|
310
|
+
void refresh();
|
|
311
|
+
return { ok: false, error: message };
|
|
312
|
+
}
|
|
313
|
+
};
|
|
217
314
|
const selectWorkspace = (next) => {
|
|
218
315
|
if (next === state.workspaceId && !state.unknownWorkspaceId)
|
|
219
316
|
return;
|
|
220
317
|
setState({ workspaceId: next, unknownWorkspaceId: null });
|
|
221
318
|
rememberWorkspaceId(next, storage);
|
|
222
319
|
};
|
|
320
|
+
const dispose = () => {
|
|
321
|
+
if (disposed)
|
|
322
|
+
return;
|
|
323
|
+
disposed = true;
|
|
324
|
+
eventStream?.close();
|
|
325
|
+
eventStream = undefined;
|
|
326
|
+
listeners.clear();
|
|
327
|
+
};
|
|
223
328
|
void refresh();
|
|
224
329
|
return {
|
|
225
330
|
get workspaces() {
|
|
@@ -241,10 +346,19 @@ export function createWorkspacesController(params = {}) {
|
|
|
241
346
|
pickWorkspaceDirectory,
|
|
242
347
|
addWorkspace,
|
|
243
348
|
removeWorkspace,
|
|
349
|
+
reorderWorkspace,
|
|
244
350
|
selectWorkspace,
|
|
351
|
+
dispose,
|
|
245
352
|
subscribe(listener) {
|
|
246
353
|
listeners.add(listener);
|
|
247
|
-
|
|
354
|
+
startEventStream();
|
|
355
|
+
return () => {
|
|
356
|
+
listeners.delete(listener);
|
|
357
|
+
if (listeners.size === 0) {
|
|
358
|
+
eventStream?.close();
|
|
359
|
+
eventStream = undefined;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
248
362
|
},
|
|
249
363
|
};
|
|
250
364
|
}
|
|
@@ -294,6 +408,8 @@ export function useWorkspaces(params) {
|
|
|
294
408
|
pickWorkspaceDirectory: controller.pickWorkspaceDirectory,
|
|
295
409
|
addWorkspace: controller.addWorkspace,
|
|
296
410
|
removeWorkspace: controller.removeWorkspace,
|
|
411
|
+
reorderWorkspace: controller.reorderWorkspace,
|
|
297
412
|
selectWorkspace: controller.selectWorkspace,
|
|
413
|
+
dispose: controller.dispose,
|
|
298
414
|
}), [state, controller]);
|
|
299
415
|
}
|
|
@@ -33,6 +33,9 @@ import { createChatTurnExecutionRegistry } from "./chat/turn-execution-registry.
|
|
|
33
33
|
import { ConsolePiRuntime } from "./chat/pi-runtime.js";
|
|
34
34
|
import { UserQuestionRegistry } from "./chat/user-questions.js";
|
|
35
35
|
import { TodoListRegistry } from "./chat/todos.js";
|
|
36
|
+
import { GoalRegistry } from "./chat/goals.js";
|
|
37
|
+
import { GoalRoundDriver } from "./chat/goal-round-driver.js";
|
|
38
|
+
import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
|
|
36
39
|
import { createOperationEventStore, } from "./operation-sse.js";
|
|
37
40
|
import { OperationStore } from "./operation-store.js";
|
|
38
41
|
import { probePiReadiness, } from "./pi-readiness.js";
|
|
@@ -107,7 +110,16 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
107
110
|
fetchImpl: deps.fetchImpl,
|
|
108
111
|
getConsoleOrigin: deps.getConsoleOrigin,
|
|
109
112
|
};
|
|
110
|
-
const chatStore = new ChatSessionStore(appData
|
|
113
|
+
const chatStore = new ChatSessionStore(appData, {
|
|
114
|
+
onRecordChange: (sessionId, session) => {
|
|
115
|
+
if (session) {
|
|
116
|
+
deps.chatSessionCatalog?.upsert(session, record.workspaceId);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
deps.chatSessionCatalog?.remove(sessionId);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
111
123
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
112
124
|
const chatEvents = createChatEventStore({
|
|
113
125
|
appData,
|
|
@@ -116,6 +128,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
116
128
|
const chatTurnExecutions = createChatTurnExecutionRegistry();
|
|
117
129
|
const questionRegistry = new UserQuestionRegistry(path.join(appData.chats, "user-questions"));
|
|
118
130
|
const todoRegistry = new TodoListRegistry();
|
|
131
|
+
const goalRegistry = new GoalRegistry(chatEvents);
|
|
119
132
|
questionRegistry.subscribe((change) => {
|
|
120
133
|
const question = change.question;
|
|
121
134
|
chatEvents.append(question.sessionId, question.turnId ?? `${question.sessionId}:question`, {
|
|
@@ -144,21 +157,27 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
144
157
|
actionContext,
|
|
145
158
|
questionRegistry,
|
|
146
159
|
todoRegistry,
|
|
160
|
+
goalRegistry,
|
|
147
161
|
});
|
|
148
|
-
|
|
162
|
+
let chatRouteDeps;
|
|
163
|
+
const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
|
|
164
|
+
chatRouteDeps = {
|
|
149
165
|
runtime: chatRuntime,
|
|
150
166
|
store: chatStore,
|
|
151
167
|
events: chatEvents,
|
|
152
168
|
turnExecutions: chatTurnExecutions,
|
|
153
169
|
questionRegistry,
|
|
154
170
|
todoRegistry,
|
|
171
|
+
goalRegistry,
|
|
172
|
+
goalRoundDriver,
|
|
155
173
|
operationLinker: chatOperationLinker,
|
|
156
174
|
appData,
|
|
157
175
|
bootToken: deps.bootToken,
|
|
158
176
|
getConsoleOrigin: deps.getConsoleOrigin,
|
|
159
177
|
allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
|
|
178
|
+
attachSession: (sessionId) => registry.attachSession(record.workspaceId, sessionId),
|
|
179
|
+
detachSession: (sessionId) => registry.detachSession(record.workspaceId, sessionId),
|
|
160
180
|
};
|
|
161
|
-
const { recoverDeferredTurns } = await import("./chat/routes.js");
|
|
162
181
|
const nightAuxTicker = new NightAuxTicker({ repoRoot, client });
|
|
163
182
|
const observeRouteContext = createObserveRouteContext(repoRoot, {
|
|
164
183
|
staticDir: deps.inspectStaticDir,
|
|
@@ -191,6 +210,12 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
191
210
|
if (disposed)
|
|
192
211
|
return;
|
|
193
212
|
disposed = true;
|
|
213
|
+
try {
|
|
214
|
+
goalRoundDriver.dispose();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// best-effort
|
|
218
|
+
}
|
|
194
219
|
try {
|
|
195
220
|
chatOperationLinker.dispose();
|
|
196
221
|
}
|