@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +106 -109
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/executors/dag-pi-executor.js +69 -7
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +43 -3
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +10 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +312 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +583 -172
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +29 -9
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +174 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +533 -79
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +6 -4
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +29 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +238 -51
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +62 -12
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { parseAssistantContent } from "../../chat/assistant-content.js";
|
|
3
2
|
import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
|
|
4
3
|
import { hydrateSessionThread } from "../../chat/turn-process.js";
|
|
5
|
-
import {
|
|
4
|
+
import { forgetActiveChatSession, readActiveChatSessionId, rememberActiveChatSession, } from "../../chat/workspace-landing.js";
|
|
6
5
|
import { CHAT_SESSION_LIST_LIMIT } from "../chat-view-types.js";
|
|
7
6
|
import { confirmationToken } from "./format.js";
|
|
7
|
+
import { upsertCompactionMessage } from "./compaction-message.js";
|
|
8
8
|
import { describeMutationGateFailure, isMutationGateFailureCode, isStaleTokenFailure, } from "./mutation-gate.js";
|
|
9
9
|
import { SessionTitleWatcher } from "./session-title-watcher.js";
|
|
10
10
|
export async function createSessionAndPrimeTitleWatcher(input) {
|
|
@@ -28,6 +28,9 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
28
28
|
});
|
|
29
29
|
if (!res.ok) {
|
|
30
30
|
const body = (await res.json().catch(() => ({})));
|
|
31
|
+
if (isMutationGateFailureCode(body.error?.code)) {
|
|
32
|
+
throw new Error(describeMutationGateFailure(body.error.code));
|
|
33
|
+
}
|
|
31
34
|
throw new Error(body.error?.message ?? `HTTP ${res.status}`);
|
|
32
35
|
}
|
|
33
36
|
const body = (await res.json());
|
|
@@ -36,6 +39,11 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
36
39
|
const durable = body.session;
|
|
37
40
|
const created = {
|
|
38
41
|
sessionId: durable.sessionId,
|
|
42
|
+
...(durable.workspaceId || input.options?.workspaceId
|
|
43
|
+
? {
|
|
44
|
+
workspaceId: durable.workspaceId ?? input.options?.workspaceId,
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
39
47
|
...runtimeSelectionFromRecord(durable),
|
|
40
48
|
};
|
|
41
49
|
const nextList = [
|
|
@@ -56,6 +64,9 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
56
64
|
input.setCreatingSession?.(false);
|
|
57
65
|
}
|
|
58
66
|
}
|
|
67
|
+
function mergeCompactionMessages(messages, compactions) {
|
|
68
|
+
return (compactions ?? []).reduce((result, item) => upsertCompactionMessage(result, item), messages);
|
|
69
|
+
}
|
|
59
70
|
/** Session lifecycle: list / activate / create / delete / rename-archive /
|
|
60
71
|
* compact, plus browser-refresh recovery from the durable session store. */
|
|
61
72
|
export function useChatSessions(params) {
|
|
@@ -63,6 +74,21 @@ export function useChatSessions(params) {
|
|
|
63
74
|
const { applyActivatedSession, resetForNewSession, setMessages } = thread;
|
|
64
75
|
const { setInput, setPendingImages } = composer;
|
|
65
76
|
const { setPreviews, setActivePreviewPath } = browser;
|
|
77
|
+
const { workspaceId, blocked } = params;
|
|
78
|
+
const workspaceQuery = workspaceId
|
|
79
|
+
? `?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
80
|
+
: "";
|
|
81
|
+
/** AC-002 (2026-08-26): session requests follow the session's DURABLE
|
|
82
|
+
* owning workspace, not the global active workspace. The sidebar list
|
|
83
|
+
* items carry workspaceId; once a session is activated its owner is kept
|
|
84
|
+
* on the session record itself. Switching the Operate/Inspect active
|
|
85
|
+
* workspace therefore never retargets an open session's requests. */
|
|
86
|
+
const sessionOwnerQuery = useCallback((ownerWorkspaceId) => {
|
|
87
|
+
const owner = ownerWorkspaceId?.trim() ||
|
|
88
|
+
refs.sessionRef.current?.workspaceId?.trim() ||
|
|
89
|
+
"";
|
|
90
|
+
return owner ? `?workspaceId=${encodeURIComponent(owner)}` : "";
|
|
91
|
+
}, [refs]);
|
|
66
92
|
const [sessionList, setSessionList] = useState([]);
|
|
67
93
|
const sessionListRef = useRef([]);
|
|
68
94
|
sessionListRef.current = sessionList;
|
|
@@ -76,11 +102,15 @@ export function useChatSessions(params) {
|
|
|
76
102
|
// state. This prevents slow responses from an older selection overwriting a
|
|
77
103
|
// newer user choice.
|
|
78
104
|
const sessionSelectionVersionRef = useRef(0);
|
|
79
|
-
const resetActiveProjection = useCallback(() => {
|
|
80
|
-
// M4.5/M5.2: selection is a hard ownership boundary
|
|
81
|
-
//
|
|
105
|
+
const resetActiveProjection = useCallback((options) => {
|
|
106
|
+
// M4.5/M5.2: selection is a hard ownership boundary for THIS projection.
|
|
107
|
+
// Multi-workspace (AC-002): keepStreams=true (workspace switch) aborts
|
|
108
|
+
// only projection-local requests and leaves background SSE streams of
|
|
109
|
+
// other sessions/workspaces running.
|
|
110
|
+
const keepStreams = options?.keepStreams === true;
|
|
82
111
|
refs.sessionGenerationRef.current += 1;
|
|
83
|
-
|
|
112
|
+
if (!keepStreams)
|
|
113
|
+
refs.abortRef.current?.abort();
|
|
84
114
|
refs.reconnectAbortRef.current?.abort();
|
|
85
115
|
for (const controller of refs.sessionAbortControllersRef.current.values())
|
|
86
116
|
controller.abort();
|
|
@@ -114,43 +144,36 @@ export function useChatSessions(params) {
|
|
|
114
144
|
setAutoScroll,
|
|
115
145
|
]);
|
|
116
146
|
const refreshSessionList = useCallback(async () => {
|
|
147
|
+
if (blocked)
|
|
148
|
+
return; // AC-R5-002: fail-closed, zero session requests
|
|
117
149
|
try {
|
|
118
|
-
const res = await fetch(`${origin}/api/operator/v1/chat/sessions`, {
|
|
150
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions${workspaceQuery}`, {
|
|
119
151
|
credentials: "include",
|
|
120
152
|
});
|
|
121
153
|
if (!res.ok)
|
|
122
154
|
return;
|
|
123
|
-
const sessions = (await res.json())
|
|
124
|
-
[];
|
|
155
|
+
const sessions = (await res.json())
|
|
156
|
+
.sessions ?? [];
|
|
157
|
+
// Multi-workspace (AC-002): list only this workspace's sessions when a
|
|
158
|
+
// scope is active; unscoped (legacy) keeps the full list.
|
|
159
|
+
const scoped = workspaceId
|
|
160
|
+
? sessions.filter((item) => !item.workspaceId || item.workspaceId === workspaceId)
|
|
161
|
+
: sessions;
|
|
125
162
|
// API returns newest-first; keep only the recent window in the sidebar.
|
|
126
|
-
const keep =
|
|
127
|
-
const excess = sessions.slice(CHAT_SESSION_LIST_LIMIT);
|
|
163
|
+
const keep = scoped.slice(0, CHAT_SESSION_LIST_LIMIT);
|
|
128
164
|
setSessionList(keep);
|
|
129
|
-
if (excess.length === 0)
|
|
130
|
-
return;
|
|
131
|
-
await Promise.all(excess.map((item) => fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(item.sessionId)}`, {
|
|
132
|
-
method: "DELETE",
|
|
133
|
-
credentials: "include",
|
|
134
|
-
headers: {
|
|
135
|
-
"x-loop-console-confirmation": confirmationToken(),
|
|
136
|
-
},
|
|
137
|
-
}).catch(() => undefined)));
|
|
138
|
-
const activeId = refs.sessionRef.current?.sessionId;
|
|
139
|
-
if (activeId && excess.some((item) => item.sessionId === activeId)) {
|
|
140
|
-
sessionSelectionVersionRef.current += 1;
|
|
141
|
-
refs.sessionRef.current = null;
|
|
142
|
-
resetActiveProjection();
|
|
143
|
-
setSession(null);
|
|
144
|
-
window.localStorage.removeItem(ACTIVE_CHAT_SESSION_STORAGE_KEY);
|
|
145
|
-
}
|
|
146
165
|
}
|
|
147
166
|
catch {
|
|
148
167
|
/* Chat remains usable without the sidebar index. */
|
|
149
168
|
}
|
|
150
|
-
}, [origin,
|
|
169
|
+
}, [origin, workspaceQuery, workspaceId, blocked]);
|
|
151
170
|
const deleteSession = useCallback(async (sessionId) => {
|
|
152
171
|
try {
|
|
153
|
-
|
|
172
|
+
// AC-002: delete follows the session's durable owner, not the
|
|
173
|
+
// global active workspace.
|
|
174
|
+
const ownerQuery = sessionOwnerQuery(sessionListRef.current.find((item) => item.sessionId === sessionId)
|
|
175
|
+
?.workspaceId ?? null);
|
|
176
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}${ownerQuery}`, {
|
|
154
177
|
method: "DELETE",
|
|
155
178
|
credentials: "include",
|
|
156
179
|
headers: {
|
|
@@ -163,11 +186,12 @@ export function useChatSessions(params) {
|
|
|
163
186
|
}
|
|
164
187
|
titleWatcherRef.current?.cancel(sessionId);
|
|
165
188
|
if (refs.sessionRef.current?.sessionId === sessionId) {
|
|
189
|
+
const activeWorkspaceId = refs.sessionRef.current.workspaceId ?? workspaceId;
|
|
166
190
|
sessionSelectionVersionRef.current += 1;
|
|
167
191
|
refs.sessionRef.current = null;
|
|
168
192
|
resetActiveProjection();
|
|
169
193
|
setSession(null);
|
|
170
|
-
window.localStorage
|
|
194
|
+
forgetActiveChatSession(window.localStorage, activeWorkspaceId, sessionId);
|
|
171
195
|
}
|
|
172
196
|
await refreshSessionList();
|
|
173
197
|
return true;
|
|
@@ -183,11 +207,17 @@ export function useChatSessions(params) {
|
|
|
183
207
|
resetActiveProjection,
|
|
184
208
|
refreshSessionList,
|
|
185
209
|
setError,
|
|
210
|
+
sessionOwnerQuery,
|
|
211
|
+
workspaceId,
|
|
186
212
|
]);
|
|
187
|
-
const activateSession = useCallback(async (sessionId) => {
|
|
213
|
+
const activateSession = useCallback(async (sessionId, ownerWorkspaceId) => {
|
|
188
214
|
const selectionVersion = ++sessionSelectionVersionRef.current;
|
|
215
|
+
// AC-002: activation targets the session's durable owning workspace —
|
|
216
|
+
// never the global active workspace (no cross-workspace leak when
|
|
217
|
+
// Operate/Inspect switch their scope while a session stays open).
|
|
218
|
+
const ownerQuery = sessionOwnerQuery(ownerWorkspaceId);
|
|
189
219
|
try {
|
|
190
|
-
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`, { credentials: "include" });
|
|
220
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}${ownerQuery}`, { credentials: "include" });
|
|
191
221
|
const body = (await res.json());
|
|
192
222
|
if (!res.ok || !body.session)
|
|
193
223
|
throw new Error(body.error?.message ?? "会话不可用");
|
|
@@ -195,12 +225,23 @@ export function useChatSessions(params) {
|
|
|
195
225
|
return;
|
|
196
226
|
const next = {
|
|
197
227
|
sessionId: body.session.sessionId,
|
|
228
|
+
...(body.session.workspaceId ? { workspaceId: body.session.workspaceId } : {}),
|
|
198
229
|
...runtimeSelectionFromRecord(body.session),
|
|
199
230
|
};
|
|
200
|
-
|
|
231
|
+
// AC-002: session switch (same or cross workspace) never aborts other
|
|
232
|
+
// sessions' background request controllers; the single /events owner is
|
|
233
|
+
// re-bound by useChatStream's session effect.
|
|
234
|
+
resetActiveProjection({ keepStreams: true });
|
|
201
235
|
refs.sessionRef.current = next;
|
|
202
|
-
refs.streamingRef.current = Boolean(body.session.activeTurnId);
|
|
203
236
|
refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
|
|
237
|
+
const visuallyStopped = body.session.turnPhase === "stopping";
|
|
238
|
+
refs.streamingRef.current = visuallyStopped
|
|
239
|
+
? false
|
|
240
|
+
: Boolean(body.session.activeTurnId);
|
|
241
|
+
refs.stoppingTurnIdRef.current = visuallyStopped
|
|
242
|
+
? (body.session.activeTurnId ?? null)
|
|
243
|
+
: null;
|
|
244
|
+
refs.stopRequestedRef.current = visuallyStopped;
|
|
204
245
|
refs.seenEventIdsRef.current = new Set();
|
|
205
246
|
refs.turnAssistantIdsRef.current = new Map();
|
|
206
247
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
@@ -214,15 +255,21 @@ export function useChatSessions(params) {
|
|
|
214
255
|
setPreviews([]);
|
|
215
256
|
setInput(body.session.composerDraft ?? "");
|
|
216
257
|
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
258
|
+
hydrated.messages = mergeCompactionMessages(hydrated.messages, body.compactions);
|
|
217
259
|
applyActivatedSession({
|
|
218
260
|
messages: hydrated.messages,
|
|
219
261
|
toolCalls: hydrated.toolCalls,
|
|
220
|
-
|
|
262
|
+
sessionStats: body.sessionStats,
|
|
263
|
+
usage: body.usage,
|
|
264
|
+
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
221
265
|
turnPhase: body.session.turnPhase ??
|
|
222
266
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
223
267
|
deferredTurn: body.session.deferredTurn ?? null,
|
|
268
|
+
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
269
|
+
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
270
|
+
todos: body.session.todos ?? [],
|
|
224
271
|
});
|
|
225
|
-
window.localStorage.
|
|
272
|
+
rememberActiveChatSession(window.localStorage, body.session.workspaceId ?? ownerWorkspaceId ?? workspaceId, sessionId);
|
|
226
273
|
setActivePreviewPath(null);
|
|
227
274
|
}
|
|
228
275
|
catch (error) {
|
|
@@ -241,6 +288,8 @@ export function useChatSessions(params) {
|
|
|
241
288
|
resetActiveProjection,
|
|
242
289
|
setActivePreviewPath,
|
|
243
290
|
setError,
|
|
291
|
+
sessionOwnerQuery,
|
|
292
|
+
workspaceId,
|
|
244
293
|
]);
|
|
245
294
|
useEffect(() => {
|
|
246
295
|
void refreshSessionList();
|
|
@@ -271,28 +320,45 @@ export function useChatSessions(params) {
|
|
|
271
320
|
.map((item) => item.sessionId));
|
|
272
321
|
}, [sessionList]);
|
|
273
322
|
useEffect(() => () => titleWatcherRef.current?.dispose(), []);
|
|
274
|
-
// Browser refresh recovery:
|
|
275
|
-
//
|
|
323
|
+
// Browser refresh/workspace-switch recovery: selection is client state keyed
|
|
324
|
+
// by workspace; messages and operation refs/facts remain server-owned.
|
|
276
325
|
useEffect(() => {
|
|
277
326
|
let cancelled = false;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return;
|
|
327
|
+
if (blocked)
|
|
328
|
+
return; // AC-R5-002: unknown workspace — no restore GET
|
|
281
329
|
const selectionVersion = ++sessionSelectionVersionRef.current;
|
|
330
|
+
// Remove the old workspace projection immediately. Background session
|
|
331
|
+
// streams remain alive; only the visible selection is rebound below.
|
|
332
|
+
resetActiveProjection({ keepStreams: true });
|
|
333
|
+
refs.sessionRef.current = null;
|
|
334
|
+
setSession(null);
|
|
335
|
+
const savedSessionId = readActiveChatSessionId(window.localStorage, workspaceId);
|
|
336
|
+
if (!savedSessionId) {
|
|
337
|
+
// Keep the per-workspace map intact, but clear the legacy global pointer
|
|
338
|
+
// so Operate/Models cannot link a session from another workspace.
|
|
339
|
+
forgetActiveChatSession(window.localStorage, workspaceId);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
282
342
|
void (async () => {
|
|
283
343
|
try {
|
|
284
|
-
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(savedSessionId)}`, { credentials: "include" });
|
|
344
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(savedSessionId)}${workspaceQuery}`, { credentials: "include" });
|
|
285
345
|
if (!res.ok) {
|
|
286
|
-
window.localStorage
|
|
346
|
+
forgetActiveChatSession(window.localStorage, workspaceId, savedSessionId);
|
|
287
347
|
return;
|
|
288
348
|
}
|
|
289
349
|
const body = (await res.json());
|
|
290
350
|
if (cancelled ||
|
|
351
|
+
blocked ||
|
|
291
352
|
selectionVersion !== sessionSelectionVersionRef.current ||
|
|
292
353
|
!body.session)
|
|
293
354
|
return;
|
|
294
355
|
const restored = {
|
|
295
356
|
sessionId: body.session.sessionId,
|
|
357
|
+
...(body.session.workspaceId
|
|
358
|
+
? { workspaceId: body.session.workspaceId }
|
|
359
|
+
: workspaceId
|
|
360
|
+
? { workspaceId }
|
|
361
|
+
: {}),
|
|
296
362
|
...runtimeSelectionFromRecord(body.session),
|
|
297
363
|
};
|
|
298
364
|
refs.sessionGenerationRef.current += 1;
|
|
@@ -302,25 +368,37 @@ export function useChatSessions(params) {
|
|
|
302
368
|
// in the event ring and must replay once. applySseBlock dedups by
|
|
303
369
|
// eventId and marks replay so usage/unread/sound do not double-count.
|
|
304
370
|
refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
|
|
371
|
+
const visuallyStopped = body.session.turnPhase === "stopping";
|
|
372
|
+
refs.streamingRef.current = visuallyStopped
|
|
373
|
+
? false
|
|
374
|
+
: Boolean(body.session.activeTurnId);
|
|
375
|
+
refs.stoppingTurnIdRef.current = visuallyStopped
|
|
376
|
+
? (body.session.activeTurnId ?? null)
|
|
377
|
+
: null;
|
|
378
|
+
refs.stopRequestedRef.current = visuallyStopped;
|
|
305
379
|
refs.seenEventIdsRef.current = new Set();
|
|
306
380
|
refs.turnAssistantIdsRef.current = new Map();
|
|
307
381
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
308
382
|
refs.settledTurnIdsRef.current = new Set();
|
|
309
383
|
refs.lastEventIdRef.current = null;
|
|
310
384
|
setSession(restored);
|
|
385
|
+
rememberActiveChatSession(window.localStorage, restored.workspaceId ?? workspaceId, restored.sessionId);
|
|
311
386
|
setInput(body.session.composerDraft ?? "");
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
387
|
+
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
388
|
+
hydrated.messages = mergeCompactionMessages(hydrated.messages, body.compactions);
|
|
389
|
+
applyActivatedSession({
|
|
390
|
+
messages: hydrated.messages,
|
|
391
|
+
toolCalls: hydrated.toolCalls,
|
|
392
|
+
sessionStats: body.sessionStats,
|
|
393
|
+
usage: body.usage,
|
|
394
|
+
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
395
|
+
turnPhase: body.session.turnPhase ??
|
|
396
|
+
(body.session.activeTurnId ? "running" : "idle"),
|
|
397
|
+
deferredTurn: body.session.deferredTurn ?? null,
|
|
398
|
+
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
399
|
+
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
400
|
+
todos: body.session.todos ?? [],
|
|
401
|
+
});
|
|
324
402
|
}
|
|
325
403
|
catch {
|
|
326
404
|
// Recovery is best-effort; a new session remains available.
|
|
@@ -329,35 +407,58 @@ export function useChatSessions(params) {
|
|
|
329
407
|
return () => {
|
|
330
408
|
cancelled = true;
|
|
331
409
|
};
|
|
332
|
-
}, [
|
|
410
|
+
}, [
|
|
411
|
+
origin,
|
|
412
|
+
workspaceQuery,
|
|
413
|
+
refs,
|
|
414
|
+
setSession,
|
|
415
|
+
setInput,
|
|
416
|
+
applyActivatedSession,
|
|
417
|
+
blocked,
|
|
418
|
+
resetActiveProjection,
|
|
419
|
+
workspaceId,
|
|
420
|
+
]);
|
|
333
421
|
// The sole production create path delegates to the exported orchestration
|
|
334
422
|
// helper so its durable commit order is directly traceable and testable.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
423
|
+
// AC-003 (2026-08-26): session creation REQUIRES a known workspaceId —
|
|
424
|
+
// either explicit (sidebar create-in-workspace) or the current valid
|
|
425
|
+
// selection. No scope → fail closed client-side (the server would reject
|
|
426
|
+
// with WORKSPACE_REQUIRED anyway); never a default-workspace fallback.
|
|
427
|
+
const createSessionAndPrimeTitleWatcherForHook = useCallback((options) => {
|
|
428
|
+
const scope = options?.workspaceId?.trim() ?? workspaceId?.trim();
|
|
429
|
+
if (!scope) {
|
|
430
|
+
setError("创建会话需要选择一个有效的工作区(不会回退默认工作区)");
|
|
431
|
+
return Promise.reject(new Error("WORKSPACE_REQUIRED"));
|
|
432
|
+
}
|
|
433
|
+
return createSessionAndPrimeTitleWatcher({
|
|
434
|
+
origin,
|
|
435
|
+
sessionListRef,
|
|
436
|
+
setSessionList,
|
|
437
|
+
titleWatcher: titleWatcherRef.current,
|
|
438
|
+
refreshSessionList,
|
|
439
|
+
options: { ...options, workspaceId: scope },
|
|
440
|
+
setCreatingSession,
|
|
441
|
+
creatingRef: creatingSessionRef,
|
|
442
|
+
});
|
|
443
|
+
}, [origin, refreshSessionList, workspaceId, setError]);
|
|
345
444
|
const createSession = useCallback(async (options) => {
|
|
346
445
|
// Suppress concurrent create from any entry (sidebar / Landing / chip).
|
|
347
446
|
if (creatingSessionRef.current)
|
|
348
447
|
return null;
|
|
448
|
+
if (blocked)
|
|
449
|
+
return null; // AC-R5-002: unknown workspace — no POST
|
|
349
450
|
const selectionVersion = ++sessionSelectionVersionRef.current;
|
|
350
451
|
setError(null);
|
|
351
452
|
try {
|
|
352
453
|
const created = await createSessionAndPrimeTitleWatcherForHook(options);
|
|
353
454
|
if (selectionVersion !== sessionSelectionVersionRef.current)
|
|
354
455
|
return null;
|
|
355
|
-
resetActiveProjection();
|
|
456
|
+
resetActiveProjection({ keepStreams: true });
|
|
356
457
|
// Set ref synchronously BEFORE setState so a chained
|
|
357
458
|
// createSession().then(sendPrompt) reads the new session even
|
|
358
459
|
// though sendPrompt's closure captured the stale (null) value.
|
|
359
460
|
refs.sessionRef.current = created;
|
|
360
|
-
window.localStorage.
|
|
461
|
+
rememberActiveChatSession(window.localStorage, created.workspaceId ?? options?.workspaceId ?? workspaceId, created.sessionId);
|
|
361
462
|
setSession(created);
|
|
362
463
|
return created;
|
|
363
464
|
}
|
|
@@ -376,6 +477,8 @@ export function useChatSessions(params) {
|
|
|
376
477
|
setError,
|
|
377
478
|
resetActiveProjection,
|
|
378
479
|
createSessionAndPrimeTitleWatcherForHook,
|
|
480
|
+
blocked,
|
|
481
|
+
workspaceId,
|
|
379
482
|
]);
|
|
380
483
|
// Phase-1: fork / in-session branch / mainline switch UI deferred; API routes remain.
|
|
381
484
|
/** Single compact helper for toolbar + `/compact <instructions>` (AC-1). */
|
|
@@ -392,7 +495,8 @@ export function useChatSessions(params) {
|
|
|
392
495
|
if (trimmed) {
|
|
393
496
|
headers["content-type"] = "application/json";
|
|
394
497
|
}
|
|
395
|
-
|
|
498
|
+
// AC-002: compaction follows the session's durable owner.
|
|
499
|
+
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(current.sessionId)}/compact${sessionOwnerQuery()}`, {
|
|
396
500
|
method: "POST",
|
|
397
501
|
credentials: "include",
|
|
398
502
|
headers,
|
|
@@ -421,22 +525,8 @@ export function useChatSessions(params) {
|
|
|
421
525
|
// pi-web parity: reload the compacted session, then leave a distinct
|
|
422
526
|
// compaction summary card in the timeline plus a transient toast.
|
|
423
527
|
const summary = body.snapshot?.summary?.trim();
|
|
424
|
-
const tokensBefore = body.snapshot?.tokensBefore;
|
|
425
528
|
const summaryText = summary || "历史上下文已精简,关键决策与当前任务已保留。";
|
|
426
529
|
await activateSession(current.sessionId);
|
|
427
|
-
setMessages((messages) => [
|
|
428
|
-
...messages,
|
|
429
|
-
{
|
|
430
|
-
id: `compaction-${Date.now()}`,
|
|
431
|
-
role: "assistant",
|
|
432
|
-
kind: "compaction",
|
|
433
|
-
text: summaryText,
|
|
434
|
-
createdAt: Date.now(),
|
|
435
|
-
...(tokensBefore
|
|
436
|
-
? { compaction: { tokensBefore } }
|
|
437
|
-
: {}),
|
|
438
|
-
},
|
|
439
|
-
]);
|
|
440
530
|
setNotice({
|
|
441
531
|
id: Date.now(),
|
|
442
532
|
message: `已压缩上下文:${summaryText.slice(0, 72)}${summaryText.length > 72 ? "…" : ""}`,
|
|
@@ -453,12 +543,14 @@ export function useChatSessions(params) {
|
|
|
453
543
|
refs,
|
|
454
544
|
setError,
|
|
455
545
|
setNotice,
|
|
456
|
-
setMessages,
|
|
457
546
|
onCompactionChange,
|
|
458
547
|
activateSession,
|
|
548
|
+
sessionOwnerQuery,
|
|
459
549
|
]);
|
|
460
550
|
const updateSession = async (sessionId, patch) => {
|
|
461
|
-
|
|
551
|
+
// AC-002: rename/archive follows the session's durable owner.
|
|
552
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}${sessionOwnerQuery(sessionListRef.current.find((item) => item.sessionId === sessionId)
|
|
553
|
+
?.workspaceId ?? null)}`, {
|
|
462
554
|
method: "PATCH",
|
|
463
555
|
credentials: "include",
|
|
464
556
|
headers: {
|
|
@@ -477,11 +569,13 @@ export function useChatSessions(params) {
|
|
|
477
569
|
}
|
|
478
570
|
if (patch.state === "archived" &&
|
|
479
571
|
refs.sessionRef.current?.sessionId === sessionId) {
|
|
572
|
+
const activeWorkspaceId = refs.sessionRef.current.workspaceId ?? workspaceId;
|
|
573
|
+
const activeSessionId = refs.sessionRef.current.sessionId;
|
|
480
574
|
sessionSelectionVersionRef.current += 1;
|
|
481
575
|
refs.sessionRef.current = null;
|
|
482
576
|
resetActiveProjection();
|
|
483
577
|
setSession(null);
|
|
484
|
-
window.localStorage
|
|
578
|
+
forgetActiveChatSession(window.localStorage, activeWorkspaceId, activeSessionId);
|
|
485
579
|
}
|
|
486
580
|
};
|
|
487
581
|
return {
|