@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.
Files changed (143) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +21 -21
  3. package/README.md +2 -2
  4. package/dist/application/dag/generate-task-dag.js +6 -2
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/command-definitions.js +9 -5
  7. package/dist/cli/program.js +38 -3
  8. package/dist/commands/client-recovery.js +8 -36
  9. package/dist/commands/init-model-catalog.js +22 -0
  10. package/dist/commands/task-advance.js +23 -1
  11. package/dist/commands/task-source-prepare.js +5 -0
  12. package/dist/commands/task-status.js +1 -0
  13. package/dist/executors/pi-sdk-executor.js +33 -5
  14. package/dist/executors/shell-executor.js +94 -20
  15. package/dist/governance/checks.js +1 -0
  16. package/dist/shared/operator/capabilities.js +9 -1
  17. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  18. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  19. package/dist/shared/pi-context-pressure/env.js +58 -0
  20. package/dist/shared/pi-context-pressure/extension.js +100 -0
  21. package/dist/shared/pi-context-pressure/index.js +7 -0
  22. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  23. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  24. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  25. package/dist/worker/console/chat/assistant-content.js +7 -0
  26. package/dist/worker/console/chat/chat-event-store.js +82 -11
  27. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  28. package/dist/worker/console/chat/goals.js +300 -0
  29. package/dist/worker/console/chat/pi-runtime.js +285 -8
  30. package/dist/worker/console/chat/provider-error.js +2 -1
  31. package/dist/worker/console/chat/repo-browser.js +94 -12
  32. package/dist/worker/console/chat/routes.js +495 -30
  33. package/dist/worker/console/chat/session-catalog.js +48 -0
  34. package/dist/worker/console/chat/session-stats.js +96 -0
  35. package/dist/worker/console/chat/session-store.js +27 -18
  36. package/dist/worker/console/chat/shortcuts.js +7 -0
  37. package/dist/worker/console/chat/sift-bridge.js +1 -0
  38. package/dist/worker/console/chat/turn-process.js +42 -5
  39. package/dist/worker/console/dag-execution-receipt.js +20 -2
  40. package/dist/worker/console/operator-actions.js +20 -5
  41. package/dist/worker/console/operator-selection.js +6 -2
  42. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  43. package/dist/worker/console/pi-plugins.js +179 -0
  44. package/dist/worker/console/routes.js +246 -6
  45. package/dist/worker/console/server.js +173 -42
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-M25fNMTE.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
  78. package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
  79. package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-CG8D9fuL.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-BuB6P2rX.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-nniBEGv2.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  105. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  106. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  107. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  108. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  109. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  110. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  111. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  112. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  113. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  114. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  115. package/dist/worker/console/workspace-context.js +28 -3
  116. package/dist/worker/console/workspace-registry.js +333 -60
  117. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  118. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  119. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  120. package/dist/worker/observe/static/operator-chrome.css +23 -4
  121. package/dist/worker/observe/static/operator-chrome.js +7 -2
  122. package/dist/worker/observe/static/styles.css +87 -25
  123. package/dist/worker/observe/static/views/dag-inspector.js +42 -1
  124. package/dist/worker/observe/static/views/dags.js +7 -5
  125. package/dist/worker/observe/static/views/session-timeline.js +135 -0
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  127. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  128. package/dist/workflows/dag/backend-test-result-contract.js +5 -1
  129. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  130. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  131. package/dist/workflows/dag/init-hybrid.js +5 -4
  132. package/docs/architecture/runtime-boundaries.md +2 -1
  133. package/docs/templates/backend-test-dag.json +6 -5
  134. package/docs/templates/init-managed-agents.md +1 -1
  135. package/package.json +3 -2
  136. package/skills/loop-agent/references/command-reference.md +15 -1
  137. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  138. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  139. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  140. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  141. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  142. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  143. 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
- ].slice(0, CHAT_SESSION_LIST_LIMIT);
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 session's DURABLE
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
- const res = await fetch(`${origin}/api/operator/v1/chat/sessions${workspaceQuery}`, {
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
- if (!res.ok)
156
+ const body = (await res.json().catch(() => ({})));
157
+ if (!res.ok) {
158
+ setError(body.error?.message ?? `会话列表加载失败(HTTP ${res.status})`);
154
159
  return;
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;
162
- // API returns newest-first; keep only the recent window in the sidebar.
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
- /* Chat remains usable without the sidebar index. */
169
+ catch (error) {
170
+ setError(`会话列表加载失败:${error instanceof Error ? error.message : String(error)}`);
168
171
  }
169
- }, [origin, workspaceQuery, workspaceId, blocked]);
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: `/inspect/?operationId=${encodeURIComponent(operationId)}#/`,
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 !== body.data.path),
83
- body.data,
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(body.data.path);
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
- return (await res.json()).data;
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
- const res = await fetch(`${origin}/api/operator/v1/chat/capabilities`);
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 (!body?.context || refs.sessionRef.current?.sessionId !== sessionId)
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
+ }