@tea-agent/loop-agent 0.44.0-next.6 → 0.44.0-next.8

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 (105) hide show
  1. package/CHANGELOG.md +288 -454
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/dag-pi/guards.js +4 -0
  4. package/dist/executors/dag-pi/model-config.js +30 -0
  5. package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +25 -41
  6. package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
  7. package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
  8. package/dist/executors/dag-pi/tools/design-terminal-tools.js +156 -0
  9. package/dist/executors/dag-pi/tools/plan-decision-tools.js +519 -0
  10. package/dist/executors/dag-pi/tools/review-terminal-tools.js +156 -0
  11. package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
  12. package/dist/executors/dag-pi-executor.js +64 -1907
  13. package/dist/shared/git-workspace-status.js +2 -0
  14. package/dist/task/frontend-preflight.js +1 -1
  15. package/dist/task/source-prepare/semantic-intake.js +1 -1
  16. package/dist/task/source-prepare/source-fidelity-pi.js +1 -1
  17. package/dist/worker/console/chat/assistant-content.js +5 -34
  18. package/dist/worker/console/chat/chat-event-store.js +27 -10
  19. package/dist/worker/console/chat/db-connections.js +72 -8
  20. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +52 -24
  21. package/dist/worker/console/chat/pi-runtime.js +34 -0
  22. package/dist/worker/console/chat/routes.js +138 -9
  23. package/dist/worker/console/chat/session-store.js +147 -19
  24. package/dist/worker/console/chat/turn-process.js +69 -23
  25. package/dist/worker/console/chat/user-turn-outline.js +19 -0
  26. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Brn2sPzL.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
  27. package/dist/worker/console/static/assets/{arc-BIMNTG0W.js → arc-dT5T4sVL.js} +1 -1
  28. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G--WtcgR_A.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
  29. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BT_Ztog1.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
  30. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5hbVr-l.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
  31. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
  32. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BKKnyR9Z.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BHs6biqA.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BdSFy_vP.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BpJIdTKV.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-C_iWJhkT.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-BTbmx3-k.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D4Ee6DmP.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B487SORR.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BKYHjNrr.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DrTdoYZG.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
  42. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
  43. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
  44. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CttUY_Jo.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
  45. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B4Rm5gnR.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
  46. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-ByxHBq_S.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
  47. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DGrfNFA0.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
  48. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-KjrUPquv.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
  49. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-VS4mlm75.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BLXYmFTM.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C6xoFAqH.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-UB23O5K3-C478dCe7.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
  53. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DHEpW5zN.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
  54. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CA5GKfo9.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
  55. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-zysi0Fl-.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
  56. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DGzcKA8Y.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
  57. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CHsuIG1w.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
  58. package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
  59. package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
  60. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BexZHPCp.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
  61. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Gkx8q-DB.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
  62. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-XDW3qrJ7.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
  63. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ07eEaN.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
  64. package/dist/worker/console/static/assets/{linear-DL-zeqOn.js → linear-DGRlieMH.js} +1 -1
  65. package/dist/worker/console/static/assets/{mermaid.core-C0HdXtOf.js → mermaid.core-4P9yPC9c.js} +5 -5
  66. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CqRX0lrt.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
  67. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVM9JZ2H.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
  68. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BA3ozdKk.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
  69. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B-0_p9_4.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
  70. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DHRzPWFc.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
  71. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-9BEC_PcW.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
  72. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DMxeW4rN.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
  73. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DixP6LIU.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
  74. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B_EifJBo.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
  75. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BwikwWbX.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
  76. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
  77. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-C162l9o1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
  78. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
  79. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-mpe_u1i4.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
  80. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DyUk54HX.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
  81. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-HYyMkzfm.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
  82. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DRoh7skR.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
  83. package/dist/worker/console/static/index.html +2 -2
  84. package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
  85. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +58 -3
  86. package/dist/worker/console/static-src/operator-chat/process-label.js +19 -6
  87. package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
  88. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -0
  89. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
  90. package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
  91. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
  92. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +202 -43
  93. package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -23
  94. package/dist/worker/console/static-src/operator-chat/useChatThread.js +55 -18
  95. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +5 -2
  96. package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
  97. package/dist/worker/scheduler/scheduled-goal-evidence.js +6 -1
  98. package/package.json +1 -1
  99. package/dist/worker/console/static/assets/channel-DNWB4hpR.js +0 -1
  100. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-COhFgJji.js +0 -1
  101. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-COhFgJji.js +0 -1
  102. package/dist/worker/console/static/assets/index-CJh6pVLm.js +0 -482
  103. package/dist/worker/console/static/assets/index-C_kvKmFM.css +0 -1
  104. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-S2ZHMo1c.js +0 -1
  105. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BZm4ByfV.js +0 -8
@@ -3,8 +3,50 @@ import { formatOperatorUserError } from "../../operator-user-error.js";
3
3
  import { inspectChatSseContinuity, readChatStreamReadyCursor, selectChatSseRepairBlocks, selectLaterChatEventId, } from "./chat-stream-continuity.js";
4
4
  import { confirmationToken, requestId, sleep } from "./format.js";
5
5
  import { classifyCreateTurnNetworkFailure, classifyCreateTurnResponse, createPendingSubmission, decideSubmissionRoute, isRuntimeBusyUnion, isSubmissionCurrent, isTerminalTurnState, keepAmbiguousAfterRetry, reconcileDeferredSubmissionFromState, reconcilePendingSubmissionFromState, resolveAmbiguousFromState, } from "./turn-submission.js";
6
- import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, turnPhaseFromOwnership, visualTurnPhaseFromOwnership, } from "./turn-stream-controller.js";
6
+ import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, createBoundedOwnerSignal, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, turnPhaseFromOwnership, visualTurnPhaseFromOwnership, } from "./turn-stream-controller.js";
7
7
  import { appendPendingSubmission, removePendingSubmission } from "./pending-user-message.js";
8
+ import { createTurnStateReconcileQueue, turnStateReconcileTrigger, } from "./turn-state-projection.js";
9
+ const STATE_REQUEST_TIMEOUT_MS = 2_000;
10
+ async function fetchReconciledSessionState(origin, sessionId, options) {
11
+ const bounded = createBoundedOwnerSignal({
12
+ ownerSignal: options?.ownerSignal,
13
+ timeoutMs: STATE_REQUEST_TIMEOUT_MS,
14
+ });
15
+ try {
16
+ const res = await fetch(operatorChatStateUrl(origin, sessionId, options?.turnIds), {
17
+ credentials: "include",
18
+ signal: bounded.signal,
19
+ });
20
+ const body = (await res.json().catch(() => ({})));
21
+ if (!res.ok)
22
+ return null;
23
+ return body.state ?? null;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ finally {
29
+ bounded.dispose();
30
+ }
31
+ }
32
+ function readChatSseTurnId(block) {
33
+ const data = block
34
+ .split("\n")
35
+ .filter((line) => line.startsWith("data: "))
36
+ .map((line) => line.slice(6))
37
+ .join("");
38
+ if (!data)
39
+ return undefined;
40
+ try {
41
+ const parsed = JSON.parse(data);
42
+ return typeof parsed.turnId === "string" && parsed.turnId
43
+ ? parsed.turnId
44
+ : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
8
50
  /** Project ChatRefs into the executable ownership bag used by the controller. */
9
51
  function ownershipBagFromRefs(refs) {
10
52
  return {
@@ -106,6 +148,17 @@ export function useChatStream(params) {
106
148
  * smooth (M4.1/M4.3). */
107
149
  const openEventsStream = useCallback(async (sessionId, generation, signal) => {
108
150
  const gapReplayBuffer = [];
151
+ const turnStateReconciles = createTurnStateReconcileQueue({
152
+ read: (turnIds) => fetchReconciledSessionState(origin, sessionId, {
153
+ turnIds,
154
+ ownerSignal: signal,
155
+ }),
156
+ apply: (summaries) => thread.reconcileTurnStates([...summaries]),
157
+ wait: (delayMs) => sleep(delayMs, signal),
158
+ isCancelled: () => signal.aborted ||
159
+ refs.sessionRef.current?.sessionId !== sessionId ||
160
+ refs.sessionGenerationRef.current !== generation,
161
+ });
109
162
  while (!signal.aborted) {
110
163
  const state = refs.eventsStreamStateRef.current;
111
164
  if (!state ||
@@ -145,6 +198,16 @@ export function useChatStream(params) {
145
198
  const block = blocks[blockIndex];
146
199
  if (block === undefined)
147
200
  continue;
201
+ const eventName = sseEventName(block);
202
+ const reconcileTrigger = turnStateReconcileTrigger(replay, eventName);
203
+ if (reconcileTrigger) {
204
+ const settledTurnId = readChatSseTurnId(block);
205
+ if (settledTurnId) {
206
+ turnStateReconciles.enqueue(settledTurnId, {
207
+ defer: reconcileTrigger === "after-replay",
208
+ });
209
+ }
210
+ }
148
211
  if (!replay) {
149
212
  const continuity = inspectChatSseContinuity(block, liveContinuityCursor);
150
213
  if (continuity.action === "reconnect") {
@@ -156,7 +219,7 @@ export function useChatStream(params) {
156
219
  break readLoop;
157
220
  }
158
221
  }
159
- if (sseEventName(block) === "stream_ready") {
222
+ if (eventName === "stream_ready") {
160
223
  const current = refs.eventsStreamStateRef.current;
161
224
  if (current?.sessionId === sessionId &&
162
225
  current.generation === generation)
@@ -179,7 +242,7 @@ export function useChatStream(params) {
179
242
  if (!replay)
180
243
  liveContinuityCursor = selectLaterChatEventId(liveContinuityCursor, eventId);
181
244
  }
182
- if (sseEventName(block) === "stream_ready" &&
245
+ if (eventName === "stream_ready" &&
183
246
  gapReplayBuffer.length > 0) {
184
247
  const repairedBlocks = selectChatSseRepairBlocks(gapReplayBuffer.splice(0), liveContinuityCursor);
185
248
  for (const repairedBlock of repairedBlocks) {
@@ -193,6 +256,9 @@ export function useChatStream(params) {
193
256
  refs.lastEventIdRef.current = selectLaterChatEventId(refs.lastEventIdRef.current, repairedEventId);
194
257
  }
195
258
  }
259
+ if (eventName === "stream_ready") {
260
+ void turnStateReconciles.start();
261
+ }
196
262
  }
197
263
  }
198
264
  if (replayRepairNeeded) {
@@ -213,7 +279,7 @@ export function useChatStream(params) {
213
279
  await sleep(2_000, signal);
214
280
  }
215
281
  }
216
- }, [origin, refs, applySseBlock]);
282
+ }, [origin, refs, applySseBlock, thread.reconcileTurnStates]);
217
283
  const startEventsStream = useCallback((sessionId, generation) => {
218
284
  const existing = refs.eventsStreamStateRef.current;
219
285
  if (existing?.sessionId === sessionId &&
@@ -249,20 +315,7 @@ export function useChatStream(params) {
249
315
  }
250
316
  throw new Error("事件连接未就绪,请检查 Console 连接后重试");
251
317
  }, [refs, startEventsStream]);
252
- const fetchSessionState = useCallback(async (sessionId) => {
253
- try {
254
- const res = await fetch(operatorChatStateUrl(origin, sessionId), {
255
- credentials: "include",
256
- });
257
- const body = (await res.json().catch(() => ({})));
258
- if (!res.ok)
259
- return null;
260
- return body.state ?? null;
261
- }
262
- catch {
263
- return null;
264
- }
265
- }, [origin]);
318
+ const fetchSessionState = useCallback((sessionId) => fetchReconciledSessionState(origin, sessionId), [origin]);
266
319
  const postCreateTurn = useCallback(async (input) => {
267
320
  try {
268
321
  // Resolve against the configured Console origin and fail closed if a
@@ -1042,20 +1095,20 @@ export function useChatStream(params) {
1042
1095
  const sessionId = session.sessionId;
1043
1096
  const sessionIdentity = { ...session };
1044
1097
  const generation = refs.sessionGenerationRef.current;
1098
+ const stateReconcileAbort = new AbortController();
1045
1099
  let cancelled = false;
1046
1100
  let syncInFlight = null;
1047
1101
  const runSync = async () => {
1048
1102
  try {
1049
- const res = await fetch(operatorChatStateUrl(origin, sessionId), {
1050
- credentials: "include",
1103
+ const state = await fetchReconciledSessionState(origin, sessionId, {
1104
+ ownerSignal: stateReconcileAbort.signal,
1051
1105
  });
1052
- const body = (await res.json().catch(() => ({})));
1053
1106
  if (cancelled ||
1054
- !res.ok ||
1107
+ !state ||
1055
1108
  refs.sessionRef.current?.sessionId !== sessionId ||
1056
1109
  refs.sessionGenerationRef.current !== generation)
1057
1110
  return;
1058
- const state = body.state;
1111
+ thread.reconcileTurnStates(state?.turnStates);
1059
1112
  const pending = refs.pendingSubmissionRef.current;
1060
1113
  // Pending ownership reconcile. Controller owns bind/release; same
1061
1114
  // clientRequestId only. Re-enter after acceptedTurnId so terminal
@@ -1200,6 +1253,7 @@ export function useChatStream(params) {
1200
1253
  void sync().finally(scheduleStopRetry);
1201
1254
  return () => {
1202
1255
  cancelled = true;
1256
+ stateReconcileAbort.abort();
1203
1257
  window.clearInterval(interval);
1204
1258
  if (stopRetryTimer !== undefined)
1205
1259
  window.clearTimeout(stopRetryTimer);
@@ -1216,6 +1270,7 @@ export function useChatStream(params) {
1216
1270
  markIdleIfCurrent,
1217
1271
  releaseSubmitOwnership,
1218
1272
  postAbortForTurn,
1273
+ thread.reconcileTurnStates,
1219
1274
  ]);
1220
1275
  // Exactly one owner starts/aborts GET /events. Switching sessions or
1221
1276
  // recovering a cursor aborts the prior stream before the next one is made.
@@ -9,6 +9,7 @@ import { taskContextUnchanged } from "./format.js";
9
9
  import { upsertCompactionMessage } from "./compaction-message.js";
10
10
  import { sameTurnGroupInputs } from "./turn-group-equality.js";
11
11
  import { conversationNodesFromEvents, } from "../../chat/conversation-nodes.js";
12
+ import { mergeTurnStateSummaries, setProjectedTurnState, } from "./turn-state-projection.js";
12
13
  const EMPTY_USAGE = {
13
14
  inputTokens: 0,
14
15
  outputTokens: 0,
@@ -141,8 +142,13 @@ export function useChatThread(params) {
141
142
  const [usage, setUsage] = useState(EMPTY_USAGE);
142
143
  const [sessionStats, setSessionStats] = useState(EMPTY_SESSION_STATS);
143
144
  const [turnTelemetry, setTurnTelemetry] = useState([]);
145
+ const [userTurnOutline, setUserTurnOutline] = useState([]);
144
146
  const [operatorActions, setOperatorActions] = useState([]);
145
147
  const [conversationNodes, setConversationNodes] = useState([]);
148
+ const [turnStatesById, setTurnStatesById] = useState({});
149
+ const reconcileTurnStates = useCallback((summaries) => {
150
+ setTurnStatesById((current) => mergeTurnStateSummaries(current, summaries));
151
+ }, []);
146
152
  const [sddSessionIdentity, setSddSessionIdentity] = useState(null);
147
153
  const [streaming, setStreaming] = useState(false);
148
154
  const [turnPhase, setTurnPhase] = useState("idle");
@@ -172,11 +178,12 @@ export function useChatThread(params) {
172
178
  setUsage(body.usage);
173
179
  setTurnTelemetry(body.turnTelemetry ?? []);
174
180
  setSddSessionIdentity(body.sddDataAlignment ?? null);
181
+ reconcileTurnStates(body.session?.turnStates);
175
182
  }
176
183
  catch {
177
184
  // Telemetry is supplementary; the normal event stream remains authoritative.
178
185
  }
179
- }, [origin, refs]);
186
+ }, [origin, refs, reconcileTurnStates]);
180
187
  // All inputs are stable (state setters + the memoized refs bag), so the
181
188
  // applier identity is stable across renders.
182
189
  const applyChatSseEvent = useMemo(() => createChatSseEventApplier({
@@ -195,6 +202,7 @@ export function useChatThread(params) {
195
202
  setGoal,
196
203
  setOperatorActions,
197
204
  setConversationNodes,
205
+ setTurnState: (turnId, state) => setTurnStatesById((current) => setProjectedTurnState(current, turnId, state)),
198
206
  setSddSessionIdentity,
199
207
  setStreamingAssistantId,
200
208
  setUnread,
@@ -334,6 +342,7 @@ export function useChatThread(params) {
334
342
  streaming,
335
343
  activeAssistantId: streamingAssistantId,
336
344
  conversationNodes,
345
+ turnStatesById,
337
346
  });
338
347
  // Preserve completed group objects across streaming tokens. Stable
339
348
  // historical groups keep their references so
@@ -353,11 +362,12 @@ export function useChatThread(params) {
353
362
  streaming,
354
363
  streamingAssistantId,
355
364
  conversationNodes,
365
+ turnStatesById,
356
366
  historyLimit,
357
367
  ]);
358
- const loadEarlierMessages = useCallback(async (beforePrepend) => {
368
+ const loadHistoryPages = useCallback(async (input) => {
359
369
  const targetSessionId = refs.sessionRef.current?.sessionId;
360
- const beforeId = historyNextCursor;
370
+ let beforeId = historyNextCursor;
361
371
  if (!targetSessionId || !historyHasMore || !beforeId || historyLoadingRef.current)
362
372
  return false;
363
373
  const generation = refs.sessionGenerationRef.current;
@@ -365,17 +375,29 @@ export function useChatThread(params) {
365
375
  historyLoadingRef.current = request;
366
376
  setHistoryLoading(true);
367
377
  try {
368
- const query = new URLSearchParams({ limit: "50", beforeId });
369
- const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}/messages?${query.toString()}`, { credentials: "include" });
370
- if (!res.ok || refs.sessionRef.current?.sessionId !== targetSessionId)
371
- return false;
372
- const body = (await res.json());
373
- if (refs.sessionRef.current?.sessionId !== targetSessionId ||
374
- refs.sessionGenerationRef.current !== generation ||
375
- historyLoadingRef.current !== request)
376
- return false;
377
- const page = hydrateSessionThread(body.messages ?? []);
378
- beforePrepend?.();
378
+ const collectedMessages = [];
379
+ const collectedTurnStates = [];
380
+ let hasMore = historyHasMore;
381
+ let found = false;
382
+ do {
383
+ const query = new URLSearchParams({ limit: "50", beforeId });
384
+ const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}/messages?${query.toString()}`, { credentials: "include" });
385
+ if (!res.ok || refs.sessionRef.current?.sessionId !== targetSessionId)
386
+ return false;
387
+ const body = (await res.json());
388
+ if (refs.sessionRef.current?.sessionId !== targetSessionId ||
389
+ refs.sessionGenerationRef.current !== generation ||
390
+ historyLoadingRef.current !== request)
391
+ return false;
392
+ const pageMessages = body.messages ?? [];
393
+ collectedMessages.unshift(...pageMessages);
394
+ collectedTurnStates.push(...(body.turnStates ?? []));
395
+ found = Boolean(input?.untilMessageId && pageMessages.some((message) => message.id === input.untilMessageId));
396
+ hasMore = body.hasMore === true;
397
+ beforeId = body.nextCursor ?? "";
398
+ } while (input?.untilMessageId && !found && hasMore && beforeId);
399
+ const page = hydrateSessionThread(collectedMessages);
400
+ input?.beforePrepend?.();
379
401
  setMessages((current) => {
380
402
  const seen = new Set();
381
403
  const merged = [...page.messages, ...current].filter((message) => {
@@ -392,10 +414,11 @@ export function useChatThread(params) {
392
414
  byId.set(tool.id, tool);
393
415
  return [...byId.values()].sort((a, b) => a.createdAt - b.createdAt);
394
416
  });
395
- setHistoryHasMore(body.hasMore === true);
396
- setHistoryNextCursor(body.nextCursor ?? null);
397
- setHistoryLimit((value) => value + 50);
398
- return true;
417
+ setTurnStatesById((current) => mergeTurnStateSummaries(current, collectedTurnStates));
418
+ setHistoryHasMore(hasMore);
419
+ setHistoryNextCursor(beforeId || null);
420
+ setHistoryLimit((value) => value + collectedMessages.length);
421
+ return input?.untilMessageId ? found : true;
399
422
  }
400
423
  catch (error) {
401
424
  if (refs.sessionRef.current?.sessionId === targetSessionId && refs.sessionGenerationRef.current === generation) {
@@ -410,6 +433,10 @@ export function useChatThread(params) {
410
433
  }
411
434
  }
412
435
  }, [historyHasMore, historyNextCursor, origin, refs, setError]);
436
+ const loadEarlierMessages = useCallback((beforePrepend) => loadHistoryPages({ beforePrepend }), [loadHistoryPages]);
437
+ const loadThroughMessage = useCallback((messageId) => messagesRef.current.some((message) => message.id === messageId)
438
+ ? Promise.resolve(true)
439
+ : loadHistoryPages({ untilMessageId: messageId }), [loadHistoryPages]);
413
440
  const visibleMessages = useMemo(() => {
414
441
  const next = [];
415
442
  for (const group of turnGroups) {
@@ -431,8 +458,10 @@ export function useChatThread(params) {
431
458
  setUsage(EMPTY_USAGE);
432
459
  setSessionStats(EMPTY_SESSION_STATS);
433
460
  setTurnTelemetry([]);
461
+ setUserTurnOutline([]);
434
462
  setOperatorActions([]);
435
463
  setConversationNodes([]);
464
+ setTurnStatesById({});
436
465
  setSddSessionIdentity(null);
437
466
  setStreaming(false);
438
467
  setTurnPhase("idle");
@@ -483,6 +512,7 @@ export function useChatThread(params) {
483
512
  : undefined));
484
513
  setToolCalls(next.toolCalls);
485
514
  setConversationNodes(conversationNodesFromEvents(next.conversationEvents ?? []));
515
+ setTurnStatesById(mergeTurnStateSummaries({}, next.turnStates));
486
516
  setOperationCards([]);
487
517
  setHumanGateCards([]);
488
518
  setArtifactCards([]);
@@ -491,6 +521,7 @@ export function useChatThread(params) {
491
521
  setUsage(next.usage ?? EMPTY_USAGE);
492
522
  setSessionStats(next.sessionStats ?? EMPTY_SESSION_STATS);
493
523
  setTurnTelemetry(next.turnTelemetry ?? []);
524
+ setUserTurnOutline(next.userTurnOutline ?? []);
494
525
  setOperatorActions([]);
495
526
  setSddSessionIdentity(next.sddSessionIdentity ?? null);
496
527
  refs.usageResponseKeysRef.current = new Set();
@@ -544,6 +575,7 @@ export function useChatThread(params) {
544
575
  if (body.usage)
545
576
  setUsage(body.usage);
546
577
  setTurnTelemetry(body.turnTelemetry ?? []);
578
+ setUserTurnOutline(body.userTurnOutline ?? []);
547
579
  setSddSessionIdentity(body.sddDataAlignment ?? null);
548
580
  // Resume the events stream from the snapshot tail so the next
549
581
  // reconnect never immediately re-expires (AC-003). The refs below
@@ -554,6 +586,7 @@ export function useChatThread(params) {
554
586
  refs.sealedAssistantIdsRef.current = new Set();
555
587
  const hydrated = hydrateSessionThread(body.session.messages ?? []);
556
588
  setConversationNodes(conversationNodesFromEvents(body.conversationEvents ?? []));
589
+ setTurnStatesById((current) => mergeTurnStateSummaries(current, body.session?.turnStates));
557
590
  // Recovery is addressed to targetSessionId, so an optimistic
558
591
  // submission from another session must never be projected into
559
592
  // this snapshot (the activation path enforces the same owner gate).
@@ -616,6 +649,7 @@ export function useChatThread(params) {
616
649
  historyNextCursor,
617
650
  historyLoading,
618
651
  loadEarlierMessages,
652
+ loadThroughMessage,
619
653
  unread,
620
654
  humanGateCards,
621
655
  interview,
@@ -625,8 +659,11 @@ export function useChatThread(params) {
625
659
  usage,
626
660
  sessionStats,
627
661
  turnTelemetry,
662
+ userTurnOutline,
628
663
  operatorActions,
629
664
  conversationNodes,
665
+ turnStatesById,
666
+ reconcileTurnStates,
630
667
  sddSessionIdentity,
631
668
  setSddSessionIdentity,
632
669
  streaming,
@@ -94,7 +94,7 @@ export function useRepoBrowser(params) {
94
94
  document.addEventListener("visibilitychange", onVisibility);
95
95
  return () => document.removeEventListener("visibilitychange", onVisibility);
96
96
  }, [refreshTree]);
97
- const openPreview = useCallback(async (file) => {
97
+ const openPreview = useCallback(async (file, focusLine) => {
98
98
  if (blocked)
99
99
  return; // AC-R5-002: fail-closed
100
100
  if (!workspaceScopeReady(workspaceId))
@@ -108,7 +108,10 @@ export function useRepoBrowser(params) {
108
108
  return;
109
109
  if (!res.ok || !body.data)
110
110
  throw new Error(body.error?.message ?? "无法预览文件");
111
- const loaded = withHtmlPreviewUrl(body.data, origin, workspaceId);
111
+ const loaded = {
112
+ ...withHtmlPreviewUrl(body.data, origin, workspaceId),
113
+ ...(focusLine ? { focusLine } : {}),
114
+ };
112
115
  setPreviews((current) => {
113
116
  const next = [
114
117
  ...current.filter((item) => item.path !== loaded.path),
@@ -69,10 +69,11 @@ export async function prepareScheduledGoalDraft(input, client) {
69
69
  if (!assessment.ok)
70
70
  throw new Error(`scheduled goal ineligible: ${assessment.reason}`);
71
71
  const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
72
+ const repositoryPath = (file) => path.relative(root, file).split(path.sep).join("/");
72
73
  return scheduledGoalDraftSchema.parse({ ...parsed, controllerRef: identity.packageFingerprint.value,
73
74
  prepared: { gateToken: `${snapshot.gate.id}:${snapshot.gate.digest}`,
74
- dagPath: path.relative(root, paths.dagDraftPath), dagHash: digest(dagBytes),
75
- contractPath: path.relative(root, paths.taskConfigPath), contractHash: digest(configBytes) }, });
75
+ dagPath: repositoryPath(paths.dagDraftPath), dagHash: digest(dagBytes),
76
+ contractPath: repositoryPath(paths.taskConfigPath), contractHash: digest(configBytes) }, });
76
77
  }
77
78
  /** Materialize only contract/runtime inputs; execution remains owned by the operation host. */
78
79
  export async function prepareDerivedScheduledGoalTask(goalId, ownerToken, goals, client) {
@@ -201,8 +201,13 @@ export async function captureScheduledGoalTree(worktree, base, allowedPaths, for
201
201
  }
202
202
  else if (stat.isFile()) {
203
203
  const bytes = await readFile(target);
204
- files.push({ name, kind: "file", hash: sha256(bytes), executable: Boolean(stat.mode & 0o111) });
205
204
  actualId = normalizedIds.get(name) ?? blobId(bytes);
205
+ files.push({
206
+ name,
207
+ kind: "file",
208
+ hash: sha256(`${objectFormat}:${actualId}`),
209
+ executable: Boolean(stat.mode & 0o111),
210
+ });
206
211
  actualMode = stat.mode & 0o111 ? "100755" : "100644";
207
212
  }
208
213
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.44.0-next.6",
3
+ "version": "0.44.0-next.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-C0HdXtOf.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-BTbmx3-k.js";import{_ as i}from"./mermaid.core-C0HdXtOf.js";import"./chunk-5VM5RSS4-BpJIdTKV.js";import"./chunk-XXDRQBXY-DrTdoYZG.js";import"./chunk-KBJHAD2P-B487SORR.js";import"./chunk-2GRJ4B5K-BKKnyR9Z.js";import"./index-CJh6pVLm.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-BTbmx3-k.js";import{_ as i}from"./mermaid.core-C0HdXtOf.js";import"./chunk-5VM5RSS4-BpJIdTKV.js";import"./chunk-XXDRQBXY-DrTdoYZG.js";import"./chunk-KBJHAD2P-B487SORR.js";import"./chunk-2GRJ4B5K-BKKnyR9Z.js";import"./index-CJh6pVLm.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};