@tea-agent/loop-agent 0.38.0 → 0.38.2

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 (147) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/dist/application/dag/generate-task-dag.js +9 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/commands/init-upgrade.js +97 -19
  5. package/dist/executors/dag-pi-executor.js +42 -4
  6. package/dist/executors/shell-executor.js +269 -15
  7. package/dist/infrastructure/harness/atomic-write.js +2 -1
  8. package/dist/shared/openspec-spec.js +76 -0
  9. package/dist/shared/operator/capabilities.js +149 -27
  10. package/dist/task/config-types.js +43 -0
  11. package/dist/task/frontend-project-capability.js +292 -38
  12. package/dist/worker/console/chat/deferred-turn.js +91 -0
  13. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  14. package/dist/worker/console/chat/pi-runtime.js +165 -83
  15. package/dist/worker/console/chat/repo-walk.js +10 -1
  16. package/dist/worker/console/chat/routes.js +477 -55
  17. package/dist/worker/console/chat/semantic-activity.js +20 -8
  18. package/dist/worker/console/chat/session-store.js +146 -2
  19. package/dist/worker/console/chat/turn-process.js +17 -30
  20. package/dist/worker/console/harness-pi-model-matrix.js +137 -0
  21. package/dist/worker/console/operator-actions.js +128 -4
  22. package/dist/worker/console/operator-user-error.js +19 -0
  23. package/dist/worker/console/pi-readiness.js +8 -1
  24. package/dist/worker/console/recovery-cta.js +4 -4
  25. package/dist/worker/console/recovery-error-copy.js +4 -4
  26. package/dist/worker/console/security.js +20 -1
  27. package/dist/worker/console/server.js +6 -1
  28. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-C4bmo3iY.js} +1 -1
  29. package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-SAD7McOS.js} +1 -1
  30. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DAVLUdK1.js} +1 -1
  31. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-CPPdv4vj.js} +1 -1
  32. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-D1GRGZax.js} +1 -1
  33. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +1 -0
  34. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-D_Bq2ZqK.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-CPLVtamK.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-DnaZWcL3.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BRTmFgnq.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BRlfYZoH.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-CjNybD4u.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-D3e-74fG.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Br3ir6f2.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-1NgVuRbh.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-BTYuvuWN.js} +1 -1
  44. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +1 -0
  45. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +1 -0
  46. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-BC6Bgosf.js} +1 -1
  47. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DV-HypfL.js} +1 -1
  48. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DhTaddO-.js} +1 -1
  49. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-BnEcOmiI.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-xphGRVKp.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CrdD1mRG.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-D4YdLsaF.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-LCKyGZ5U.js} +1 -1
  54. package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-BeiTe7t0.js} +1 -1
  55. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BO2cq8GS.js} +1 -1
  56. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-CmWww3Nt.js} +1 -1
  57. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-C2kkUYg5.js} +1 -1
  58. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-DY_JZ2iQ.js} +1 -1
  59. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-BizhCSw9.js} +1 -1
  60. package/dist/worker/console/static/assets/index-BXN89VPX.js +325 -0
  61. package/dist/worker/console/static/assets/index-CuH5CNFu.css +1 -0
  62. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-Dlo2eK_d.js} +1 -1
  63. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js} +1 -1
  64. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DiMDe5Yh.js} +1 -1
  65. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-sN553oIN.js} +1 -1
  66. package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-D9Py31Ld.js} +1 -1
  67. package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-D7Occzk_.js} +5 -5
  68. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-DjDRboiR.js} +1 -1
  69. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Cgc4qlzp.js} +1 -1
  70. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-DxZj8pKd.js} +1 -1
  71. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CJ50MngR.js} +1 -1
  72. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-BY-OD8Tr.js} +1 -1
  73. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-CQFButfK.js} +1 -1
  74. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BB_QUK_b.js} +1 -1
  75. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CqhXZjyx.js} +1 -1
  76. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-2l1U4rgJ.js} +1 -1
  77. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-BX1FbrhJ.js} +1 -1
  78. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +1 -0
  79. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DOSnT7hb.js} +2 -2
  80. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +8 -0
  81. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DVJQA7AN.js} +1 -1
  82. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-74uxl9gx.js} +1 -1
  83. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-DsBxlcg_.js} +1 -1
  84. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-snuW9Mjw.js} +1 -1
  85. package/dist/worker/console/static/index.html +2 -2
  86. package/dist/worker/console/static-src/app/console-types.js +39 -0
  87. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  88. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  89. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +82 -22
  90. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
  91. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  92. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  93. package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
  94. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +18 -0
  95. package/dist/worker/console/static-src/operator-chat/useChatStream.js +216 -10
  96. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  97. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  98. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
  99. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  100. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  101. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  102. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  103. package/dist/workflows/dag/backend-test-layout.js +133 -0
  104. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  105. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  106. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  107. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  108. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  109. package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
  110. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  111. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  112. package/dist/workflows/dag/convergence/controller.js +141 -0
  113. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  114. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  115. package/dist/workflows/dag/failure-routing.js +9 -4
  116. package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
  117. package/dist/workflows/dag/frontend-plan-render.js +90 -0
  118. package/dist/workflows/dag/frontend-prewrite-gate.js +479 -28
  119. package/dist/workflows/dag/init-hybrid.js +920 -110
  120. package/dist/workflows/dag/lifecycle.js +4 -0
  121. package/dist/workflows/dag/node-execution.js +77 -8
  122. package/dist/workflows/dag/prompt.js +45 -0
  123. package/dist/workflows/dag/report.js +6 -0
  124. package/dist/workflows/dag/rerun-feedback.js +212 -0
  125. package/dist/workflows/dag/rerun-plan.js +93 -27
  126. package/dist/workflows/dag/rerun-run.js +3 -0
  127. package/dist/workflows/dag/rerun-task.js +61 -2
  128. package/dist/workflows/dag/retry-policy.js +33 -0
  129. package/dist/workflows/dag/types.js +148 -4
  130. package/dist/workflows/dag/validate.js +7 -2
  131. package/docs/templates/backend-test-dag.json +60 -35
  132. package/docs/templates/frontend-design-contract.md +40 -14
  133. package/docs/templates/frontend-implementation-contract.schema.json +9 -2
  134. package/docs/templates/frontend-task-constraints.md +40 -13
  135. package/docs/templates/frontend-task-requirement.md +54 -20
  136. package/harness.json +3 -1
  137. package/package.json +1 -1
  138. package/skills/frontend-design-review/references/review-checklist.md +2 -0
  139. package/skills/frontend-implementation/references/design-spec.md +16 -19
  140. package/skills/frontend-implementation/references/node-contracts.md +15 -11
  141. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  142. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  143. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  144. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  145. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  147. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
- import { parseAssistantContent } from "../../chat/assistant-content.js";
3
- import { buildTurnProcessGroups } from "../../chat/turn-process.js";
2
+ import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
4
3
  import { createChatSseEventApplier, } from "./chat-sse-events.js";
5
4
  import { taskContextUnchanged } from "./format.js";
6
5
  const EMPTY_USAGE = {
@@ -32,6 +31,8 @@ export function useChatThread(params) {
32
31
  const [contextLoading, setContextLoading] = useState(false);
33
32
  const [usage, setUsage] = useState(EMPTY_USAGE);
34
33
  const [streaming, setStreaming] = useState(false);
34
+ const [turnPhase, setTurnPhase] = useState("idle");
35
+ const [deferredTurn, setDeferredTurn] = useState(null);
35
36
  /** Assistant bubble id for the in-flight turn; only this row shows the streaming cursor. */
36
37
  const [streamingAssistantId, setStreamingAssistantId] = useState(null);
37
38
  const [showSystemInMessages] = useState(false);
@@ -45,6 +46,8 @@ export function useChatThread(params) {
45
46
  setHumanGateCards,
46
47
  setArtifactCards,
47
48
  setStreaming,
49
+ setTurnPhase,
50
+ setDeferredTurn,
48
51
  setStreamingAssistantId,
49
52
  setUnread,
50
53
  setOperationCards,
@@ -121,9 +124,7 @@ export function useChatThread(params) {
121
124
  }, 350);
122
125
  return () => window.clearTimeout(timer);
123
126
  }, [sessionId, operationCards, humanGateCards, interview, loadTaskContext]);
124
- const visibleMessages = useMemo(() => messages
125
- .filter((m) => m.role !== "system" || showSystemInMessages)
126
- .slice(-historyLimit), [messages, showSystemInMessages, historyLimit]);
127
+ const timelineMessages = useMemo(() => messages.filter((m) => m.role !== "system" || showSystemInMessages), [messages, showSystemInMessages]);
127
128
  useEffect(() => {
128
129
  const onVisibility = () => {
129
130
  if (!document.hidden)
@@ -158,12 +159,30 @@ export function useChatThread(params) {
158
159
  statusKind,
159
160
  };
160
161
  }, [toolCalls]);
161
- const turnGroups = useMemo(() => buildTurnProcessGroups({
162
- messages: visibleMessages,
162
+ const turnGroups = useMemo(() => {
163
+ const all = buildTurnProcessGroups({
164
+ messages: timelineMessages,
165
+ toolCalls,
166
+ streaming,
167
+ activeAssistantId: streamingAssistantId,
168
+ });
169
+ return selectRecentTurnGroups(all, historyLimit);
170
+ }, [
171
+ timelineMessages,
163
172
  toolCalls,
164
173
  streaming,
165
- activeAssistantId: streamingAssistantId,
166
- }), [visibleMessages, toolCalls, streaming, streamingAssistantId]);
174
+ streamingAssistantId,
175
+ historyLimit,
176
+ ]);
177
+ const visibleMessages = useMemo(() => {
178
+ const next = [];
179
+ for (const group of turnGroups) {
180
+ if (group.user)
181
+ next.push(group.user);
182
+ next.push(...group.assistants);
183
+ }
184
+ return next;
185
+ }, [turnGroups]);
167
186
  /** Reset all session-scoped projections before adopting a fresh session. */
168
187
  const resetForNewSession = useCallback(() => {
169
188
  setMessages([]);
@@ -175,6 +194,8 @@ export function useChatThread(params) {
175
194
  setTaskContext(null);
176
195
  setUsage(EMPTY_USAGE);
177
196
  setStreaming(false);
197
+ setTurnPhase("idle");
198
+ setDeferredTurn(null);
178
199
  setStreamingAssistantId(null);
179
200
  setUnread(0);
180
201
  setHistoryLimit(50);
@@ -192,6 +213,8 @@ export function useChatThread(params) {
192
213
  setUsage(EMPTY_USAGE);
193
214
  refs.usageResponseKeysRef.current = new Set();
194
215
  setStreaming(next.streaming);
216
+ setTurnPhase(next.turnPhase ?? (next.streaming ? "running" : "idle"));
217
+ setDeferredTurn(next.deferredTurn ?? null);
195
218
  setStreamingAssistantId(null);
196
219
  setUnread(0);
197
220
  setHistoryLimit(50);
@@ -213,6 +236,8 @@ export function useChatThread(params) {
213
236
  const activeTurnId = body.session.activeTurnId ?? null;
214
237
  refs.activeTurnIdRef.current = activeTurnId;
215
238
  refs.streamingRef.current = Boolean(activeTurnId);
239
+ setTurnPhase(body.session.turnPhase ?? (activeTurnId ? "running" : "idle"));
240
+ setDeferredTurn(body.session.deferredTurn ?? null);
216
241
  // Resume the events stream from the snapshot tail so the next
217
242
  // reconnect never immediately re-expires (AC-003). The refs below
218
243
  // are reset so any follow-up tail replay re-applies cleanly.
@@ -220,18 +245,9 @@ export function useChatThread(params) {
220
245
  refs.seenEventIdsRef.current = new Set();
221
246
  refs.turnAssistantIdsRef.current = new Map();
222
247
  refs.sealedAssistantIdsRef.current = new Set();
223
- setMessages((body.session.messages ?? [])
224
- .filter((message) => message.role === "user" || message.role === "assistant")
225
- .map((message) => {
226
- const content = parseAssistantContent(message.content);
227
- return {
228
- id: message.id,
229
- role: message.role,
230
- text: message.text,
231
- createdAt: Date.parse(message.at) || Date.now(),
232
- ...(content && content.length > 0 ? { content } : {}),
233
- };
234
- }));
248
+ const hydrated = hydrateSessionThread(body.session.messages ?? []);
249
+ setMessages(hydrated.messages);
250
+ setToolCalls(hydrated.toolCalls);
235
251
  setStreamingAssistantId(null);
236
252
  return true;
237
253
  }
@@ -240,7 +256,15 @@ export function useChatThread(params) {
240
256
  // trigger another reconcile and retry (best-effort recovery).
241
257
  return false;
242
258
  }
243
- }, [origin, refs, setMessages, setStreamingAssistantId]);
259
+ }, [
260
+ origin,
261
+ refs,
262
+ setMessages,
263
+ setToolCalls,
264
+ setStreamingAssistantId,
265
+ setTurnPhase,
266
+ setDeferredTurn,
267
+ ]);
244
268
  return {
245
269
  messages,
246
270
  setMessages,
@@ -259,6 +283,10 @@ export function useChatThread(params) {
259
283
  usage,
260
284
  streaming,
261
285
  setStreaming,
286
+ turnPhase,
287
+ setTurnPhase,
288
+ deferredTurn,
289
+ setDeferredTurn,
262
290
  streamingAssistantId,
263
291
  setStreamingAssistantId,
264
292
  visibleMessages,
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
3
3
  /** Left-rail repo tree + file preview tabs state. */
4
4
  export function useRepoBrowser(params) {
@@ -9,6 +9,8 @@ export function useRepoBrowser(params) {
9
9
  const [previews, setPreviews] = useState([]);
10
10
  const [activePreviewPath, setActivePreviewPath] = useState(null);
11
11
  const [previewLoading, setPreviewLoading] = useState(false);
12
+ const expandedDirsRef = useRef(new Set());
13
+ expandedDirsRef.current = expandedDirs;
12
14
  const loadTree = useCallback(async (directory = "") => {
13
15
  try {
14
16
  setTreeError(null);
@@ -28,6 +30,28 @@ export function useRepoBrowser(params) {
28
30
  useEffect(() => {
29
31
  void loadTree();
30
32
  }, [loadTree]);
33
+ /** pi-web parity: re-fetch the root + every expanded directory. Bump
34
+ * `refreshTick` to trigger; cached entries are NOT reused, so newly
35
+ * created/deleted files appear without a manual refresh. */
36
+ const [refreshTick, setRefreshTick] = useState(0);
37
+ const refreshTree = useCallback(() => {
38
+ setRefreshTick((tick) => tick + 1);
39
+ }, []);
40
+ useEffect(() => {
41
+ const dirs = ["", ...expandedDirsRef.current];
42
+ for (const dir of dirs)
43
+ void loadTree(dir);
44
+ }, [loadTree, refreshTick]);
45
+ // Also refresh when the tab regains focus: external tools (editor, git,
46
+ // CLI) may have changed the tree while the console was hidden.
47
+ useEffect(() => {
48
+ const onVisibility = () => {
49
+ if (!document.hidden)
50
+ refreshTree();
51
+ };
52
+ document.addEventListener("visibilitychange", onVisibility);
53
+ return () => document.removeEventListener("visibilitychange", onVisibility);
54
+ }, [refreshTree]);
31
55
  const openPreview = useCallback(async (file) => {
32
56
  setPreviewLoading(true);
33
57
  try {
@@ -116,6 +140,7 @@ export function useRepoBrowser(params) {
116
140
  previewLoading,
117
141
  setPreviews,
118
142
  loadTree,
143
+ refreshTree,
119
144
  openPreview,
120
145
  closePreview,
121
146
  toggleDir,