@prestyj/cli 5.6.0 → 5.8.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 (246) hide show
  1. package/README.md +2 -2
  2. package/dist/app-sidecar.js +369 -92
  3. package/dist/app-sidecar.js.map +1 -1
  4. package/dist/cli/auth.d.ts.map +1 -1
  5. package/dist/cli/auth.js +5 -2
  6. package/dist/cli/auth.js.map +1 -1
  7. package/dist/cli/shared.d.ts.map +1 -1
  8. package/dist/cli/shared.js +2 -0
  9. package/dist/cli/shared.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +33 -3
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +1 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/config.test.js +7 -0
  17. package/dist/config.test.js.map +1 -1
  18. package/dist/core/agent-session-compaction.test.js +521 -10
  19. package/dist/core/agent-session-compaction.test.js.map +1 -1
  20. package/dist/core/agent-session-memory-tail.test.js +1 -1
  21. package/dist/core/agent-session-memory-tail.test.js.map +1 -1
  22. package/dist/core/agent-session-queue.test.js +1 -1
  23. package/dist/core/agent-session-queue.test.js.map +1 -1
  24. package/dist/core/agent-session-review-coverage.test.d.ts +2 -0
  25. package/dist/core/agent-session-review-coverage.test.d.ts.map +1 -0
  26. package/dist/core/agent-session-review-coverage.test.js +83 -0
  27. package/dist/core/agent-session-review-coverage.test.js.map +1 -0
  28. package/dist/core/agent-session-tool-result-policy.test.d.ts +2 -0
  29. package/dist/core/agent-session-tool-result-policy.test.d.ts.map +1 -0
  30. package/dist/core/agent-session-tool-result-policy.test.js +25 -0
  31. package/dist/core/agent-session-tool-result-policy.test.js.map +1 -0
  32. package/dist/core/agent-session.d.ts +51 -12
  33. package/dist/core/agent-session.d.ts.map +1 -1
  34. package/dist/core/agent-session.js +415 -106
  35. package/dist/core/agent-session.js.map +1 -1
  36. package/dist/core/auth-providers.d.ts.map +1 -1
  37. package/dist/core/auth-providers.js +15 -8
  38. package/dist/core/auth-providers.js.map +1 -1
  39. package/dist/core/compaction/active-context.d.ts +17 -0
  40. package/dist/core/compaction/active-context.d.ts.map +1 -0
  41. package/dist/core/compaction/active-context.js +20 -0
  42. package/dist/core/compaction/active-context.js.map +1 -0
  43. package/dist/core/compaction/active-context.test.d.ts +2 -0
  44. package/dist/core/compaction/active-context.test.d.ts.map +1 -0
  45. package/dist/core/compaction/active-context.test.js +36 -0
  46. package/dist/core/compaction/active-context.test.js.map +1 -0
  47. package/dist/core/compaction/compactor.d.ts +19 -18
  48. package/dist/core/compaction/compactor.d.ts.map +1 -1
  49. package/dist/core/compaction/compactor.js +64 -42
  50. package/dist/core/compaction/compactor.js.map +1 -1
  51. package/dist/core/compaction/compactor.test.js +122 -65
  52. package/dist/core/compaction/compactor.test.js.map +1 -1
  53. package/dist/core/compaction/token-estimator.d.ts +25 -1
  54. package/dist/core/compaction/token-estimator.d.ts.map +1 -1
  55. package/dist/core/compaction/token-estimator.js +86 -1
  56. package/dist/core/compaction/token-estimator.js.map +1 -1
  57. package/dist/core/compaction/token-estimator.test.js +147 -1
  58. package/dist/core/compaction/token-estimator.test.js.map +1 -1
  59. package/dist/core/compaction/tool-result-pruner.d.ts +37 -0
  60. package/dist/core/compaction/tool-result-pruner.d.ts.map +1 -0
  61. package/dist/core/compaction/tool-result-pruner.js +103 -0
  62. package/dist/core/compaction/tool-result-pruner.js.map +1 -0
  63. package/dist/core/compaction/tool-result-pruner.test.d.ts +2 -0
  64. package/dist/core/compaction/tool-result-pruner.test.d.ts.map +1 -0
  65. package/dist/core/compaction/tool-result-pruner.test.js +148 -0
  66. package/dist/core/compaction/tool-result-pruner.test.js.map +1 -0
  67. package/dist/core/event-bus.d.ts +2 -0
  68. package/dist/core/event-bus.d.ts.map +1 -1
  69. package/dist/core/event-bus.js.map +1 -1
  70. package/dist/core/ideal-review.d.ts +33 -0
  71. package/dist/core/ideal-review.d.ts.map +1 -1
  72. package/dist/core/ideal-review.js +78 -0
  73. package/dist/core/ideal-review.js.map +1 -1
  74. package/dist/core/ideal-review.test.js +43 -1
  75. package/dist/core/ideal-review.test.js.map +1 -1
  76. package/dist/core/index.d.ts +1 -1
  77. package/dist/core/index.d.ts.map +1 -1
  78. package/dist/core/index.js +1 -1
  79. package/dist/core/index.js.map +1 -1
  80. package/dist/core/lsp/client.d.ts +3 -0
  81. package/dist/core/lsp/client.d.ts.map +1 -1
  82. package/dist/core/lsp/client.js +20 -0
  83. package/dist/core/lsp/client.js.map +1 -1
  84. package/dist/core/lsp/manager.d.ts +32 -9
  85. package/dist/core/lsp/manager.d.ts.map +1 -1
  86. package/dist/core/lsp/manager.js +89 -37
  87. package/dist/core/lsp/manager.js.map +1 -1
  88. package/dist/core/lsp/manager.test.js +56 -4
  89. package/dist/core/lsp/manager.test.js.map +1 -1
  90. package/dist/core/project-discovery.d.ts +6 -9
  91. package/dist/core/project-discovery.d.ts.map +1 -1
  92. package/dist/core/project-discovery.js +35 -28
  93. package/dist/core/project-discovery.js.map +1 -1
  94. package/dist/core/project-discovery.test.js +148 -0
  95. package/dist/core/project-discovery.test.js.map +1 -1
  96. package/dist/core/resolve-start.test.js +1 -0
  97. package/dist/core/resolve-start.test.js.map +1 -1
  98. package/dist/core/run-lifecycle.d.ts +40 -0
  99. package/dist/core/run-lifecycle.d.ts.map +1 -0
  100. package/dist/core/run-lifecycle.js +95 -0
  101. package/dist/core/run-lifecycle.js.map +1 -0
  102. package/dist/core/run-lifecycle.test.d.ts +2 -0
  103. package/dist/core/run-lifecycle.test.d.ts.map +1 -0
  104. package/dist/core/run-lifecycle.test.js +64 -0
  105. package/dist/core/run-lifecycle.test.js.map +1 -0
  106. package/dist/core/session-compaction.d.ts +3 -0
  107. package/dist/core/session-compaction.d.ts.map +1 -1
  108. package/dist/core/session-compaction.js +14 -1
  109. package/dist/core/session-compaction.js.map +1 -1
  110. package/dist/core/session-compaction.test.js +5 -0
  111. package/dist/core/session-compaction.test.js.map +1 -1
  112. package/dist/core/session-manager.d.ts +33 -2
  113. package/dist/core/session-manager.d.ts.map +1 -1
  114. package/dist/core/session-manager.js +73 -2
  115. package/dist/core/session-manager.js.map +1 -1
  116. package/dist/core/session-manager.test.js +97 -2
  117. package/dist/core/session-manager.test.js.map +1 -1
  118. package/dist/core/session-preview.d.ts +11 -0
  119. package/dist/core/session-preview.d.ts.map +1 -0
  120. package/dist/core/session-preview.js +50 -0
  121. package/dist/core/session-preview.js.map +1 -0
  122. package/dist/core/settings-manager.d.ts +1 -0
  123. package/dist/core/settings-manager.d.ts.map +1 -1
  124. package/dist/core/settings-manager.js +3 -2
  125. package/dist/core/settings-manager.js.map +1 -1
  126. package/dist/core/subagent-manager.d.ts +42 -7
  127. package/dist/core/subagent-manager.d.ts.map +1 -1
  128. package/dist/core/subagent-manager.js +346 -30
  129. package/dist/core/subagent-manager.js.map +1 -1
  130. package/dist/core/subagent-manager.test.js +201 -5
  131. package/dist/core/subagent-manager.test.js.map +1 -1
  132. package/dist/core/subagent-store.d.ts +20 -0
  133. package/dist/core/subagent-store.d.ts.map +1 -0
  134. package/dist/core/subagent-store.js +177 -0
  135. package/dist/core/subagent-store.js.map +1 -0
  136. package/dist/core/subagent-store.test.d.ts +2 -0
  137. package/dist/core/subagent-store.test.d.ts.map +1 -0
  138. package/dist/core/subagent-store.test.js +94 -0
  139. package/dist/core/subagent-store.test.js.map +1 -0
  140. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  141. package/dist/modes/agent-home-mode.js +10 -3
  142. package/dist/modes/agent-home-mode.js.map +1 -1
  143. package/dist/modes/serve-mode.d.ts.map +1 -1
  144. package/dist/modes/serve-mode.js +10 -3
  145. package/dist/modes/serve-mode.js.map +1 -1
  146. package/dist/modes/subagent-worker-mode.d.ts +2 -0
  147. package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
  148. package/dist/modes/subagent-worker-mode.js +13 -3
  149. package/dist/modes/subagent-worker-mode.js.map +1 -1
  150. package/dist/tools/bash.d.ts +9 -0
  151. package/dist/tools/bash.d.ts.map +1 -1
  152. package/dist/tools/bash.js +28 -18
  153. package/dist/tools/bash.js.map +1 -1
  154. package/dist/tools/bash.test.d.ts +2 -0
  155. package/dist/tools/bash.test.d.ts.map +1 -0
  156. package/dist/tools/bash.test.js +56 -0
  157. package/dist/tools/bash.test.js.map +1 -0
  158. package/dist/tools/goals.d.ts +3 -3
  159. package/dist/tools/overflow.d.ts +12 -2
  160. package/dist/tools/overflow.d.ts.map +1 -1
  161. package/dist/tools/overflow.js +49 -4
  162. package/dist/tools/overflow.js.map +1 -1
  163. package/dist/tools/overflow.test.d.ts +2 -0
  164. package/dist/tools/overflow.test.d.ts.map +1 -0
  165. package/dist/tools/overflow.test.js +62 -0
  166. package/dist/tools/overflow.test.js.map +1 -0
  167. package/dist/tools/subagent-shared.d.ts +8 -0
  168. package/dist/tools/subagent-shared.d.ts.map +1 -1
  169. package/dist/tools/subagent-shared.js.map +1 -1
  170. package/dist/tools/subagent.d.ts +3 -8
  171. package/dist/tools/subagent.d.ts.map +1 -1
  172. package/dist/tools/subagent.js +10 -8
  173. package/dist/tools/subagent.js.map +1 -1
  174. package/dist/tools/subagent.test.js +17 -1
  175. package/dist/tools/subagent.test.js.map +1 -1
  176. package/dist/tools/tasks.d.ts +2 -2
  177. package/dist/tools/web-fetch.d.ts +1 -1
  178. package/dist/ui/App.d.ts +7 -3
  179. package/dist/ui/App.d.ts.map +1 -1
  180. package/dist/ui/App.js +68 -98
  181. package/dist/ui/App.js.map +1 -1
  182. package/dist/ui/components/Footer.d.ts +1 -0
  183. package/dist/ui/components/Footer.d.ts.map +1 -1
  184. package/dist/ui/components/Footer.js +3 -3
  185. package/dist/ui/components/Footer.js.map +1 -1
  186. package/dist/ui/components/Footer.test.d.ts +2 -0
  187. package/dist/ui/components/Footer.test.d.ts.map +1 -0
  188. package/dist/ui/components/Footer.test.js +16 -0
  189. package/dist/ui/components/Footer.test.js.map +1 -0
  190. package/dist/ui/components/ModelSelector.d.ts.map +1 -1
  191. package/dist/ui/components/ModelSelector.js +1 -0
  192. package/dist/ui/components/ModelSelector.js.map +1 -1
  193. package/dist/ui/components/SubAgentPanel.d.ts +2 -0
  194. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  195. package/dist/ui/components/SubAgentPanel.js +4 -2
  196. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  197. package/dist/ui/hooks/useAgentLoop.d.ts +9 -6
  198. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  199. package/dist/ui/hooks/useAgentLoop.js +68 -8
  200. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  201. package/dist/ui/hooks/useAgentLoop.test.js +31 -0
  202. package/dist/ui/hooks/useAgentLoop.test.js.map +1 -1
  203. package/dist/ui/hooks/useContextCompaction.d.ts +5 -8
  204. package/dist/ui/hooks/useContextCompaction.d.ts.map +1 -1
  205. package/dist/ui/hooks/useContextCompaction.js +77 -16
  206. package/dist/ui/hooks/useContextCompaction.js.map +1 -1
  207. package/dist/ui/hooks/useContextCompaction.test.d.ts +2 -0
  208. package/dist/ui/hooks/useContextCompaction.test.d.ts.map +1 -0
  209. package/dist/ui/hooks/useContextCompaction.test.js +135 -0
  210. package/dist/ui/hooks/useContextCompaction.test.js.map +1 -0
  211. package/dist/ui/hooks/useSessionPersistence.d.ts +4 -2
  212. package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
  213. package/dist/ui/hooks/useSessionPersistence.js +10 -1
  214. package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
  215. package/dist/ui/hooks/useTerminalTitle.d.ts +3 -3
  216. package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
  217. package/dist/ui/hooks/useTerminalTitle.js +5 -9
  218. package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
  219. package/dist/ui/login.d.ts.map +1 -1
  220. package/dist/ui/login.js +7 -2
  221. package/dist/ui/login.js.map +1 -1
  222. package/dist/ui/render.d.ts +7 -2
  223. package/dist/ui/render.d.ts.map +1 -1
  224. package/dist/ui/render.js +4 -4
  225. package/dist/ui/render.js.map +1 -1
  226. package/dist/ui/render.test.js +10 -1
  227. package/dist/ui/render.test.js.map +1 -1
  228. package/dist/ui/terminal-history.js +6 -2
  229. package/dist/ui/terminal-history.js.map +1 -1
  230. package/dist/ui/terminal-history.test.js +2 -2
  231. package/dist/ui/terminal-history.test.js.map +1 -1
  232. package/dist/ui/tui-history-parity.test.js +8 -1
  233. package/dist/ui/tui-history-parity.test.js.map +1 -1
  234. package/dist/utils/git.d.ts +2 -0
  235. package/dist/utils/git.d.ts.map +1 -1
  236. package/dist/utils/git.js +12 -0
  237. package/dist/utils/git.js.map +1 -1
  238. package/dist/utils/git.test.d.ts +2 -0
  239. package/dist/utils/git.test.d.ts.map +1 -0
  240. package/dist/utils/git.test.js +36 -0
  241. package/dist/utils/git.test.js.map +1 -0
  242. package/package.json +5 -5
  243. package/dist/utils/session-title.d.ts +0 -19
  244. package/dist/utils/session-title.d.ts.map +0 -1
  245. package/dist/utils/session-title.js +0 -82
  246. package/dist/utils/session-title.js.map +0 -1
package/dist/ui/App.js CHANGED
@@ -13,6 +13,7 @@ import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTa
13
13
  import { playNotificationSound } from "../utils/sound.js";
14
14
  import {} from "@prestyj/ai";
15
15
  import { downscaleForPreview, extractMediaPaths } from "../utils/image.js";
16
+ import { buildSubAgentCompletionFollowUp, } from "../core/subagent-manager.js";
16
17
  import { useAgentLoop } from "./hooks/useAgentLoop.js";
17
18
  import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
18
19
  import { createWebSearchTool } from "../tools/web-search.js";
@@ -27,7 +28,6 @@ import { getAuthStorageKeys, getModel, getVideoByteLimit } from "../core/model-r
27
28
  import { SessionManager } from "../core/session-manager.js";
28
29
  import { log } from "../core/logger.js";
29
30
  import { getPendingUpdate, startPeriodicUpdateCheck, stopPeriodicUpdateCheck, } from "../core/auto-update.js";
30
- import { generateSessionTitle } from "../utils/session-title.js";
31
31
  import { SettingsManager } from "../core/settings-manager.js";
32
32
  import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
33
33
  import { isFirstTimeSetup, markSetupAudited, getAnnouncedLanguages, markLanguagesAnnounced, } from "../core/setup-history.js";
@@ -234,22 +234,14 @@ export function App(props) {
234
234
  // measured further down; 0 means "not measured yet" and disables the
235
235
  // oversized-item flush below.
236
236
  const liveLayoutRef = useRef({ columns, liveAreaRows: 0 });
237
- // Hoisted before terminal title hook so it can reference them
238
237
  const [lastUserMessage, setLastUserMessage] = useState("");
239
238
  // Bumped on every prompt submit; the fullscreen transcript scroll controller
240
239
  // watches this to snap back to the bottom so the newest output is visible.
241
240
  const [scrollResetToken, setScrollResetToken] = useState(0);
242
241
  const [exitPending, setExitPending] = useState(false);
243
242
  const [quittingSummary, setQuittingSummary] = useState(null);
244
- // Terminal title — updated later after agentLoop is created
245
- // (hoisted here so the hook is always called in the same order)
243
+ // Native terminal title keeps the active project visible outside the app frame.
246
244
  const [titleRunning, setTitleRunning] = useState(false);
247
- const [sessionTitle, setSessionTitle] = useState(() => props.sessionStore?.sessionTitle);
248
- const sessionTitleGeneratedRef = useRef(props.sessionStore?.sessionTitleGenerated ?? false);
249
- useTerminalTitle({
250
- isRunning: titleRunning,
251
- sessionTitle,
252
- });
253
245
  // Completed transcript rows are kept as durable session data but are no longer
254
246
  // rendered through Ink history. They are serialized once into real terminal
255
247
  // scrollback via terminalHistoryPrinter, while Ink owns only live rows and
@@ -353,6 +345,7 @@ export function App(props) {
353
345
  // Suppress "done" status when a plan overlay is about to open
354
346
  const planOverlayPendingRef = useRef(false);
355
347
  const [gitBranch, setGitBranch] = useState(null);
348
+ useTerminalTitle({ isRunning: titleRunning, cwd: displayedCwd, gitBranch });
356
349
  const [currentModel, setCurrentModel] = useState(props.model);
357
350
  const currentModelRef = useRef(props.model);
358
351
  const [currentProvider, setCurrentProvider] = useState(props.provider);
@@ -395,16 +388,13 @@ export function App(props) {
395
388
  const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
396
389
  const sessionPathRef = useRef(props.sessionStore?.sessionPath ?? props.sessionPath);
397
390
  const persistedIndexRef = useRef(messagesRef.current.length);
391
+ const turnMetricsRef = useRef(props.sessionStore?.turnMetrics ?? []);
398
392
  const sessionStatsRef = useRef(props.sessionStore?.sessionStats ??
399
393
  createSessionStats({ sessionId: props.sessionStore?.sessionId ?? props.sessionId }));
400
394
  const [idealReviewEnabled, setIdealReviewEnabled] = useState(props.sessionStore?.idealReviewEnabled ?? props.idealReviewEnabled ?? true);
401
395
  const idealReviewEnabledRef = useRef(idealReviewEnabled);
402
396
  const [autoApprovePlansEnabled, setAutoApprovePlansEnabled] = useState(props.sessionStore?.autoApprovePlans ?? props.autoApprovePlans ?? true);
403
397
  const autoApprovePlansEnabledRef = useRef(autoApprovePlansEnabled);
404
- /** Last actual API-reported input token count (from turn_end). */
405
- const lastActualTokensRef = useRef(0);
406
- /** Timestamp (ms) when lastActualTokensRef was last updated by turn_end. */
407
- const lastActualTokensTimestampRef = useRef(0);
408
398
  /**
409
399
  * Languages whose style packs are currently injected into the system prompt.
410
400
  * Grown by `maybeInjectLanguagePacks` after `write`/`bash` tool results when
@@ -568,10 +558,6 @@ export function App(props) {
568
558
  if (sessionStore)
569
559
  sessionStore.planSteps = planSteps;
570
560
  }, [planSteps, sessionStore]);
571
- useEffect(() => {
572
- if (sessionStore)
573
- sessionStore.sessionTitle = sessionTitle;
574
- }, [sessionTitle, sessionStore]);
575
561
  useEffect(() => {
576
562
  if (sessionStore)
577
563
  sessionStore.overlay = overlay;
@@ -609,9 +595,9 @@ export function App(props) {
609
595
  .map((key) => props.credentialsByProvider?.[key])
610
596
  .find((c) => c !== undefined);
611
597
  const activeApiKey = currentCreds?.accessToken ?? props.apiKey;
612
- const activeAccountId = currentCreds?.accountId ?? props.accountId;
613
- const activeProjectId = currentCreds?.projectId ?? props.projectId;
614
- const activeBaseUrl = currentProvider === "gemini" ? undefined : (currentCreds?.baseUrl ?? props.baseUrl);
598
+ const activeAccountId = currentCreds ? currentCreds.accountId : props.accountId;
599
+ const activeProjectId = currentCreds ? currentCreds.projectId : props.projectId;
600
+ const activeBaseUrl = currentProvider === "gemini" ? undefined : currentCreds ? currentCreds.baseUrl : props.baseUrl;
615
601
  const contextWindowOptions = useMemo(() => ({ provider: currentProvider, accountId: activeAccountId }), [currentProvider, activeAccountId]);
616
602
  // Load git branch — re-runs whenever the displayed cwd changes (e.g. when
617
603
  // a pixel fix moves the agent into a different project root).
@@ -790,16 +776,19 @@ export function App(props) {
790
776
  useEffect(() => {
791
777
  void applyLanguageDetectionRef.current("initial");
792
778
  }, []);
779
+ const rebindSubagentsAfterCompaction = useCallback((sessionId) => props.subAgentManager?.rebindParentSession(sessionId) ?? Promise.resolve(), [props.subAgentManager]);
793
780
  const { persistCompactedSession, persistNewMessages } = useSessionPersistence({
794
781
  sessionManagerRef,
795
782
  sessionPathRef,
796
783
  sessionStatsRef,
797
784
  persistedIndexRef,
798
785
  messagesRef,
786
+ turnMetricsRef,
799
787
  cwdRef,
800
788
  currentProvider,
801
789
  currentModel,
802
790
  sessionStore,
791
+ onCompactedSession: rebindSubagentsAfterCompaction,
803
792
  });
804
793
  /**
805
794
  * Run the language detector against the current cwd. If the detected set is a
@@ -835,10 +824,9 @@ export function App(props) {
835
824
  });
836
825
  }
837
826
  }, [props.settingsFile]);
838
- const { compactionAbortRef, compactConversation, transformContext } = useContextCompaction({
827
+ const { compactionAbortRef, compactConversation, transformContext, recordProviderUsage } = useContextCompaction({
839
828
  currentModel,
840
829
  currentProvider,
841
- maxTokens: props.maxTokens,
842
830
  authStorage: props.authStorage,
843
831
  contextWindowOptions,
844
832
  activeApiKey,
@@ -850,8 +838,6 @@ export function App(props) {
850
838
  approvedPlanPathRef,
851
839
  settingsRef,
852
840
  messagesRef,
853
- lastActualTokensRef,
854
- lastActualTokensTimestampRef,
855
841
  persistCompactedSession,
856
842
  });
857
843
  // ── Background task bar state (external store) ──────────
@@ -904,6 +890,8 @@ export function App(props) {
904
890
  projectId: activeProjectId,
905
891
  resolveCredentials,
906
892
  transformContext,
893
+ lspManager: props.lspManager,
894
+ reviewCoverageTracker: props.reviewCoverageTracker,
907
895
  getIdealReviewMessage: (stats, touchedFiles) => {
908
896
  if (!idealReviewEnabledRef.current)
909
897
  return null;
@@ -961,56 +949,7 @@ export function App(props) {
961
949
  // Rebuild system prompt to remove the completed plan from context
962
950
  void replaceSystemPrompt({ clearApprovedPlan: true });
963
951
  }
964
- // Generate session title after the first turn (background, best-effort)
965
- if (!sessionTitleGeneratedRef.current) {
966
- sessionTitleGeneratedRef.current = true;
967
- const msgs = messagesRef.current;
968
- // Find the first user message and first assistant text
969
- const userMsg = msgs.find((m) => m.role === "user");
970
- const assistantMsg = msgs.find((m) => m.role === "assistant");
971
- const userText = typeof userMsg?.content === "string"
972
- ? userMsg.content
973
- : Array.isArray(userMsg?.content)
974
- ? userMsg.content
975
- .filter((c) => c.type === "text")
976
- .map((c) => c.text)
977
- .join(" ")
978
- : "";
979
- const assistantText = typeof assistantMsg?.content === "string"
980
- ? assistantMsg.content
981
- : Array.isArray(assistantMsg?.content)
982
- ? assistantMsg.content
983
- .filter((c) => c.type === "text")
984
- .map((c) => c.text)
985
- .join(" ")
986
- : "";
987
- if (userText) {
988
- generateSessionTitle({
989
- provider: currentProvider,
990
- userMessage: userText,
991
- assistantPreview: assistantText.slice(0, 200),
992
- apiKey: activeApiKey,
993
- baseUrl: activeBaseUrl,
994
- accountId: activeAccountId,
995
- resolveCredentials,
996
- }).then((title) => {
997
- setSessionTitle(title);
998
- log("INFO", "title", `Session title generated: ${title}`);
999
- }, () => {
1000
- // Best-effort — silently ignore failures
1001
- });
1002
- }
1003
- }
1004
- }, [
1005
- persistNewMessages,
1006
- props.cwd,
1007
- props.skills,
1008
- currentProvider,
1009
- activeApiKey,
1010
- activeAccountId,
1011
- activeBaseUrl,
1012
- resolveCredentials,
1013
- ]),
952
+ }, [persistNewMessages, props.cwd, props.skills]),
1014
953
  onTurnText: useCallback((text, thinking, thinkingMs) => {
1015
954
  const hadStreamedAssistantFlush = streamedAssistantFlushRef.current.flushedChars > 0;
1016
955
  const unflushedAssistantText = text.slice(streamedAssistantFlushRef.current.flushedChars);
@@ -1490,22 +1429,39 @@ export function App(props) {
1490
1429
  return remaining;
1491
1430
  });
1492
1431
  }, [queueFlush]),
1493
- onTurnEnd: useCallback((turn, stopReason, usage) => {
1432
+ onTurnEnd: useCallback((turn, stopReason, usage, timing) => {
1433
+ recordProviderUsage(usage, messagesRef.current);
1494
1434
  recordTurnEnd(sessionStatsRef.current, usage);
1435
+ const metric = {
1436
+ version: 1,
1437
+ turn,
1438
+ provider: currentProvider,
1439
+ model: currentModel,
1440
+ stopReason,
1441
+ usage: { ...usage },
1442
+ timing: { ...timing },
1443
+ cost: {
1444
+ status: "unavailable",
1445
+ reason: "No authoritative effective-dated provider pricing is available",
1446
+ },
1447
+ };
1448
+ turnMetricsRef.current.push(metric);
1449
+ if (sessionStore)
1450
+ sessionStore.turnMetrics = [...turnMetricsRef.current];
1451
+ const metricSessionPath = sessionPathRef.current;
1452
+ const metricManager = sessionManagerRef.current;
1453
+ if (metricSessionPath && metricManager) {
1454
+ void metricManager.appendTurnMetric(metricSessionPath, metric);
1455
+ }
1495
1456
  log("INFO", "turn", `Turn ${turn} ended`, {
1496
1457
  stopReason,
1497
1458
  inputTokens: String(usage.inputTokens),
1498
1459
  outputTokens: String(usage.outputTokens),
1499
1460
  ...(usage.cacheRead != null && { cacheRead: String(usage.cacheRead) }),
1500
1461
  ...(usage.cacheWrite != null && { cacheWrite: String(usage.cacheWrite) }),
1462
+ providerDurationMs: String(timing.providerDurationMs),
1463
+ ...(timing.ttftMs != null && { ttftMs: String(timing.ttftMs) }),
1501
1464
  });
1502
- // Track actual token count for compaction decisions.
1503
- // Anthropic has separate input/output limits — only count input.
1504
- // All other providers share the context window — count both.
1505
- const inputContext = usage.inputTokens + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
1506
- lastActualTokensRef.current =
1507
- currentProvider === "anthropic" ? inputContext : inputContext + usage.outputTokens;
1508
- lastActualTokensTimestampRef.current = Date.now();
1509
1465
  // For tool-only turns (no text), flush completed items to finalized
1510
1466
  // history so liveItems doesn't grow unbounded across consecutive turns.
1511
1467
  setLiveItems((prev) => {
@@ -1515,7 +1471,7 @@ export function App(props) {
1515
1471
  }
1516
1472
  return remaining;
1517
1473
  });
1518
- }, [queueFlush]),
1474
+ }, [currentModel, currentProvider, queueFlush, recordProviderUsage, sessionStore]),
1519
1475
  onDone: useCallback((durationMs, toolsUsed, runStats) => {
1520
1476
  log("INFO", "agent", `Agent done`, {
1521
1477
  duration: `${durationMs}ms`,
@@ -1688,6 +1644,9 @@ export function App(props) {
1688
1644
  // natural completion boundary regardless. The stuck-guard caps
1689
1645
  // nudges per step so a genuinely blocked agent surfaces.
1690
1646
  getFollowUpMessages: useCallback(() => {
1647
+ const childCompletionFollowUp = buildSubAgentCompletionFollowUp(props.subAgentManager);
1648
+ if (childCompletionFollowUp)
1649
+ return childCompletionFollowUp;
1691
1650
  const steps = planStepsRef.current;
1692
1651
  if (steps.length === 0 || !approvedPlanPathRef.current)
1693
1652
  return null;
@@ -1711,7 +1670,7 @@ export function App(props) {
1711
1670
  `or you genuinely need user input.`,
1712
1671
  },
1713
1672
  ];
1714
- }, []),
1673
+ }, [props.subAgentManager]),
1715
1674
  onRetry: useCallback(() => {
1716
1675
  // Roll back any pending progressive flushes from the aborted attempt.
1717
1676
  // Without this, a stall retry regenerates the preamble and the old
@@ -2251,8 +2210,6 @@ export function App(props) {
2251
2210
  persistedIndexRef.current = messagesRef.current.length;
2252
2211
  })();
2253
2212
  agentLoop.reset();
2254
- setSessionTitle(undefined);
2255
- sessionTitleGeneratedRef.current = false;
2256
2213
  setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
2257
2214
  },
2258
2215
  openThemeSelector: () => setOverlay("theme"),
@@ -2786,6 +2743,10 @@ export function App(props) {
2786
2743
  try {
2787
2744
  const session = await sm.create(taskCwd, currentProvider, currentModel);
2788
2745
  newSessionPath = session.path;
2746
+ sessionStatsRef.current.sessionId = session.id;
2747
+ if (props.sessionStore)
2748
+ props.sessionStore.sessionId = session.id;
2749
+ await props.subAgentManager?.resetParentSession(session.id);
2789
2750
  log("INFO", "tasks", "New session for task", { path: session.path });
2790
2751
  }
2791
2752
  catch {
@@ -2811,22 +2772,27 @@ export function App(props) {
2811
2772
  agentLoop.reset();
2812
2773
  persistedIndexRef.current = messagesRef.current.length;
2813
2774
  const sm = sessionManagerRef.current;
2814
- if (sm) {
2815
- void sm.create(taskCwd, currentProvider, currentModel).then((session) => {
2816
- sessionPathRef.current = session.path;
2817
- log("INFO", "tasks", "New session for task", { path: session.path });
2818
- });
2819
- }
2820
2775
  const taskItem = { kind: "task", title, id: getId() };
2821
2776
  setLastUserMessage(title);
2822
2777
  setDoneStatus(null);
2823
2778
  setLiveItems([taskItem]);
2824
- void agentLoop.run(fullPrompt).catch((err) => {
2825
- if (agentLoop.isRunning) {
2826
- agentLoop.reset();
2779
+ void (async () => {
2780
+ try {
2781
+ if (sm) {
2782
+ const session = await sm.create(taskCwd, currentProvider, currentModel);
2783
+ sessionPathRef.current = session.path;
2784
+ sessionStatsRef.current.sessionId = session.id;
2785
+ await props.subAgentManager?.resetParentSession(session.id);
2786
+ log("INFO", "tasks", "New session for task", { path: session.path });
2787
+ }
2788
+ await agentLoop.run(fullPrompt);
2827
2789
  }
2828
- setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
2829
- });
2790
+ catch (err) {
2791
+ if (agentLoop.isRunning)
2792
+ agentLoop.reset();
2793
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
2794
+ }
2795
+ })();
2830
2796
  }, [agentLoop, currentModel, currentProvider, props]);
2831
2797
  // Keep the ref in sync so stale closures (task picker, onDone run-all) can
2832
2798
  // start a task without being recreated each render.
@@ -3408,6 +3374,10 @@ export function App(props) {
3408
3374
  if (sm) {
3409
3375
  const s = await sm.create(props.cwd, currentProvider, currentModel);
3410
3376
  newSessionPath = s.path;
3377
+ sessionStatsRef.current.sessionId = s.id;
3378
+ if (props.sessionStore)
3379
+ props.sessionStore.sessionId = s.id;
3380
+ await props.subAgentManager?.resetParentSession(s.id);
3411
3381
  }
3412
3382
  if (props.resetUI && props.sessionStore) {
3413
3383
  // Clear the overlay so the new mount lands on the chat,