@tea-agent/loop-agent 0.43.0-next.2 → 0.43.0-next.4

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 (92) hide show
  1. package/CHANGELOG.md +15 -12
  2. package/dist/application/dag/run-dag.js +5 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/executors/dag-pi-executor.js +274 -172
  5. package/dist/shared/frontend-execution-policy.js +1 -0
  6. package/dist/worker/console/chat/assistant-content.js +23 -2
  7. package/dist/worker/console/chat/explore-tools.js +13 -0
  8. package/dist/worker/console/chat/pi-runtime.js +18 -0
  9. package/dist/worker/console/chat/routes.js +11 -0
  10. package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
  11. package/dist/worker/console/chat/terminal-routes.js +216 -0
  12. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  13. package/dist/worker/console/chat/terminal-tools.js +199 -0
  14. package/dist/worker/console/chat/tool-preview.js +11 -0
  15. package/dist/worker/console/chat/tools.js +1 -0
  16. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-CCj36aaC.js} +1 -1
  17. package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-Cl3MIVFh.js} +1 -1
  18. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-C_n4kMJS.js} +1 -1
  19. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-DlWYJ7cK.js} +1 -1
  20. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-Ud5pS7vS.js} +1 -1
  21. package/dist/worker/console/static/assets/channel-CEMRpNm-.js +1 -0
  22. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-CQPwOv_j.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-CMzjxrWI.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-pcnXnD6t.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CEB3AvU2.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-DSKnbXbp.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-CS6-PjCZ.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-f6q8Tnzz.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-DkB0QX3q.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-BdB4jAv6.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-BVm1bnX6.js} +1 -1
  32. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +1 -0
  33. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +1 -0
  34. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-FV_P-IEp.js} +1 -1
  35. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-BM7RtkA2.js} +1 -1
  36. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-C1BZwTG5.js} +1 -1
  37. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-C6McNKmY.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-C0aWRUaE.js} +1 -1
  39. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-Bfk_SKE8.js} +1 -1
  40. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-prx1T4qf.js} +1 -1
  41. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-DBvty0eU.js} +1 -1
  42. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-CF0em8e9.js} +1 -1
  43. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-CF90b4Ws.js} +1 -1
  44. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-hfunxzof.js} +1 -1
  45. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-D-QSy4Nl.js} +1 -1
  46. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-DaBGNcvT.js} +1 -1
  47. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-BChcubGL.js} +1 -1
  48. package/dist/worker/console/static/assets/index-CJkCY1XR.js +464 -0
  49. package/dist/worker/console/static/assets/index-DUBCbiqv.css +1 -0
  50. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-BkC67fIa.js} +1 -1
  51. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-ibhLczCD.js} +1 -1
  52. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DCRMitay.js} +1 -1
  53. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-DJ_hKaYI.js} +1 -1
  54. package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-CIlLsYsA.js} +1 -1
  55. package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-C-5IQMP_.js} +5 -5
  56. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-TCzV5TQT.js} +1 -1
  57. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-bfhd2BQH.js} +1 -1
  58. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-BxFRAm5N.js} +1 -1
  59. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-C-m12lxv.js} +1 -1
  60. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-CwmLiGgx.js} +1 -1
  61. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-oc0Q1jiD.js} +1 -1
  62. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-9Ght6Yur.js} +1 -1
  63. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-ndm3lyz9.js} +1 -1
  64. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-7sPlKVcw.js} +1 -1
  65. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-Izl21-oS.js} +1 -1
  66. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +1 -0
  67. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-CtO3D65o.js} +2 -2
  68. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +8 -0
  69. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-Cyg5cY7z.js} +1 -1
  70. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-CwtE_TFK.js} +1 -1
  71. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-Dtt9v8OO.js} +1 -1
  72. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-B2Y3ryb6.js} +1 -1
  73. package/dist/worker/console/static/index.html +2 -2
  74. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
  75. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
  76. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  77. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  78. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  79. package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
  80. package/dist/worker/console/static-src/operator-chat/useChatThread.js +39 -4
  81. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  82. package/dist/worker/console/workspace-context.js +9 -1
  83. package/dist/workflows/dag/frontend-shape.js +30 -1
  84. package/dist/workflows/dag/node-execution.js +6 -0
  85. package/package.json +6 -3
  86. package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
  87. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
  88. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
  89. package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
  90. package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
  91. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
  92. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
@@ -0,0 +1,51 @@
1
+ import { useEffect, useLayoutEffect, useRef } from "react";
2
+ /** Whether this browser can reveal `hidden="until-found"` content through
3
+ * `beforematch` (Chrome 102+, Firefox 148+, Safari: not supported at all).
4
+ * `content-visibility` support does NOT imply this — Safari 18+ and
5
+ * Firefox 125–147 have the former without the latter. */
6
+ function supportsUntilFound() {
7
+ return typeof document !== "undefined" && "onbeforematch" in document.body;
8
+ }
9
+ /**
10
+ * Collapse content without removing it from the document: the element gets
11
+ * `hidden="until-found"` so browser find-in-page can still reach the text, and
12
+ * a match fires `beforematch`, which reveals (expands) the disclosure again.
13
+ *
14
+ * Mirrors DSH `useSearchableHidden` (2026-09-08 alignment). Browsers without
15
+ * `until-found` support get a plain `hidden=""` element, which keeps the
16
+ * pre-alignment behavior (collapsed and not searchable).
17
+ *
18
+ * @param hidden - whether the content is collapsed.
19
+ * @param reveal - expand callback invoked on `beforematch` and when the
20
+ * collapsed subtree already owns focus.
21
+ * @returns ref for the collapsible container.
22
+ */
23
+ export function useSearchableHidden(hidden, reveal) {
24
+ const ref = useRef(null);
25
+ useLayoutEffect(() => {
26
+ const element = ref.current;
27
+ if (element === null)
28
+ return;
29
+ // Focus would otherwise be lost inside a hidden subtree.
30
+ if (hidden && element.contains(element.ownerDocument.activeElement)) {
31
+ reveal();
32
+ return;
33
+ }
34
+ if (hidden) {
35
+ element.setAttribute("hidden", supportsUntilFound() ? "until-found" : "");
36
+ }
37
+ else {
38
+ element.removeAttribute("hidden");
39
+ }
40
+ }, [hidden, reveal]);
41
+ useEffect(() => {
42
+ const element = ref.current;
43
+ if (element === null)
44
+ return;
45
+ element.addEventListener("beforematch", reveal);
46
+ return () => {
47
+ element.removeEventListener("beforematch", reveal);
48
+ };
49
+ }, [reveal]);
50
+ return ref;
51
+ }
@@ -53,7 +53,11 @@ function clearEmptyAssistant(setMessages, assistantId) {
53
53
  return;
54
54
  setMessages((messages) => messages.filter((message) => !(message.id === assistantId &&
55
55
  message.role === "assistant" &&
56
- !message.text.trim())));
56
+ !message.text.trim() &&
57
+ // A row carrying only thinking/tool blocks is real content. Deleting
58
+ // it here dropped the turn's process nodes while the settled path in
59
+ // chat-sse-events deliberately keeps them (2026-09-08).
60
+ !(message.content && message.content.length > 0))));
57
61
  }
58
62
  /** One GET /events stream owns all Chat events. Sending a prompt creates a
59
63
  * detached Turn resource only; it never opens a second inline SSE consumer. */
@@ -140,7 +144,10 @@ export function useChatStream(params) {
140
144
  current.generation === generation)
141
145
  current.ready = true;
142
146
  replay = false;
143
- continue;
147
+ // Fall through: the applier owns the buffered-replay flush and
148
+ // returns immediately for this event. `continue` here silently
149
+ // dropped that flush, so a reconnect that landed after the
150
+ // turn settled never restored the answer (2026-09-08).
144
151
  }
145
152
  const assistantId = refs.assistantIdRef.current ?? fallbackAssistantId;
146
153
  const eventId = applySseBlock(block, assistantId, {
@@ -713,6 +720,15 @@ export function useChatStream(params) {
713
720
  if (refs.sessionRef.current?.sessionId !== activeSession.sessionId ||
714
721
  refs.sessionGenerationRef.current !== generation) {
715
722
  releaseSubmitOwnership(clientRequestId);
723
+ if (refs.sessionRef.current?.sessionId === activeSession.sessionId) {
724
+ // Same session, newer generation: the activation snapshot replaced
725
+ // the thread, so the optimistic row is gone. Restore the draft
726
+ // instead of losing the user's text silently (2026-09-08).
727
+ setMessages((messages) => removePendingSubmission(messages, clientRequestId));
728
+ clearEmptyAssistant(setMessages, assistantId);
729
+ setInput(draftText);
730
+ setPendingImages(draftImages);
731
+ }
716
732
  return;
717
733
  }
718
734
  // Attachments stay until accepted (AC/UIS-accepted).
@@ -1,10 +1,12 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
2
2
  import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
3
+ import { isToolFailure } from "../../chat/tool-preview.js";
3
4
  import { mergeOperationCardState, } from "../../chat/operation-card.js";
4
5
  import { createChatSseEventApplier, } from "./chat-sse-events.js";
5
6
  import { mergePendingSubmissionOnHydration } from "./pending-user-message.js";
6
7
  import { taskContextUnchanged } from "./format.js";
7
8
  import { upsertCompactionMessage } from "./compaction-message.js";
9
+ import { sameTurnGroupInputs } from "./turn-group-equality.js";
8
10
  const EMPTY_USAGE = {
9
11
  inputTokens: 0,
10
12
  outputTokens: 0,
@@ -115,6 +117,12 @@ export function useChatThread(params) {
115
117
  // (UI-08); the SSE applier must not open the process panel.
116
118
  void params.setProcessOpen;
117
119
  const [messages, setMessages] = useState([]);
120
+ /** Rendered-messages mirror for the replay flush: it must resolve its target
121
+ * row synchronously, before React commits the queued updater (2026-09-08). */
122
+ const messagesRef = useRef([]);
123
+ useLayoutEffect(() => {
124
+ messagesRef.current = messages;
125
+ }, [messages]);
118
126
  const [toolCalls, setToolCalls] = useState([]);
119
127
  const [operationCards, setOperationCards] = useState([]);
120
128
  const [artifactCards, setArtifactCards] = useState([]);
@@ -187,6 +195,7 @@ export function useChatThread(params) {
187
195
  onTelemetryRefresh: () => void refreshSessionTelemetry(),
188
196
  setMessages,
189
197
  setToolCalls,
198
+ getMessages: () => messagesRef.current,
190
199
  }), [refs, onReconcile, setError, refreshSessionTelemetry]);
191
200
  /** Parse + apply one raw SSE block (frames separated by \n\n). Returns the
192
201
  * eventId if the block carried an `id:` line (for Last-Event-ID tracking). */
@@ -285,7 +294,7 @@ export function useChatThread(params) {
285
294
  if (toolCalls.length === 0)
286
295
  return null;
287
296
  const running = toolCalls.filter((t) => t.status === "running");
288
- const errored = toolCalls.filter((t) => t.status === "error");
297
+ const errored = toolCalls.filter((t) => isToolFailure(t));
289
298
  const current = running[running.length - 1];
290
299
  let statusLabel = "完成";
291
300
  let statusKind = "ok";
@@ -348,7 +357,9 @@ export function useChatThread(params) {
348
357
  const previous = new Map(completedGroupsRef.current.map((group) => [group.key, group]));
349
358
  const all = built.map((group) => {
350
359
  const cached = previous.get(group.key);
351
- return cached && !group.live ? cached : group;
360
+ return cached && !group.live && sameTurnGroupInputs(cached, group)
361
+ ? cached
362
+ : group;
352
363
  });
353
364
  completedGroupsRef.current = all.filter((group) => !group.live);
354
365
  previousTimelineRef.current = {
@@ -450,7 +461,31 @@ export function useChatThread(params) {
450
461
  /** Adopt a freshly activated session's hydrated thread + streaming flags. */
451
462
  const applyActivatedSession = useCallback((next) => {
452
463
  const visuallyStopped = next.turnPhase === "stopping";
453
- setMessages(next.messages);
464
+ // A submission still owned for THIS session keeps its optimistic bubble
465
+ // across an activation snapshot (refresh / reopen); a lease owned by
466
+ // another session must never leak into this thread (2026-09-08).
467
+ const ownedPending = refs.pendingSubmissionRef.current?.sessionId ===
468
+ refs.sessionRef.current?.sessionId
469
+ ? refs.pendingSubmissionRef.current
470
+ : undefined;
471
+ setMessages(mergePendingSubmissionOnHydration(next.messages, ownedPending
472
+ ? {
473
+ clientRequestId: ownedPending.clientRequestId,
474
+ user: {
475
+ id: ownedPending.userMessageId,
476
+ role: "user",
477
+ text: ownedPending.text,
478
+ createdAt: Date.now(),
479
+ clientRequestId: ownedPending.clientRequestId,
480
+ },
481
+ assistant: {
482
+ id: ownedPending.assistantId,
483
+ role: "assistant",
484
+ text: "",
485
+ createdAt: Date.now(),
486
+ },
487
+ }
488
+ : undefined));
454
489
  setToolCalls(next.toolCalls);
455
490
  setOperationCards([]);
456
491
  setHumanGateCards([]);
@@ -0,0 +1,21 @@
1
+ /** User anchors only depend on the ordered user-message objects, not on
2
+ * assistant/process updates that rebuild their containing turn groups. */
3
+ export function sameUserMessages(previous, next) {
4
+ let previousIndex = 0;
5
+ let nextIndex = 0;
6
+ for (;;) {
7
+ while (previousIndex < previous.length && !previous[previousIndex].user) {
8
+ previousIndex += 1;
9
+ }
10
+ while (nextIndex < next.length && !next[nextIndex].user) {
11
+ nextIndex += 1;
12
+ }
13
+ if (previousIndex === previous.length || nextIndex === next.length) {
14
+ return previousIndex === previous.length && nextIndex === next.length;
15
+ }
16
+ if (previous[previousIndex].user !== next[nextIndex].user)
17
+ return false;
18
+ previousIndex += 1;
19
+ nextIndex += 1;
20
+ }
21
+ }
@@ -37,6 +37,7 @@ import { UserQuestionRegistry } from "./chat/user-questions.js";
37
37
  import { TodoListRegistry } from "./chat/todos.js";
38
38
  import { GoalRegistry } from "./chat/goals.js";
39
39
  import { GoalRoundDriver } from "./chat/goal-round-driver.js";
40
+ import { TerminalSessionRegistry } from "./chat/terminal-sessions.js";
40
41
  import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
41
42
  import { createOperationEventStore, } from "./operation-sse.js";
42
43
  import { OperationStore } from "../../infrastructure/console/operation-store.js";
@@ -254,7 +255,9 @@ export async function createWorkspaceContext(record, deps) {
254
255
  }
255
256
  },
256
257
  });
257
- const sddDataAlignment = new SddDataAlignmentService(chatStore, new AimaxAuthReader(), deps.fetchImpl);
258
+ const aimaxAuth = new AimaxAuthReader();
259
+ await aimaxAuth.getToken();
260
+ const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
258
261
  const chatEventEpoch = randomBytes(12).toString("hex");
259
262
  const chatEvents = createChatEventStore({
260
263
  appData,
@@ -285,6 +288,9 @@ export async function createWorkspaceContext(record, deps) {
285
288
  operations,
286
289
  operationEvents: events,
287
290
  });
291
+ // Persistent PTY terminal sessions shared by the model tool (terminal) and
292
+ // the Console UI terminal panel (HTTP API), keyed per chat session owner.
293
+ const terminalRegistry = new TerminalSessionRegistry();
288
294
  const chatRuntime = new ConsolePiRuntime({
289
295
  cwd: repoRoot,
290
296
  sessionDir: appData.chatSessions,
@@ -294,6 +300,7 @@ export async function createWorkspaceContext(record, deps) {
294
300
  todoRegistry,
295
301
  goalRegistry,
296
302
  sddDataAlignment,
303
+ terminalRegistry,
297
304
  });
298
305
  let chatRouteDeps;
299
306
  const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
@@ -308,6 +315,7 @@ export async function createWorkspaceContext(record, deps) {
308
315
  sddDataAlignment,
309
316
  goalRoundDriver,
310
317
  operationLinker: chatOperationLinker,
318
+ terminals: terminalRegistry,
311
319
  appData,
312
320
  bootToken: deps.bootToken,
313
321
  getConsoleOrigin: deps.getConsoleOrigin,
@@ -61,7 +61,7 @@ function pathSpreadScore(paths) {
61
61
  function collectEscalationSignals(blob, supervised) {
62
62
  const signals = [];
63
63
  for (const pattern of SHAPE_ESCALATION_PATTERNS) {
64
- if (pattern.re.test(blob))
64
+ if (hasAffirmativeShapeSignal(blob, pattern))
65
65
  signals.push(pattern.id);
66
66
  }
67
67
  if (supervised)
@@ -72,6 +72,35 @@ function collectEscalationSignals(blob, supervised) {
72
72
  }
73
73
  return [...new Set(signals)];
74
74
  }
75
+ function isExplicitShapeExclusion(clause, pattern) {
76
+ const match = new RegExp(pattern.re.source, "i").exec(clause);
77
+ if (!match)
78
+ return false;
79
+ const prefix = clause.slice(0, match.index).trim();
80
+ const suffix = clause.slice(match.index + match[0].length).trim();
81
+ const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
82
+ if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
83
+ return false;
84
+ if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
85
+ return true;
86
+ return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
87
+ }
88
+ /** Only a complete, independent scope exclusion can suppress a risk signal.
89
+ * Nested negation, conditions and trailing consequences remain risk evidence;
90
+ * an excluded occurrence never cancels another occurrence elsewhere. */
91
+ function hasAffirmativeShapeSignal(blob, pattern) {
92
+ for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
93
+ if (!pattern.re.test(sentence))
94
+ continue;
95
+ // Commas can attach a condition to an exclusion. A comma-separated list
96
+ // is independent only when every item is itself an explicit exclusion.
97
+ const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
98
+ if (clauses.every(clause => SHAPE_ESCALATION_PATTERNS.some(candidate => isExplicitShapeExclusion(clause, candidate))))
99
+ continue;
100
+ return true;
101
+ }
102
+ return false;
103
+ }
75
104
  /** Negation applies to the API mention itself, never to unrelated Mock policy. */
76
105
  function hasFrontendRemoteSignal(blob) {
77
106
  const remaining = blob
@@ -299,6 +299,12 @@ export function buildContextOverflowRetryPrompt(task, basePrompt) {
299
299
  compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
300
300
  "</task>",
301
301
  ].join("\n"),
302
+ // Plan still needs the complete frozen obligations for ledger ownership
303
+ // and scoped projection. Its executor packs this block before dispatch;
304
+ // dropping or character-truncating it prevents safe progress recovery.
305
+ ...(isFrontendPlanLadderTask(task)
306
+ ? [basePrompt.match(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/)?.[0] ?? ""]
307
+ : []),
302
308
  ].join("\n\n");
303
309
  }
304
310
  function isFrontendPlanLadderTask(task) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.43.0-next.2",
3
+ "version": "0.43.0-next.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -78,9 +78,12 @@
78
78
  "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
79
79
  "@earendil-works/pi-ai": "0.83.0",
80
80
  "@earendil-works/pi-coding-agent": "0.83.0",
81
+ "@xterm/addon-fit": "^0.11.0",
82
+ "@xterm/xterm": "^6.0.0",
81
83
  "commander": "^12.1.0",
82
84
  "katex": "^0.16.47",
83
85
  "mermaid": "^11.16.1",
86
+ "node-pty": "^1.2.0-beta.15",
84
87
  "rehype-highlight": "^7.0.2",
85
88
  "rehype-katex": "^7.0.1",
86
89
  "remark-math": "^6.0.0",
@@ -90,8 +93,8 @@
90
93
  "zod": "^3.25.76"
91
94
  },
92
95
  "optionalDependencies": {
93
- "@cursor/sdk": "^1.0.7",
94
- "@agent-context/sift": "0.0.1-alpha.7"
96
+ "@agent-context/sift": "0.0.1-alpha.7",
97
+ "@cursor/sdk": "^1.0.7"
95
98
  },
96
99
  "devDependencies": {
97
100
  "@remixicon/react": "^4.9.0",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-BUuGHmWO.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-sF1AEy7T.js";import{_ as i}from"./mermaid.core-BUuGHmWO.js";import"./chunk-5VM5RSS4-D5J9PHC7.js";import"./chunk-XXDRQBXY-BEJi3iIs.js";import"./chunk-KBJHAD2P-BKJrPcJ6.js";import"./chunk-2GRJ4B5K-C0osm1Zf.js";import"./index-C0O48S_P.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-sF1AEy7T.js";import{_ as i}from"./mermaid.core-BUuGHmWO.js";import"./chunk-5VM5RSS4-D5J9PHC7.js";import"./chunk-XXDRQBXY-BEJi3iIs.js";import"./chunk-KBJHAD2P-BKJrPcJ6.js";import"./chunk-2GRJ4B5K-C0osm1Zf.js";import"./index-C0O48S_P.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};