@tea-agent/loop-agent 0.40.0-next.3 → 0.40.0-next.5
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 +21 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +46 -5
- package/dist/executors/shell-executor.js +28 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/task/config-types.js +10 -0
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- 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 +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +292 -29
- 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 +14 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/routes.js +309 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +529 -186
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CGZrpj0K.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-CwEyA8iq.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-DLTkrEQ3.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-4w12zR3t.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-BppqLAjk.js} +1 -1
- package/dist/worker/console/static/assets/channel-TzuwQha5.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DO6U26Pm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-DC7YEdOy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtMYVQBw.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-C5Ed-57N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-BUY4oIzT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-VnWHALIV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-COV-RQk4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-dSnLtUiz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-CX6FDzWh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D3mEgb6W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DCu69b3S.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DCu69b3S.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-QQBMUnAf.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-BxSD-LVV.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-DxHNiNbk.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-C83bYy8E.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-BhtZu2YY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-K0P_ntRE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-CXYy1Nib.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-D-CtIFkU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BEK9-_7c.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-L0EX-9wn.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-9pt0fc_5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-CZVeM_n1.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-DqUKkAZP.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-BYUuXEgp.js} +1 -1
- package/dist/worker/console/static/assets/index-B0jGzQj6.js +407 -0
- package/dist/worker/console/static/assets/index-BiYbPiTw.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-DNzKk7ah.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-Clbyg5IP.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-zDO_gZNO.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-x3LpRYek.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CTpwZqKR.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-D_WXK6C7.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-CWK-OYP5.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-B9gtUPsM.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-Dj2lzceR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-Nwljut7m.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-BY-KNt93.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Vx0wvpPy.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-B6grNXUk.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-CteidBvh.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Du4W8mFK.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-jyI6Yh81.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyQblISl.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-DYWs_fRv.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DiygmgYP.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DO5LLy47.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CLF2p1_L.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-AwTaYBxA.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-DzbblQRw.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -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 +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- 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/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -17
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- 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 +337 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +26 -6
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/index.html +1 -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/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 +172 -0
- package/dist/worker/observe/static/styles.css +20 -1
- package/dist/worker/observe/static/views/dag-inspector.js +4 -3
- package/dist/worker/observe/static/views/dag.js +2 -1
- 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 +1 -1
- package/dist/workflows/dag/init-hybrid.js +19 -0
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/types.js +3 -1
- package/docs/README.md +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/harness.json +3 -3
- package/package.json +3 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { classifyRecoveryFact, consoleRecoveryCtas, partitionRecoveryCtas, recommendedRecoveryCtaId, } from "../../recovery-cta.js";
|
|
3
3
|
import { buildRecoveryReason, isAutoRecoveryReason, pickDefaultRecoveryNodeId, pickDefaultRecoveryRunId, sortRecoveryNodeCandidates, } from "../../recovery-selection.js";
|
|
4
|
-
import { postOperator, RECOVERY_FACT_LABEL, } from "./console-types.js";
|
|
4
|
+
import { postOperator, scopedReadUrl, RECOVERY_FACT_LABEL, } from "./console-types.js";
|
|
5
5
|
import { useRecoveryActions } from "./useRecoveryActions.js";
|
|
6
|
+
import { applyCanonicalHref, buildInspectHref, } from "../shell/workspace-route.js";
|
|
6
7
|
/** Recovery workspace: DAG-run / node candidate selection, fact-classified
|
|
7
8
|
* CTA lanes, structured reason prefill, plan preflight and gated reruns. */
|
|
8
9
|
export function useRecoveryConsole(params) {
|
|
@@ -36,12 +37,12 @@ export function useRecoveryConsole(params) {
|
|
|
36
37
|
const inspectPath = useCallback(() => {
|
|
37
38
|
if (dagRunId.trim()) {
|
|
38
39
|
// from=recovery 激活统一壳的对象上下文 chip 与显式返回路径
|
|
39
|
-
return `/
|
|
40
|
+
return buildInspectHref(`/dag/${encodeURIComponent(dagRunId.trim())}`, "from=recovery");
|
|
40
41
|
}
|
|
41
|
-
return "
|
|
42
|
+
return "/#/inspect";
|
|
42
43
|
}, [dagRunId]);
|
|
43
44
|
const openFullInspect = useCallback(() => {
|
|
44
|
-
|
|
45
|
+
applyCanonicalHref(inspectPath());
|
|
45
46
|
}, [inspectPath]);
|
|
46
47
|
/** Resolve DAG run id for inspect (side panel or full page). */
|
|
47
48
|
const resolveDagRunForInspect = useCallback(async () => {
|
|
@@ -70,7 +71,7 @@ export function useRecoveryConsole(params) {
|
|
|
70
71
|
setDagRunId(resolvedDag);
|
|
71
72
|
setObserveLinkStatus("match");
|
|
72
73
|
setObserveLinkMessage(null);
|
|
73
|
-
setObserveUrl(`/
|
|
74
|
+
setObserveUrl(buildInspectHref(`/dag/${encodeURIComponent(resolvedDag)}`));
|
|
74
75
|
return resolvedDag;
|
|
75
76
|
}
|
|
76
77
|
setOpError("尚未找到该任务的 DAG 运行,请稍后再打开检视");
|
|
@@ -97,7 +98,7 @@ export function useRecoveryConsole(params) {
|
|
|
97
98
|
const resolved = await resolveDagRunForInspect();
|
|
98
99
|
if (!resolved)
|
|
99
100
|
return;
|
|
100
|
-
|
|
101
|
+
applyCanonicalHref(buildInspectHref(`/dag/${encodeURIComponent(resolved)}`, "from=recovery"));
|
|
101
102
|
}, [resolveDagRunForInspect]);
|
|
102
103
|
const loadRecoveryRuns = useCallback(async () => {
|
|
103
104
|
setRecoveryRunsLoading(true);
|
|
@@ -107,7 +108,7 @@ export function useRecoveryConsole(params) {
|
|
|
107
108
|
if (id)
|
|
108
109
|
qs.set("taskId", id);
|
|
109
110
|
qs.set("limit", "40");
|
|
110
|
-
const res = await fetch(`/api/operator/v1/recovery/candidates?${qs.toString()}
|
|
111
|
+
const res = await fetch(scopedReadUrl(`/api/operator/v1/recovery/candidates?${qs.toString()}`), { credentials: "same-origin" });
|
|
111
112
|
if (!res.ok) {
|
|
112
113
|
setRecoveryRuns([]);
|
|
113
114
|
return;
|
|
@@ -133,7 +134,7 @@ export function useRecoveryConsole(params) {
|
|
|
133
134
|
}
|
|
134
135
|
setRecoveryNodesLoading(true);
|
|
135
136
|
try {
|
|
136
|
-
const res = await fetch(`/api/dag-runs/${encodeURIComponent(id)}
|
|
137
|
+
const res = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(id)}`), {
|
|
137
138
|
credentials: "same-origin",
|
|
138
139
|
});
|
|
139
140
|
if (!res.ok) {
|
|
@@ -253,9 +254,7 @@ export function useRecoveryConsole(params) {
|
|
|
253
254
|
if (!refreshRunStatus)
|
|
254
255
|
return;
|
|
255
256
|
try {
|
|
256
|
-
const res = await fetch(`/api/dag-runs/${encodeURIComponent(runId)}
|
|
257
|
-
credentials: "same-origin",
|
|
258
|
-
});
|
|
257
|
+
const res = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(runId)}`), { credentials: "same-origin" });
|
|
259
258
|
if (!res.ok || cancelled)
|
|
260
259
|
return;
|
|
261
260
|
const runBody = (await res.json());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { isTerminalDagRun } from "../../inspect-split.js";
|
|
3
|
+
import { scopedReadUrl } from "./console-types.js";
|
|
3
4
|
import { summarizeRunProgress, } from "../pages/tasks/run-panel-progress.js";
|
|
4
5
|
const RUN_PROGRESS_POLL_INTERVAL_MS = 5000;
|
|
5
6
|
/**
|
|
@@ -18,7 +19,7 @@ export function useRunProgress(dagRunId) {
|
|
|
18
19
|
let timer;
|
|
19
20
|
const load = async () => {
|
|
20
21
|
try {
|
|
21
|
-
const response = await fetch(`/api/dag-runs/${encodeURIComponent(runId)}
|
|
22
|
+
const response = await fetch(scopedReadUrl(`/api/dag-runs/${encodeURIComponent(runId)}`), { credentials: "same-origin", signal: controller.signal });
|
|
22
23
|
if (!response.ok)
|
|
23
24
|
return;
|
|
24
25
|
const body = (await response.json());
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ensureUniqueDocumentNames, operatorRequestFits, operatorRequestTooLargeMessage, } from "../prd-file-import.js";
|
|
3
|
-
import { requestId, WORKFLOW_OPTIONS, } from "./console-types.js";
|
|
3
|
+
import { requestId, scopedReadUrl, WORKFLOW_OPTIONS, } from "./console-types.js";
|
|
4
4
|
export async function fetchLedgerSummary(taskId, fetchImpl) {
|
|
5
5
|
const impl = fetchImpl ?? fetch;
|
|
6
|
-
const response = await impl(`/api/tasks/${encodeURIComponent(taskId)}/ledger
|
|
6
|
+
const response = await impl(scopedReadUrl(`/api/tasks/${encodeURIComponent(taskId)}/ledger`), { credentials: "same-origin" });
|
|
7
7
|
if (!response.ok)
|
|
8
8
|
return { ok: false };
|
|
9
9
|
const summary = (await response.json());
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { scopedReadUrl } from "../app/console-types.js";
|
|
2
3
|
/** Night board data plane: schedules list + doctor report + task pool +
|
|
3
4
|
* foreground aux-ticker projection, loaded together on mount/refresh. */
|
|
4
5
|
export function useNightBoard(params) {
|
|
@@ -16,10 +17,12 @@ export function useNightBoard(params) {
|
|
|
16
17
|
setLoadError(null);
|
|
17
18
|
try {
|
|
18
19
|
const [jobsRes, doctorRes, tasksRes, tickerRes] = await Promise.all([
|
|
19
|
-
fetch("/api/night-jobs", { credentials: "same-origin" }),
|
|
20
|
-
fetch("/api/night-jobs/doctor", {
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
fetch(scopedReadUrl("/api/night-jobs"), { credentials: "same-origin" }),
|
|
21
|
+
fetch(scopedReadUrl("/api/night-jobs/doctor"), {
|
|
22
|
+
credentials: "same-origin",
|
|
23
|
+
}),
|
|
24
|
+
fetch(scopedReadUrl("/api/tasks"), { credentials: "same-origin" }),
|
|
25
|
+
fetch(scopedReadUrl("/api/operator/v1/night-aux-ticker"), {
|
|
23
26
|
credentials: "same-origin",
|
|
24
27
|
}),
|
|
25
28
|
]);
|
|
@@ -59,7 +62,7 @@ export function useNightBoard(params) {
|
|
|
59
62
|
}, [refreshBoard]);
|
|
60
63
|
const onMorningReport = useCallback(async () => {
|
|
61
64
|
try {
|
|
62
|
-
const res = await fetch("/api/night-jobs/morning", {
|
|
65
|
+
const res = await fetch(scopedReadUrl("/api/night-jobs/morning"), {
|
|
63
66
|
credentials: "same-origin",
|
|
64
67
|
});
|
|
65
68
|
if (!res.ok) {
|
|
@@ -10,7 +10,9 @@ import { parseAssistantContent } from "../../chat/assistant-content.js";
|
|
|
10
10
|
* original request would have. `assistantId` is the in-flight assistant
|
|
11
11
|
* message id to patch for streaming text; reconnect creates one if none. */
|
|
12
12
|
export function createChatSseEventApplier(deps) {
|
|
13
|
-
const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
|
|
13
|
+
const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setDeferredTurns = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, onTelemetryRefresh, setMessages, setToolCalls, setPendingQuestion = () => undefined, setTodos = () => undefined, } = deps;
|
|
14
|
+
let queueOwner;
|
|
15
|
+
let queueRevision = -1;
|
|
14
16
|
return (eventName, data, assistantId, options) => {
|
|
15
17
|
if (options?.generation !== undefined &&
|
|
16
18
|
refs.sessionGenerationRef.current !== options.generation)
|
|
@@ -42,6 +44,26 @@ export function createChatSseEventApplier(deps) {
|
|
|
42
44
|
const payload = data.data && typeof data.data === "object"
|
|
43
45
|
? data.data
|
|
44
46
|
: data;
|
|
47
|
+
if (eventName === "todo_updated" || eventName === "todo_cleared") {
|
|
48
|
+
const todos = Array.isArray(payload.todos) ? payload.todos : [];
|
|
49
|
+
setTodos(todos.filter((item) => Boolean(item) && typeof item === "object" && typeof item.content === "string" && (item.status === "pending" || item.status === "in_progress" || item.status === "completed")));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if ((eventName === "question_requested" ||
|
|
53
|
+
eventName === "question_resolved" ||
|
|
54
|
+
eventName === "question_cancelled") &&
|
|
55
|
+
payload.question &&
|
|
56
|
+
typeof payload.question === "object") {
|
|
57
|
+
const question = payload.question;
|
|
58
|
+
if (typeof question.requestId === "string" &&
|
|
59
|
+
typeof question.sessionId === "string" &&
|
|
60
|
+
question.sessionId === refs.sessionRef.current?.sessionId) {
|
|
61
|
+
setPendingQuestion(eventName === "question_requested" && question.status === "pending"
|
|
62
|
+
? question
|
|
63
|
+
: null);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
45
67
|
const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
|
|
46
68
|
const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
|
|
47
69
|
? data.seq
|
|
@@ -51,13 +73,20 @@ export function createChatSseEventApplier(deps) {
|
|
|
51
73
|
: Date.now();
|
|
52
74
|
if (eventName === "queue_update") {
|
|
53
75
|
const revision = typeof payload.revision === "number" ? payload.revision : -1;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
const owner = refs.sessionRef.current?.sessionId;
|
|
77
|
+
if (owner !== queueOwner) {
|
|
78
|
+
queueOwner = owner;
|
|
79
|
+
queueRevision = -1;
|
|
80
|
+
}
|
|
81
|
+
if (revision < queueRevision)
|
|
82
|
+
return;
|
|
83
|
+
queueRevision = revision;
|
|
84
|
+
setDeferredTurn(payload.deferredTurn && typeof payload.deferredTurn === "object"
|
|
85
|
+
? payload.deferredTurn
|
|
86
|
+
: null);
|
|
87
|
+
if (Array.isArray(payload.deferredTurns)) {
|
|
88
|
+
setDeferredTurns(payload.deferredTurns);
|
|
89
|
+
}
|
|
61
90
|
return;
|
|
62
91
|
}
|
|
63
92
|
if (eventName === "user_message") {
|
|
@@ -84,6 +113,40 @@ export function createChatSseEventApplier(deps) {
|
|
|
84
113
|
}
|
|
85
114
|
return;
|
|
86
115
|
}
|
|
116
|
+
if (eventName === "compact") {
|
|
117
|
+
const summary = typeof payload.summary === "string" ? payload.summary : "";
|
|
118
|
+
const tokensBefore = typeof payload.tokensBefore === "number" && Number.isFinite(payload.tokensBefore)
|
|
119
|
+
? Math.max(0, Math.floor(payload.tokensBefore))
|
|
120
|
+
: undefined;
|
|
121
|
+
const firstKeptEntryId = typeof payload.firstKeptEntryId === "string" && payload.firstKeptEntryId
|
|
122
|
+
? payload.firstKeptEntryId
|
|
123
|
+
: undefined;
|
|
124
|
+
const stableId = typeof data.eventId === "string" && data.eventId
|
|
125
|
+
? `compaction-${data.eventId}`
|
|
126
|
+
: turnId && eventSeq !== undefined
|
|
127
|
+
? `compaction-${turnId}-${eventSeq}`
|
|
128
|
+
: requestId("compaction");
|
|
129
|
+
setMessages((messages) => {
|
|
130
|
+
if (messages.some((message) => message.id === stableId))
|
|
131
|
+
return messages;
|
|
132
|
+
return [
|
|
133
|
+
...messages,
|
|
134
|
+
{
|
|
135
|
+
id: stableId,
|
|
136
|
+
role: "assistant",
|
|
137
|
+
kind: "compaction",
|
|
138
|
+
text: summary || "历史上下文已精简,关键决策与当前任务已保留。",
|
|
139
|
+
createdAt: eventAt,
|
|
140
|
+
compaction: {
|
|
141
|
+
...(tokensBefore === undefined ? {} : { tokensBefore }),
|
|
142
|
+
...(firstKeptEntryId ? { firstKeptEntryId } : {}),
|
|
143
|
+
...(typeof data.eventId === "string" ? { eventId: data.eventId } : {}),
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
87
150
|
if (turnId &&
|
|
88
151
|
(eventName === "agent_start" || eventName === "message_update") &&
|
|
89
152
|
refs.settledTurnIdsRef.current.has(turnId)) {
|
|
@@ -236,10 +299,21 @@ export function createChatSseEventApplier(deps) {
|
|
|
236
299
|
return true;
|
|
237
300
|
return assistantOwnsTools;
|
|
238
301
|
}));
|
|
302
|
+
if (!replay)
|
|
303
|
+
onTelemetryRefresh?.();
|
|
239
304
|
return;
|
|
240
305
|
}
|
|
241
306
|
if (eventName === "operation-ref" || eventName === "operation-status") {
|
|
242
307
|
const operation = operationFromEventPayload(payload);
|
|
308
|
+
// R9: tombstones are session-scoped. The same operation may be linked
|
|
309
|
+
// to another session, but delayed frames cannot resurrect it here.
|
|
310
|
+
const currentSessionId = refs.sessionRef.current?.sessionId;
|
|
311
|
+
if (operation &&
|
|
312
|
+
currentSessionId &&
|
|
313
|
+
refs.unlinkedOperationIdsRef.current
|
|
314
|
+
.get(currentSessionId)
|
|
315
|
+
?.has(operation.operationId))
|
|
316
|
+
return;
|
|
243
317
|
if (operation) {
|
|
244
318
|
const toolCallId = typeof payload.toolCallId === "string"
|
|
245
319
|
? payload.toolCallId
|
|
@@ -256,6 +330,23 @@ export function createChatSseEventApplier(deps) {
|
|
|
256
330
|
}
|
|
257
331
|
return;
|
|
258
332
|
}
|
|
333
|
+
if (eventName === "operation-unlinked") {
|
|
334
|
+
// R8 AC-001/AC-R8-001: durable tombstone — the server persisted an
|
|
335
|
+
// operation-unlinked event that OVERSHADOWS any earlier operation-ref.
|
|
336
|
+
// Drop the card from the projection; replay/recovery can never
|
|
337
|
+
// resurrect it because the tombstone is appended after the ref in
|
|
338
|
+
// the same ring.
|
|
339
|
+
const operationId = typeof payload.operationId === "string" ? payload.operationId : "";
|
|
340
|
+
const currentSessionId = refs.sessionRef.current?.sessionId;
|
|
341
|
+
if (operationId && currentSessionId) {
|
|
342
|
+
const sessionTombstones = refs.unlinkedOperationIdsRef.current.get(currentSessionId) ??
|
|
343
|
+
new Set();
|
|
344
|
+
sessionTombstones.add(operationId);
|
|
345
|
+
refs.unlinkedOperationIdsRef.current.set(currentSessionId, sessionTombstones);
|
|
346
|
+
setOperationCards((current) => current.filter((card) => card.operationId !== operationId));
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
259
350
|
if (eventName === "usage") {
|
|
260
351
|
if (replay)
|
|
261
352
|
return;
|
|
@@ -451,6 +542,47 @@ export function createChatSseEventApplier(deps) {
|
|
|
451
542
|
});
|
|
452
543
|
}
|
|
453
544
|
else if (eventName === "error") {
|
|
545
|
+
const candidate = payload.providerError && typeof payload.providerError === "object"
|
|
546
|
+
? payload.providerError
|
|
547
|
+
: undefined;
|
|
548
|
+
if (candidate &&
|
|
549
|
+
typeof candidate.message === "string" &&
|
|
550
|
+
typeof candidate.raw === "string" &&
|
|
551
|
+
(candidate.category === "authentication" ||
|
|
552
|
+
candidate.category === "context-overflow" ||
|
|
553
|
+
candidate.category === "rate-limit" ||
|
|
554
|
+
candidate.category === "provider")) {
|
|
555
|
+
// Session hydration already restores the durable card; retained replay
|
|
556
|
+
// must not add a second synthetic row on top of it.
|
|
557
|
+
if (replay)
|
|
558
|
+
return;
|
|
559
|
+
const providerError = candidate;
|
|
560
|
+
const errorContent = parseAssistantContent(payload.content);
|
|
561
|
+
const id = turnId && eventSeq !== undefined
|
|
562
|
+
? `provider-error-${turnId}-${eventSeq}`
|
|
563
|
+
: requestId("provider-error");
|
|
564
|
+
setMessages((messages) => {
|
|
565
|
+
if (messages.some((message) => message.id === id))
|
|
566
|
+
return messages;
|
|
567
|
+
const activeAlreadyOwnsContent = messages.some((message) => message.id === resolvedAssistantId &&
|
|
568
|
+
Boolean(message.content?.length));
|
|
569
|
+
return [
|
|
570
|
+
...messages,
|
|
571
|
+
{
|
|
572
|
+
id,
|
|
573
|
+
role: "assistant",
|
|
574
|
+
kind: "provider-error",
|
|
575
|
+
text: providerError.message,
|
|
576
|
+
createdAt: eventAt,
|
|
577
|
+
providerError,
|
|
578
|
+
...(!activeAlreadyOwnsContent && errorContent
|
|
579
|
+
? { content: errorContent }
|
|
580
|
+
: {}),
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
454
586
|
setError(typeof payload.message === "string" ? payload.message : "chat error");
|
|
455
587
|
}
|
|
456
588
|
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
function resolveDagId(id) {
|
|
2
|
+
const value = typeof id === "function" ? id() : id;
|
|
3
|
+
return value?.trim() || null;
|
|
4
|
+
}
|
|
5
|
+
export function createDetailsDagActions(params) {
|
|
6
|
+
const fetchImpl = params.fetchFn ?? fetch;
|
|
7
|
+
let pending = false;
|
|
8
|
+
let error = null;
|
|
9
|
+
const completionListeners = new Set();
|
|
10
|
+
const emitState = () => params.onStateChange?.({ pending, error });
|
|
11
|
+
const emitCompletion = (outcome, identity, message) => {
|
|
12
|
+
for (const listener of [...completionListeners]) {
|
|
13
|
+
try {
|
|
14
|
+
listener(outcome, identity, message);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// a listener throwing must never break the request path
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const run = (action, issue) => {
|
|
22
|
+
const sessionId = resolveDagId(params.sessionId);
|
|
23
|
+
const operationId = resolveDagId(params.operationId);
|
|
24
|
+
if (!sessionId || !operationId || pending)
|
|
25
|
+
return false;
|
|
26
|
+
const identity = {
|
|
27
|
+
action,
|
|
28
|
+
workspaceId: resolveDagId(params.workspaceId ?? null),
|
|
29
|
+
sessionId,
|
|
30
|
+
operationId,
|
|
31
|
+
};
|
|
32
|
+
pending = true;
|
|
33
|
+
error = null;
|
|
34
|
+
emitState();
|
|
35
|
+
void (async () => {
|
|
36
|
+
try {
|
|
37
|
+
const result = await issue(identity);
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
error = result.message ?? `DAG 关联操作失败`;
|
|
40
|
+
params.onFailure?.(identity, error);
|
|
41
|
+
emitCompletion("failure", identity, error);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
params.onSuccess?.(identity);
|
|
45
|
+
emitCompletion("success", identity);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
error = err instanceof Error ? err.message : String(err);
|
|
49
|
+
params.onFailure?.(identity, error);
|
|
50
|
+
emitCompletion("failure", identity, error);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
pending = false;
|
|
54
|
+
emitState();
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
get pending() {
|
|
61
|
+
return pending;
|
|
62
|
+
},
|
|
63
|
+
get error() {
|
|
64
|
+
return error;
|
|
65
|
+
},
|
|
66
|
+
subscribe(listener) {
|
|
67
|
+
completionListeners.add(listener);
|
|
68
|
+
return () => completionListeners.delete(listener);
|
|
69
|
+
},
|
|
70
|
+
onAddDag() {
|
|
71
|
+
return run("add", async (identity) => {
|
|
72
|
+
// AC-002: session-scoped mutations carry the session's durable
|
|
73
|
+
// owning workspace so a global active-workspace switch can never
|
|
74
|
+
// retarget the link to another workspace's store.
|
|
75
|
+
const ownerQuery = identity.workspaceId
|
|
76
|
+
? `?workspaceId=${encodeURIComponent(identity.workspaceId)}`
|
|
77
|
+
: "";
|
|
78
|
+
const res = await fetchImpl(`/api/operator/v1/chat/sessions/${encodeURIComponent(identity.sessionId)}/operation-links${ownerQuery}`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
credentials: "same-origin",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"x-loop-console-confirmation": params.confirmationToken(),
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ operationId: identity.operationId }),
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
const body = (await res.json().catch(() => ({})));
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
message: body.error?.message ??
|
|
92
|
+
`添加 DAG 关联失败(HTTP ${res.status})`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { ok: true };
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
onRemoveDag() {
|
|
99
|
+
return run("remove", async (identity) => {
|
|
100
|
+
const ownerQuery = identity.workspaceId
|
|
101
|
+
? `?workspaceId=${encodeURIComponent(identity.workspaceId)}`
|
|
102
|
+
: "";
|
|
103
|
+
const res = await fetchImpl(`/api/operator/v1/chat/sessions/${encodeURIComponent(identity.sessionId)}/operation-links/${encodeURIComponent(identity.operationId)}${ownerQuery}`, {
|
|
104
|
+
method: "DELETE",
|
|
105
|
+
credentials: "same-origin",
|
|
106
|
+
headers: {
|
|
107
|
+
"x-loop-console-confirmation": params.confirmationToken(),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok && res.status !== 404) {
|
|
111
|
+
const body = (await res.json().catch(() => ({})));
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
message: body.error?.message ??
|
|
115
|
+
`移除 DAG 关联失败(HTTP ${res.status})`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return { ok: true };
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -24,6 +24,7 @@ export function useChatRefs() {
|
|
|
24
24
|
const abortPostedTurnIdsRef = useRef(new Set());
|
|
25
25
|
const releasedClientRequestIdsRef = useRef(new Set());
|
|
26
26
|
const usageResponseKeysRef = useRef(new Set());
|
|
27
|
+
const unlinkedOperationIdsRef = useRef(new Map());
|
|
27
28
|
const isComposingRef = useRef(false);
|
|
28
29
|
const compositionEndedAtRef = useRef(0);
|
|
29
30
|
// Keep the bag identity stable: individual refs never change, so domain
|
|
@@ -53,6 +54,7 @@ export function useChatRefs() {
|
|
|
53
54
|
abortPostedTurnIdsRef,
|
|
54
55
|
releasedClientRequestIdsRef,
|
|
55
56
|
usageResponseKeysRef,
|
|
57
|
+
unlinkedOperationIdsRef,
|
|
56
58
|
isComposingRef,
|
|
57
59
|
compositionEndedAtRef,
|
|
58
60
|
}), [
|
|
@@ -80,6 +82,7 @@ export function useChatRefs() {
|
|
|
80
82
|
abortPostedTurnIdsRef,
|
|
81
83
|
releasedClientRequestIdsRef,
|
|
82
84
|
usageResponseKeysRef,
|
|
85
|
+
unlinkedOperationIdsRef,
|
|
83
86
|
isComposingRef,
|
|
84
87
|
compositionEndedAtRef,
|
|
85
88
|
]);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { formatModelRef } from "../../chat/runtime-selection.js";
|
|
2
|
+
const THINKING_SHORT_LABELS = {
|
|
3
|
+
auto: "Auto",
|
|
4
|
+
off: "Off",
|
|
5
|
+
minimal: "Minimal",
|
|
6
|
+
low: "Low",
|
|
7
|
+
medium: "Medium",
|
|
8
|
+
high: "High",
|
|
9
|
+
xhigh: "XHigh",
|
|
10
|
+
max: "Max",
|
|
11
|
+
};
|
|
12
|
+
export function formatThinkingSelectionLabel(selection, effective) {
|
|
13
|
+
const normalized = selection || "auto";
|
|
14
|
+
const label = THINKING_SHORT_LABELS[normalized] ?? normalized;
|
|
15
|
+
if (normalized !== "auto" || !effective)
|
|
16
|
+
return label;
|
|
17
|
+
return `${label} · ${THINKING_SHORT_LABELS[effective] ?? effective}`;
|
|
18
|
+
}
|
|
19
|
+
export function resolveModelDisplayName(models, activeModelRef) {
|
|
20
|
+
if (!activeModelRef)
|
|
21
|
+
return "选择模型";
|
|
22
|
+
const current = models.find((model) => formatModelRef(model.provider, model.id) === activeModelRef);
|
|
23
|
+
if (current)
|
|
24
|
+
return current.name?.trim() || current.id;
|
|
25
|
+
const slash = activeModelRef.indexOf("/");
|
|
26
|
+
return slash === -1 ? activeModelRef : activeModelRef.slice(slash + 1);
|
|
27
|
+
}
|
|
@@ -40,8 +40,9 @@ export function reconcileDeferredSubmissionFromState(input) {
|
|
|
40
40
|
const { clientRequestId, blockedByTurnId, state } = input;
|
|
41
41
|
if (!state)
|
|
42
42
|
return { action: "hold" };
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const queued = state.deferredTurns?.find((item) => item.clientRequestId === clientRequestId) ?? (state.deferredTurn?.clientRequestId === clientRequestId ? state.deferredTurn : undefined);
|
|
44
|
+
if (queued) {
|
|
45
|
+
return { action: "deferred", deferredTurn: queued };
|
|
45
46
|
}
|
|
46
47
|
if (state.activeTurn?.clientRequestId === clientRequestId) {
|
|
47
48
|
return { action: "accepted", turn: state.activeTurn };
|