@tea-agent/loop-agent 0.40.0-next.9 → 0.40.0
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 +21 -21
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/pi-sdk-executor.js +33 -5
- package/dist/executors/shell-executor.js +94 -20
- package/dist/governance/checks.js +1 -0
- 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 +495 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -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 +42 -5
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +20 -5
- 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 +179 -0
- package/dist/worker/console/routes.js +246 -6
- package/dist/worker/console/server.js +173 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
- package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
- package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
- package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-nniBEGv2.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 +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- 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/static-src/shell/workspace-route.js +3 -1
- 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 +7 -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/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +5 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +5 -4
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/backend-test-dag.json +6 -5
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +15 -1
- 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
|
|
@@ -261,6 +264,7 @@ export function useChatSessions(params) {
|
|
|
261
264
|
toolCalls: hydrated.toolCalls,
|
|
262
265
|
sessionStats: body.sessionStats,
|
|
263
266
|
usage: body.usage,
|
|
267
|
+
turnTelemetry: body.turnTelemetry,
|
|
264
268
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
265
269
|
turnPhase: body.session.turnPhase ??
|
|
266
270
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -268,6 +272,7 @@ export function useChatSessions(params) {
|
|
|
268
272
|
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
269
273
|
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
270
274
|
todos: body.session.todos ?? [],
|
|
275
|
+
goal: body.session.goal ?? null,
|
|
271
276
|
});
|
|
272
277
|
rememberActiveChatSession(window.localStorage, body.session.workspaceId ?? ownerWorkspaceId ?? workspaceId, sessionId);
|
|
273
278
|
setActivePreviewPath(null);
|
|
@@ -391,6 +396,7 @@ export function useChatSessions(params) {
|
|
|
391
396
|
toolCalls: hydrated.toolCalls,
|
|
392
397
|
sessionStats: body.sessionStats,
|
|
393
398
|
usage: body.usage,
|
|
399
|
+
turnTelemetry: body.turnTelemetry,
|
|
394
400
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
395
401
|
turnPhase: body.session.turnPhase ??
|
|
396
402
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -398,6 +404,7 @@ export function useChatSessions(params) {
|
|
|
398
404
|
deferredTurns: body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []),
|
|
399
405
|
pendingQuestion: body.session.pendingQuestion ?? null,
|
|
400
406
|
todos: body.session.todos ?? [],
|
|
407
|
+
goal: body.session.goal ?? null,
|
|
401
408
|
});
|
|
402
409
|
}
|
|
403
410
|
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") } : {}),
|
|
@@ -125,12 +125,14 @@ export function useChatThread(params) {
|
|
|
125
125
|
const [contextLoading, setContextLoading] = useState(false);
|
|
126
126
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
127
127
|
const [sessionStats, setSessionStats] = useState(EMPTY_SESSION_STATS);
|
|
128
|
+
const [turnTelemetry, setTurnTelemetry] = useState([]);
|
|
128
129
|
const [streaming, setStreaming] = useState(false);
|
|
129
130
|
const [turnPhase, setTurnPhase] = useState("idle");
|
|
130
131
|
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
131
132
|
const [deferredTurns, setDeferredTurns] = useState([]);
|
|
132
133
|
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
133
134
|
const [todos, setTodos] = useState([]);
|
|
135
|
+
const [goal, setGoal] = useState(null);
|
|
134
136
|
/** Assistant bubble id for the in-flight turn; only this row shows the streaming cursor. */
|
|
135
137
|
const [streamingAssistantId, setStreamingAssistantId] = useState(null);
|
|
136
138
|
const [showSystemInMessages] = useState(false);
|
|
@@ -149,6 +151,7 @@ export function useChatThread(params) {
|
|
|
149
151
|
setSessionStats(body.sessionStats);
|
|
150
152
|
if (body.usage)
|
|
151
153
|
setUsage(body.usage);
|
|
154
|
+
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
152
155
|
}
|
|
153
156
|
catch {
|
|
154
157
|
// Telemetry is supplementary; the normal event stream remains authoritative.
|
|
@@ -169,6 +172,7 @@ export function useChatThread(params) {
|
|
|
169
172
|
setDeferredTurns,
|
|
170
173
|
setPendingQuestion,
|
|
171
174
|
setTodos,
|
|
175
|
+
setGoal,
|
|
172
176
|
setStreamingAssistantId,
|
|
173
177
|
setUnread,
|
|
174
178
|
setOperationCards,
|
|
@@ -331,12 +335,14 @@ export function useChatThread(params) {
|
|
|
331
335
|
setTaskContext(null);
|
|
332
336
|
setUsage(EMPTY_USAGE);
|
|
333
337
|
setSessionStats(EMPTY_SESSION_STATS);
|
|
338
|
+
setTurnTelemetry([]);
|
|
334
339
|
setStreaming(false);
|
|
335
340
|
setTurnPhase("idle");
|
|
336
341
|
setDeferredTurn(null);
|
|
337
342
|
setDeferredTurns([]);
|
|
338
343
|
setPendingQuestion(null);
|
|
339
344
|
setTodos([]);
|
|
345
|
+
setGoal(null);
|
|
340
346
|
setStreamingAssistantId(null);
|
|
341
347
|
setUnread(0);
|
|
342
348
|
setHistoryLimit(50);
|
|
@@ -354,6 +360,7 @@ export function useChatThread(params) {
|
|
|
354
360
|
setInterview(null);
|
|
355
361
|
setUsage(next.usage ?? EMPTY_USAGE);
|
|
356
362
|
setSessionStats(next.sessionStats ?? EMPTY_SESSION_STATS);
|
|
363
|
+
setTurnTelemetry(next.turnTelemetry ?? []);
|
|
357
364
|
refs.usageResponseKeysRef.current = new Set();
|
|
358
365
|
setStreaming(visuallyStopped ? false : next.streaming);
|
|
359
366
|
setTurnPhase(visuallyStopped
|
|
@@ -363,6 +370,7 @@ export function useChatThread(params) {
|
|
|
363
370
|
setDeferredTurns(next.deferredTurns ?? (next.deferredTurn ? [next.deferredTurn] : []));
|
|
364
371
|
setPendingQuestion(next.pendingQuestion ?? null);
|
|
365
372
|
setTodos(next.todos ?? []);
|
|
373
|
+
setGoal(next.goal ?? null);
|
|
366
374
|
setStreamingAssistantId(null);
|
|
367
375
|
setUnread(0);
|
|
368
376
|
setHistoryLimit(50);
|
|
@@ -393,10 +401,12 @@ export function useChatThread(params) {
|
|
|
393
401
|
setDeferredTurns(body.session.deferredTurns ?? (body.session.deferredTurn ? [body.session.deferredTurn] : []));
|
|
394
402
|
setPendingQuestion(body.session.pendingQuestion ?? null);
|
|
395
403
|
setTodos(body.session.todos ?? []);
|
|
404
|
+
setGoal(body.session.goal ?? null);
|
|
396
405
|
if (body.sessionStats)
|
|
397
406
|
setSessionStats(body.sessionStats);
|
|
398
407
|
if (body.usage)
|
|
399
408
|
setUsage(body.usage);
|
|
409
|
+
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
400
410
|
// Resume the events stream from the snapshot tail so the next
|
|
401
411
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
402
412
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -444,6 +454,7 @@ export function useChatThread(params) {
|
|
|
444
454
|
contextLoading,
|
|
445
455
|
usage,
|
|
446
456
|
sessionStats,
|
|
457
|
+
turnTelemetry,
|
|
447
458
|
streaming,
|
|
448
459
|
setStreaming,
|
|
449
460
|
turnPhase,
|
|
@@ -455,6 +466,8 @@ export function useChatThread(params) {
|
|
|
455
466
|
pendingQuestion,
|
|
456
467
|
setPendingQuestion,
|
|
457
468
|
todos,
|
|
469
|
+
goal,
|
|
470
|
+
setGoal,
|
|
458
471
|
streamingAssistantId,
|
|
459
472
|
setStreamingAssistantId,
|
|
460
473
|
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;
|
|
@@ -22,6 +22,8 @@ export function useRuntimeControls(params) {
|
|
|
22
22
|
const [thinkingLevels, setThinkingLevels] = useState([]);
|
|
23
23
|
const [runtimeContext, setRuntimeContext] = useState(null);
|
|
24
24
|
const [runtimeContextError, setRuntimeContextError] = useState(null);
|
|
25
|
+
/** 注入区加载态:首次打开会话到 runtime-context 返回前显示占位(服务端需冷启动 Pi 会话)。 */
|
|
26
|
+
const [runtimeContextLoading, setRuntimeContextLoading] = useState(false);
|
|
25
27
|
const [switching, setSwitching] = useState(false);
|
|
26
28
|
const [toolsOpen, setToolsOpen] = useState(false);
|
|
27
29
|
const [runtimeContextOpen, setRuntimeContextOpen] = useState(false);
|
|
@@ -41,7 +43,14 @@ export function useRuntimeControls(params) {
|
|
|
41
43
|
const snapshot = snapshotState.snapshot;
|
|
42
44
|
const loadCapabilities = useCallback(async () => {
|
|
43
45
|
try {
|
|
44
|
-
|
|
46
|
+
// Workspace-scoped: the server rejects a bare capabilities call with
|
|
47
|
+
// WORKSPACE_REQUIRED in multi-workspace consoles, which would silently
|
|
48
|
+
// null the tool catalog (trajectory injection section included).
|
|
49
|
+
const scope = session?.workspaceId ?? params.workspaceId ?? undefined;
|
|
50
|
+
const query = scope
|
|
51
|
+
? `?workspaceId=${encodeURIComponent(scope)}`
|
|
52
|
+
: "";
|
|
53
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/capabilities${query}`);
|
|
45
54
|
if (!res.ok)
|
|
46
55
|
return;
|
|
47
56
|
const body = (await res.json());
|
|
@@ -50,7 +59,7 @@ export function useRuntimeControls(params) {
|
|
|
50
59
|
catch {
|
|
51
60
|
// capabilities optional; Chat still works without them
|
|
52
61
|
}
|
|
53
|
-
}, [origin]);
|
|
62
|
+
}, [origin, session?.workspaceId, params.workspaceId]);
|
|
54
63
|
useEffect(() => {
|
|
55
64
|
void loadCapabilities();
|
|
56
65
|
}, [loadCapabilities]);
|
|
@@ -98,19 +107,29 @@ export function useRuntimeControls(params) {
|
|
|
98
107
|
if (!session) {
|
|
99
108
|
setRuntimeContext(null);
|
|
100
109
|
setRuntimeContextError(null);
|
|
110
|
+
setRuntimeContextLoading(false);
|
|
101
111
|
return;
|
|
102
112
|
}
|
|
103
113
|
const sessionId = session.sessionId;
|
|
104
114
|
const base = `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`;
|
|
105
115
|
setRuntimeContextError(null);
|
|
116
|
+
// 首次打开历史会话时服务端需冷启动 Pi 会话才能投影系统提示词,
|
|
117
|
+
// 加载期间注入区显示占位而不是空缺。
|
|
118
|
+
setRuntimeContext((current) => (current === null ? current : current));
|
|
119
|
+
setRuntimeContextLoading(true);
|
|
106
120
|
void fetch(`${base}/runtime-context`, { credentials: "include" })
|
|
107
121
|
.then(async (r) => {
|
|
108
122
|
const body = await r.json().catch(() => null);
|
|
109
123
|
// Stale-response guard: a slow runtime-context reply for session A
|
|
110
124
|
// must not overwrite the context/session state of the now-current
|
|
111
125
|
// session B.
|
|
112
|
-
if (
|
|
126
|
+
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
127
|
+
return;
|
|
128
|
+
if (!body?.context) {
|
|
129
|
+
if (!r.ok)
|
|
130
|
+
setRuntimeContextError("注入上下文读取失败,请重试");
|
|
113
131
|
return;
|
|
132
|
+
}
|
|
114
133
|
// RF5: the response body NEVER writes model/thinkingLevel — it
|
|
115
134
|
// carries no revision semantics, so a late reply for the same
|
|
116
135
|
// session could hold a stale selection. Selection is derived only
|
|
@@ -128,6 +147,11 @@ export function useRuntimeControls(params) {
|
|
|
128
147
|
const ctx = body.context;
|
|
129
148
|
const adoptedSnapshot = getRuntimeSnapshotState().snapshot;
|
|
130
149
|
setRuntimeContext((current) => mergeRuntimeContextWithSnapshot(current, ctx, adoptedSnapshot, sessionId));
|
|
150
|
+
})
|
|
151
|
+
.finally(() => {
|
|
152
|
+
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
153
|
+
return;
|
|
154
|
+
setRuntimeContextLoading(false);
|
|
131
155
|
})
|
|
132
156
|
.catch(() => {
|
|
133
157
|
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
@@ -336,6 +360,7 @@ export function useRuntimeControls(params) {
|
|
|
336
360
|
thinkingLevels: effectiveThinkingLevels,
|
|
337
361
|
runtimeContext,
|
|
338
362
|
runtimeContextError,
|
|
363
|
+
runtimeContextLoading,
|
|
339
364
|
onRetryRuntimeContext: () => {
|
|
340
365
|
setRuntimeContextError(null);
|
|
341
366
|
void refreshRuntimeSnapshot();
|
|
@@ -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
|
+
}
|