@gajae-code/coding-agent 0.11.1 → 0.11.3

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 (413) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/launch-disposition.d.ts +23 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +141 -11
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  54. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  55. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  56. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  57. package/dist/types/goals/runtime.d.ts +1 -0
  58. package/dist/types/goals/state.d.ts +8 -0
  59. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  60. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  61. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  62. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  63. package/dist/types/hindsight/state.d.ts +11 -4
  64. package/dist/types/lsp/lspmux.d.ts +5 -3
  65. package/dist/types/lsp/path-trust.d.ts +1 -0
  66. package/dist/types/main.d.ts +4 -2
  67. package/dist/types/memories/storage.d.ts +1 -0
  68. package/dist/types/modes/action-registry.d.ts +29 -0
  69. package/dist/types/modes/components/chrome.d.ts +14 -0
  70. package/dist/types/modes/components/command-palette.d.ts +27 -0
  71. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  72. package/dist/types/modes/components/index.d.ts +2 -1
  73. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  74. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  75. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  76. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  77. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  78. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  79. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  80. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  81. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  82. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  83. package/dist/types/modes/components/status-line.d.ts +1 -94
  84. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  85. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  86. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  87. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  88. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  89. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  90. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  91. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  92. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  93. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  94. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  95. package/dist/types/modes/interactive-mode.d.ts +19 -17
  96. package/dist/types/modes/jobs-observer.d.ts +2 -0
  97. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  98. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  99. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  100. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  101. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  102. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  103. package/dist/types/modes/types.d.ts +20 -13
  104. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  105. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  106. package/dist/types/sdk/broker/broker.d.ts +78 -12
  107. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  108. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  109. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  110. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  111. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  112. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  113. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  114. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  117. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  118. package/dist/types/sdk/bus/index.d.ts +29 -0
  119. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  120. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  121. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  122. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  123. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  124. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  125. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  126. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  127. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  128. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  129. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  130. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  131. package/dist/types/sdk/host/host.d.ts +2 -0
  132. package/dist/types/sdk/index.d.ts +1 -0
  133. package/dist/types/sdk/session-directory.d.ts +56 -0
  134. package/dist/types/sdk/session.d.ts +2 -0
  135. package/dist/types/session/agent-session.d.ts +82 -47
  136. package/dist/types/session/agent-storage.d.ts +7 -0
  137. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  138. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  139. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  140. package/dist/types/session/messages.d.ts +16 -0
  141. package/dist/types/session/session-manager-internal.d.ts +14 -0
  142. package/dist/types/session/session-manager.d.ts +82 -12
  143. package/dist/types/session/session-storage.d.ts +43 -3
  144. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  145. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  146. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  147. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  148. package/dist/types/system-prompt.d.ts +2 -11
  149. package/dist/types/task/executor.d.ts +3 -1
  150. package/dist/types/thinking.d.ts +7 -0
  151. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  152. package/dist/types/tools/ask.d.ts +64 -2
  153. package/dist/types/tools/bash.d.ts +3 -0
  154. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  155. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  156. package/dist/types/tools/cron.d.ts +2 -0
  157. package/dist/types/tools/fetch.d.ts +1 -0
  158. package/dist/types/tools/index.d.ts +30 -15
  159. package/dist/types/tools/output-meta.d.ts +1 -0
  160. package/dist/types/tools/read.d.ts +5 -0
  161. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  162. package/dist/types/tools/subagent.d.ts +1 -0
  163. package/dist/types/utils/event-bus.d.ts +1 -1
  164. package/dist/types/utils/file-mentions.d.ts +3 -3
  165. package/dist/types/web/search/index.d.ts +3 -1
  166. package/package.json +21 -9
  167. package/scripts/build-sdk-package-smoke.ts +34 -1
  168. package/scripts/generate-hotkeys-docs.ts +74 -0
  169. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  170. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  171. package/src/async/job-manager.ts +49 -6
  172. package/src/cli/config-cli.ts +75 -2
  173. package/src/cli/launch-disposition.ts +42 -0
  174. package/src/cli/notify-cli.ts +92 -26
  175. package/src/cli/session-picker.ts +20 -3
  176. package/src/cli/setup-cli.ts +1 -1
  177. package/src/cli.ts +1 -1
  178. package/src/commands/config.ts +1 -1
  179. package/src/commands/harness.ts +34 -106
  180. package/src/commands/launch.ts +6 -4
  181. package/src/commands/mcp-serve.ts +85 -8
  182. package/src/commands/mcp.ts +2 -2
  183. package/src/commands/notify.ts +8 -4
  184. package/src/commands/sdk.ts +61 -40
  185. package/src/commands/ultragoal.ts +4 -1
  186. package/src/config/atomic-yaml-patch.ts +80 -13
  187. package/src/config/file-lock.ts +105 -43
  188. package/src/config/keybindings.ts +64 -20
  189. package/src/config/model-auth.ts +9 -0
  190. package/src/config/model-bindings-applier.ts +139 -0
  191. package/src/config/model-discovery-manager.ts +224 -0
  192. package/src/config/model-equivalence.ts +66 -0
  193. package/src/config/model-registry.ts +169 -302
  194. package/src/config/model-resolver.ts +361 -369
  195. package/src/config/models-config-schema.ts +5 -4
  196. package/src/config/settings-schema.ts +214 -12
  197. package/src/config/settings.ts +458 -482
  198. package/src/coordinator-mcp/policy.ts +76 -11
  199. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  200. package/src/coordinator-mcp/question-state.ts +778 -0
  201. package/src/coordinator-mcp/server.ts +1702 -238
  202. package/src/debug/index.ts +1 -1
  203. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  204. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  205. package/src/discovery/helpers.ts +1 -50
  206. package/src/edit/index.ts +64 -0
  207. package/src/eval/py/env.ts +26 -0
  208. package/src/eval/py/kernel.ts +5 -3
  209. package/src/extensibility/extensions/loader.ts +9 -1
  210. package/src/extensibility/extensions/runner.ts +12 -3
  211. package/src/extensibility/extensions/types.ts +39 -1
  212. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  213. package/src/extensibility/hooks/runner.ts +2 -2
  214. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  215. package/src/extensibility/shared-events.ts +2 -0
  216. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  217. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  218. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  219. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  220. package/src/gjc-runtime/launch-tmux.ts +2 -5
  221. package/src/gjc-runtime/launch-worktree.ts +5 -5
  222. package/src/gjc-runtime/linux-proc.ts +71 -0
  223. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  224. package/src/gjc-runtime/session-resolution.ts +18 -3
  225. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  226. package/src/gjc-runtime/state-migrations.ts +1 -1
  227. package/src/gjc-runtime/state-runtime.ts +327 -249
  228. package/src/gjc-runtime/state-writer.ts +36 -40
  229. package/src/gjc-runtime/team-launch.ts +319 -0
  230. package/src/gjc-runtime/team-notify.ts +326 -0
  231. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  232. package/src/gjc-runtime/team-store.ts +828 -0
  233. package/src/gjc-runtime/team-workers.ts +962 -0
  234. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  235. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  236. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  237. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  238. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  239. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  240. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  241. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  242. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  243. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  244. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  245. package/src/goals/continuation-timeout-guard.ts +79 -0
  246. package/src/goals/runtime.ts +7 -5
  247. package/src/goals/state.ts +18 -0
  248. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  249. package/src/harness-control-plane/phase-rollup.ts +4 -7
  250. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  251. package/src/harness-control-plane/receipt-spool.ts +51 -7
  252. package/src/harness-control-plane/session-lease.ts +12 -130
  253. package/src/hindsight/backend.ts +26 -27
  254. package/src/hindsight/state.ts +101 -32
  255. package/src/hooks/skill-state.ts +17 -11
  256. package/src/internal-urls/docs-index.generated.ts +21 -18
  257. package/src/lsp/client.ts +1 -1
  258. package/src/lsp/config.ts +145 -26
  259. package/src/lsp/index.ts +1 -1
  260. package/src/lsp/lspmux.ts +39 -10
  261. package/src/lsp/path-trust.ts +81 -0
  262. package/src/main.ts +127 -30
  263. package/src/memories/index.ts +1 -0
  264. package/src/memories/storage.ts +6 -2
  265. package/src/modes/action-registry.ts +165 -0
  266. package/src/modes/components/chrome.ts +40 -0
  267. package/src/modes/components/command-palette.ts +168 -0
  268. package/src/modes/components/custom-editor.ts +1 -1
  269. package/src/modes/components/custom-provider-wizard.ts +51 -14
  270. package/src/modes/components/index.ts +2 -1
  271. package/src/modes/components/pet-selector.ts +11 -17
  272. package/src/modes/components/plan-preview-overlay.ts +295 -0
  273. package/src/modes/components/queue-mode-selector.ts +10 -27
  274. package/src/modes/components/queue-pane.ts +79 -0
  275. package/src/modes/components/read-tool-group.ts +13 -0
  276. package/src/modes/components/session-observer-overlay.ts +207 -772
  277. package/src/modes/components/sessions-dashboard.ts +181 -0
  278. package/src/modes/components/settings-selector.ts +1 -1
  279. package/src/modes/components/show-images-selector.ts +10 -24
  280. package/src/modes/components/status-line/types.ts +1 -1
  281. package/src/modes/components/status-line.ts +1 -969
  282. package/src/modes/components/tasks-pane.ts +73 -0
  283. package/src/modes/components/theme-selector.ts +11 -31
  284. package/src/modes/components/tool-execution.ts +22 -0
  285. package/src/modes/components/tool-status-header.ts +1056 -0
  286. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  287. package/src/modes/controllers/btw-controller.ts +1 -0
  288. package/src/modes/controllers/event-controller.ts +3 -2
  289. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  290. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  291. package/src/modes/controllers/input-controller.ts +764 -229
  292. package/src/modes/controllers/mode-gate.ts +24 -0
  293. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  294. package/src/modes/controllers/selector-controller.ts +225 -10
  295. package/src/modes/controllers/stt-controller.ts +125 -0
  296. package/src/modes/interactive-mode.ts +289 -1150
  297. package/src/modes/jobs-observer.ts +3 -0
  298. package/src/modes/prompt-action-autocomplete.ts +7 -0
  299. package/src/modes/runtime-init.ts +8 -0
  300. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  301. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  302. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  303. package/src/modes/tasks-aggregator.ts +199 -0
  304. package/src/modes/transcript-item-registry.ts +229 -0
  305. package/src/modes/types.ts +45 -18
  306. package/src/modes/utils/ui-helpers.ts +17 -9
  307. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  308. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  309. package/src/prompts/agents/architect.md +2 -8
  310. package/src/prompts/agents/critic.md +2 -8
  311. package/src/prompts/agents/executor.md +3 -0
  312. package/src/prompts/agents/planner.md +4 -12
  313. package/src/prompts/system/custom-system-prompt.md +1 -23
  314. package/src/prompts/system/plan-mode-approved.md +8 -0
  315. package/src/prompts/system/system-prompt.md +17 -96
  316. package/src/prompts/tools/bash.md +4 -4
  317. package/src/prompts/tools/browser.md +15 -52
  318. package/src/prompts/tools/find.md +2 -2
  319. package/src/prompts/tools/hashline.md +13 -55
  320. package/src/prompts/tools/read.md +3 -6
  321. package/src/prompts/tools/search-tool-bm25.md +0 -2
  322. package/src/prompts/tools/search.md +2 -4
  323. package/src/prompts/tools/skill-discovery.md +1 -2
  324. package/src/prompts/tools/task.md +1 -1
  325. package/src/runtime-mcp/manager.ts +1 -1
  326. package/src/sdk/broker/broker.ts +206 -78
  327. package/src/sdk/broker/discovery.ts +1 -1
  328. package/src/sdk/broker/ensure.ts +3 -1
  329. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  330. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  331. package/src/sdk/broker/lifecycle.ts +1399 -142
  332. package/src/sdk/broker/process-incarnation.ts +5 -13
  333. package/src/sdk/broker/session-index.ts +145 -56
  334. package/src/sdk/broker/state-version.ts +21 -0
  335. package/src/sdk/broker/transport.ts +1 -1
  336. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  337. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  338. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  339. package/src/sdk/bus/config-commands.ts +5 -4
  340. package/src/sdk/bus/config.ts +98 -0
  341. package/src/sdk/bus/conversation-store.ts +5 -29
  342. package/src/sdk/bus/daemon-paths.ts +2 -0
  343. package/src/sdk/bus/html-format.ts +20 -3
  344. package/src/sdk/bus/index.ts +782 -94
  345. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  346. package/src/sdk/bus/natives-augment.d.ts +26 -1
  347. package/src/sdk/bus/notification-service.ts +168 -20
  348. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  349. package/src/sdk/bus/recent-activity.ts +188 -74
  350. package/src/sdk/bus/rich-document.ts +258 -0
  351. package/src/sdk/bus/rich-render.ts +62 -70
  352. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  353. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  354. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  355. package/src/sdk/bus/threaded-inbound.ts +8 -3
  356. package/src/sdk/bus/threaded-render.ts +57 -1
  357. package/src/sdk/bus/topic-registry.ts +120 -13
  358. package/src/sdk/host/control/dispatch.ts +14 -2
  359. package/src/sdk/host/control/operations.ts +6 -1
  360. package/src/sdk/host/host.ts +44 -2
  361. package/src/sdk/host/query/handlers.ts +20 -22
  362. package/src/sdk/host/query/revision-store.ts +80 -57
  363. package/src/sdk/index.ts +1 -0
  364. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  365. package/src/sdk/session-directory.ts +141 -0
  366. package/src/sdk/session.ts +462 -480
  367. package/src/session/agent-session.ts +1700 -583
  368. package/src/session/agent-storage.ts +18 -0
  369. package/src/session/cache-economics.ts +25 -8
  370. package/src/session/fallback-chain-controller.ts +53 -3
  371. package/src/session/internal/managed-session-scope.ts +2226 -0
  372. package/src/session/internal/managed-session-storage.ts +453 -0
  373. package/src/session/messages.ts +91 -2
  374. package/src/session/session-manager-internal.ts +33 -0
  375. package/src/session/session-manager.ts +1112 -333
  376. package/src/session/session-storage.ts +444 -39
  377. package/src/session/volatile-context-pruning.ts +78 -0
  378. package/src/setup/credential-auto-import.ts +2 -1
  379. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  380. package/src/setup/provider-onboarding.ts +36 -61
  381. package/src/setup/provider-presets.json +5 -0
  382. package/src/skill-state/active-state.ts +3 -13
  383. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  384. package/src/skill-state/workflow-state-contract.ts +6 -41
  385. package/src/slash-commands/builtin-registry.ts +46 -11
  386. package/src/system-prompt.ts +37 -44
  387. package/src/task/agents.ts +19 -1
  388. package/src/task/executor.ts +90 -6
  389. package/src/task/index.ts +36 -21
  390. package/src/thinking.ts +21 -6
  391. package/src/tool-discovery/tool-index.ts +2 -134
  392. package/src/tools/ask.ts +157 -21
  393. package/src/tools/ast-edit.ts +1 -1
  394. package/src/tools/bash.ts +38 -0
  395. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  396. package/src/tools/browser/tab-supervisor.ts +282 -36
  397. package/src/tools/computer.ts +10 -3
  398. package/src/tools/cron.ts +30 -16
  399. package/src/tools/eval.ts +1 -2
  400. package/src/tools/fetch.ts +11 -4
  401. package/src/tools/index.ts +76 -24
  402. package/src/tools/output-meta.ts +19 -7
  403. package/src/tools/read.ts +219 -64
  404. package/src/tools/resource-gc.ts +2 -2
  405. package/src/tools/search-tool-bm25.ts +11 -74
  406. package/src/tools/subagent.ts +43 -2
  407. package/src/utils/event-bus.ts +9 -4
  408. package/src/utils/file-mentions.ts +4 -4
  409. package/src/web/search/index.ts +3 -3
  410. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  411. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  412. package/src/discovery/plugin-dir-roots.ts +0 -28
  413. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -1,969 +1 @@
1
- import * as fs from "node:fs";
2
-
3
- import { type Component, truncateToWidth, visibleWidth } from "@gajae-code/tui";
4
- import { formatCount, getProjectDir } from "@gajae-code/utils";
5
- import { $ } from "bun";
6
- import { settings } from "../../config/settings";
7
- import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
8
- import { theme } from "../../modes/theme/theme";
9
- import type { AgentSession } from "../../session/agent-session";
10
- import { readVisibleSkillActiveState, type SkillActiveEntry } from "../../skill-state/active-state";
11
- import * as git from "../../utils/git";
12
- import { getSessionAccentAnsi, getSessionAccentHex } from "../../utils/session-color";
13
- import { EMPTY_JOBS_SNAPSHOT, type JobsSnapshot } from "../jobs-observer";
14
- import { sanitizeStatusText } from "../shared";
15
- import { renderSkillHudBar } from "./skill-hud/render";
16
- import {
17
- canReuseCachedPr,
18
- createPrCacheContext,
19
- isSamePrCacheContext,
20
- type PrCacheContext,
21
- resolveCurrentBranch,
22
- } from "./status-line/git-utils";
23
- import { getPreset } from "./status-line/presets";
24
- import { renderSegment, type SegmentContext } from "./status-line/segments";
25
- import { getSeparator } from "./status-line/separators";
26
- import { calculateTokensPerSecond } from "./status-line/token-rate";
27
- import type { SeparatorDef } from "./status-line/types";
28
-
29
- export interface StatusLineSegmentOptions {
30
- model?: { showThinkingLevel?: boolean; showContextPercent?: boolean };
31
- path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
32
- git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
33
- time?: { format?: "12h" | "24h"; showSeconds?: boolean };
34
- usage?: { mode?: "used" | "remaining" };
35
- }
36
-
37
- export interface StatusLineSettings {
38
- preset?: StatusLinePreset;
39
- leftSegments?: StatusLineSegmentId[];
40
- rightSegments?: StatusLineSegmentId[];
41
- separator?: StatusLineSeparatorStyle;
42
- segmentOptions?: StatusLineSegmentOptions;
43
- previewHighlightSegment?: StatusLineSegmentId;
44
- showHookStatus?: boolean;
45
- showSkillHud?: boolean;
46
- sessionAccent?: boolean;
47
- maxRows?: number;
48
- }
49
-
50
- export interface StatusLineComponentOptions {
51
- version?: string;
52
- }
53
-
54
- interface CollectedStatusSegments {
55
- ctx: SegmentContext;
56
- separatorDef: SeparatorDef;
57
- bgAnsi: string;
58
- fgAnsi: string;
59
- sepAnsi: string;
60
- left: string[];
61
- leftSegIds: StatusLineSegmentId[];
62
- right: string[];
63
- previewHighlightSegment: StatusLineSegmentId | undefined;
64
- sessionAccent: boolean | undefined;
65
- leftSepWidth: number;
66
- rightSepWidth: number;
67
- leftCapWidth: number;
68
- rightCapWidth: number;
69
- }
70
-
71
- // StatusLineComponent
72
- // ═══════════════════════════════════════════════════════════════════════════
73
-
74
- export class StatusLineComponent implements Component {
75
- #settings: StatusLineSettings = {};
76
- #cachedBranch: string | null | undefined = undefined;
77
- #cachedBranchRepoId: string | null | undefined = undefined;
78
- #branchProjectDir: string | undefined;
79
- #branchLastFetch = 0;
80
- #branchInFlight = false;
81
- #gitWatcher: fs.FSWatcher | null = null;
82
- #onBranchChange: (() => void) | null = null;
83
- #autoCompactEnabled: boolean = true;
84
- #hookStatuses: Map<string, string> = new Map();
85
- #subagentCount: number = 0;
86
- #jobs: JobsSnapshot = EMPTY_JOBS_SNAPSHOT;
87
- #sessionStartTime: number = Date.now();
88
- #planModeStatus: { enabled: boolean; paused: boolean } | null = null;
89
- #goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
90
- #skillHudEntries: SkillActiveEntry[] = [];
91
- #skillHudLastFetch = 0;
92
- #skillHudInFlight = false;
93
- #version: string | undefined;
94
- #resolvedSettingsCache:
95
- | (Required<Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">> &
96
- StatusLineSettings)
97
- | undefined;
98
- #resolvedSettingsFingerprint: string | undefined;
99
- #renderedRowsCache: { key: string; rows: string[] } | undefined;
100
- #renderedRowsCacheHits = 0;
101
- #renderedRowsCacheMisses = 0;
102
-
103
- // Git status caching (1s TTL)
104
- #cachedGitStatus: { staged: number; unstaged: number; untracked: number } | null = null;
105
- #gitStatusLastFetch = 0;
106
- #gitStatusInFlight = false;
107
-
108
- // PR lookup caching (invalidated on branch/repo context changes)
109
- #cachedPr: { number: number; url: string } | null | undefined = undefined;
110
- #cachedPrContext: PrCacheContext | undefined = undefined;
111
- #prLookupInFlight = false;
112
- #defaultBranch?: string;
113
- #lastTokensPerSecond: number | null = null;
114
- #lastTokensPerSecondTimestamp: number | null = null;
115
-
116
- // Provider usage caching (5-min TTL, OAuth/sub only)
117
- #cachedUsage: SegmentContext["usage"] = null;
118
- #usageFetchedAt = 0;
119
- #usageInFlight = false;
120
-
121
- constructor(
122
- private readonly session: AgentSession,
123
- options: StatusLineComponentOptions = {},
124
- ) {
125
- this.#settings = {
126
- preset: settings.get("statusLine.preset"),
127
- leftSegments: settings.get("statusLine.leftSegments"),
128
- rightSegments: settings.get("statusLine.rightSegments"),
129
- separator: settings.get("statusLine.separator"),
130
- showHookStatus: settings.get("statusLine.showHookStatus"),
131
- showSkillHud: settings.get("statusLine.showSkillHud"),
132
- segmentOptions: settings.getGroup("statusLine").segmentOptions,
133
- sessionAccent: settings.get("statusLine.sessionAccent"),
134
- maxRows: settings.get("statusLine.maxRows"),
135
- };
136
- this.#version = options.version?.trim() || undefined;
137
- }
138
-
139
- updateSettings(settings: StatusLineSettings): void {
140
- this.#settings = { ...this.#settings, previewHighlightSegment: undefined, ...settings };
141
- }
142
-
143
- setAutoCompactEnabled(enabled: boolean): void {
144
- this.#autoCompactEnabled = enabled;
145
- }
146
-
147
- setSubagentCount(count: number): void {
148
- this.#subagentCount = count;
149
- }
150
-
151
- setJobs(jobs: JobsSnapshot): void {
152
- this.#jobs = jobs;
153
- }
154
-
155
- setSessionStartTime(time: number): void {
156
- this.#sessionStartTime = time;
157
- }
158
-
159
- setPlanModeStatus(status: { enabled: boolean; paused: boolean } | undefined): void {
160
- this.#planModeStatus = status ?? null;
161
- }
162
-
163
- setGoalModeStatus(status: { enabled: boolean; paused: boolean } | undefined): void {
164
- this.#goalModeStatus = status ?? null;
165
- }
166
-
167
- setSkillHudEntriesForTest(entries: SkillActiveEntry[]): void {
168
- this.#skillHudEntries = entries;
169
- this.#skillHudLastFetch = Date.now();
170
- }
171
-
172
- setHookStatus(key: string, text: string | undefined): void {
173
- if (text === undefined) {
174
- this.#hookStatuses.delete(key);
175
- } else {
176
- this.#hookStatuses.set(key, text);
177
- }
178
- }
179
-
180
- watchBranch(onBranchChange: () => void): void {
181
- this.#onBranchChange = onBranchChange;
182
- this.#setupGitWatcher();
183
- }
184
-
185
- #setupGitWatcher(): void {
186
- if (this.#gitWatcher) {
187
- this.#gitWatcher.close();
188
- this.#gitWatcher = null;
189
- }
190
-
191
- const gitHeadPath = git.repo.resolveSync(getProjectDir())?.headPath ?? null;
192
- if (!gitHeadPath) return;
193
-
194
- try {
195
- this.#gitWatcher = fs.watch(gitHeadPath, () => {
196
- this.#invalidateGitCaches();
197
- if (this.#onBranchChange) {
198
- this.#onBranchChange();
199
- }
200
- });
201
- } catch {
202
- this.#invalidateGitCaches();
203
- }
204
- }
205
-
206
- dispose(): void {
207
- if (this.#gitWatcher) {
208
- this.#gitWatcher.close();
209
- this.#gitWatcher = null;
210
- }
211
- }
212
-
213
- invalidate(): void {
214
- this.#invalidateGitCaches();
215
- }
216
-
217
- #invalidateGitCaches(): void {
218
- this.#cachedBranch = undefined;
219
- this.#cachedBranchRepoId = undefined;
220
- this.#branchProjectDir = undefined;
221
- this.#cachedPrContext = undefined;
222
- this.#branchLastFetch = 0;
223
- this.#branchInFlight = false;
224
- this.#renderedRowsCache = undefined;
225
- }
226
- #getCurrentBranch(): string | null {
227
- const now = Date.now();
228
- const projectDir = getProjectDir();
229
- const withinTtl =
230
- this.#cachedBranch !== undefined &&
231
- this.#branchProjectDir === projectDir &&
232
- Date.now() - this.#branchLastFetch < 1000;
233
- if (withinTtl || this.#branchInFlight) {
234
- return this.#cachedBranch ?? null;
235
- }
236
-
237
- this.#branchInFlight = true;
238
- try {
239
- const current = resolveCurrentBranch(projectDir);
240
- this.#cachedBranchRepoId = current.repoId;
241
- this.#cachedBranch = current.branch;
242
- this.#branchProjectDir = projectDir;
243
- return this.#cachedBranch ?? null;
244
- } catch {
245
- this.#cachedBranchRepoId = null;
246
- this.#cachedBranch = null;
247
- this.#branchProjectDir = projectDir;
248
- return null;
249
- } finally {
250
- this.#branchLastFetch = now;
251
- this.#branchInFlight = false;
252
- }
253
- }
254
-
255
- #isDefaultBranch(branch: string): boolean {
256
- if (this.#defaultBranch === undefined) {
257
- this.#defaultBranch = "main";
258
- (async () => {
259
- const resolved = await git.branch.default(getProjectDir());
260
- if (resolved) {
261
- this.#defaultBranch = resolved;
262
- if (this.#onBranchChange) {
263
- this.#onBranchChange();
264
- }
265
- }
266
- })();
267
- }
268
- return branch === this.#defaultBranch;
269
- }
270
-
271
- #getGitStatus(): { staged: number; unstaged: number; untracked: number } | null {
272
- if (this.#gitStatusInFlight || Date.now() - this.#gitStatusLastFetch < 1000) {
273
- return this.#cachedGitStatus;
274
- }
275
-
276
- this.#gitStatusInFlight = true;
277
-
278
- (async () => {
279
- try {
280
- this.#cachedGitStatus = await git.status.summary(getProjectDir());
281
- } catch {
282
- this.#cachedGitStatus = null;
283
- } finally {
284
- this.#gitStatusLastFetch = Date.now();
285
- this.#gitStatusInFlight = false;
286
- }
287
- })();
288
-
289
- return this.#cachedGitStatus;
290
- }
291
-
292
- #lookupPr(): { number: number; url: string } | null {
293
- const branch = this.#getCurrentBranch();
294
- const currentContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : null;
295
-
296
- if (canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
297
- return this.#cachedPr ?? null;
298
- }
299
-
300
- const stalePr = this.#cachedPr;
301
-
302
- // Don't look up if no branch, detached HEAD, default branch, or already in flight
303
- if (!branch || branch === "detached" || this.#isDefaultBranch(branch) || this.#prLookupInFlight) {
304
- return stalePr ?? null;
305
- }
306
-
307
- this.#prLookupInFlight = true;
308
- const lookupContext = currentContext;
309
-
310
- // Fire async lookup, keep stale value visible until resolved
311
- (async () => {
312
- // Helper: only write cache if branch/repo context hasn't changed since launch
313
- const setCachedPr = (value: { number: number; url: string } | null) => {
314
- const latestBranch = this.#getCurrentBranch();
315
- const latestContext = latestBranch
316
- ? createPrCacheContext(latestBranch, this.#cachedBranchRepoId ?? null)
317
- : undefined;
318
- if (lookupContext && isSamePrCacheContext(latestContext, lookupContext)) {
319
- this.#cachedPr = value;
320
- this.#cachedPrContext = lookupContext;
321
- }
322
- };
323
- try {
324
- // Requires `gh repo set-default` to be configured; fails gracefully if not
325
- const result = await $`gh pr view --json number,url`.quiet().nothrow();
326
- if (result.exitCode !== 0) {
327
- setCachedPr(null);
328
- return;
329
- }
330
- const pr = JSON.parse(result.stdout.toString()) as { number: number; url: string };
331
- if (typeof pr.number === "number") {
332
- setCachedPr({ number: pr.number, url: pr.url });
333
- } else {
334
- setCachedPr(null);
335
- }
336
- } catch {
337
- setCachedPr(null);
338
- } finally {
339
- this.#prLookupInFlight = false;
340
- if (this.#onBranchChange) {
341
- this.#onBranchChange();
342
- }
343
- }
344
- })();
345
-
346
- return stalePr ?? null;
347
- }
348
-
349
- #getTokensPerSecond(): number | null {
350
- let lastAssistantTimestamp: number | null = null;
351
- for (let i = this.session.state.messages.length - 1; i >= 0; i--) {
352
- const message = this.session.state.messages[i];
353
- if (message?.role === "assistant") {
354
- lastAssistantTimestamp = message.timestamp;
355
- break;
356
- }
357
- }
358
-
359
- if (lastAssistantTimestamp === null) {
360
- this.#lastTokensPerSecond = null;
361
- this.#lastTokensPerSecondTimestamp = null;
362
- return null;
363
- }
364
-
365
- const rate = calculateTokensPerSecond(this.session.state.messages, this.session.isStreaming);
366
- if (rate !== null) {
367
- this.#lastTokensPerSecond = rate;
368
- this.#lastTokensPerSecondTimestamp = lastAssistantTimestamp;
369
- return rate;
370
- }
371
-
372
- if (this.#lastTokensPerSecondTimestamp === lastAssistantTimestamp) {
373
- return this.#lastTokensPerSecond;
374
- }
375
-
376
- return null;
377
- }
378
-
379
- #refreshSkillHudInBackground(): void {
380
- if (this.#settings.showSkillHud === false) return;
381
- const now = Date.now();
382
- if (this.#skillHudInFlight || now - this.#skillHudLastFetch < 1000) return;
383
- const getCwd = this.session.sessionManager?.getCwd;
384
- const getSessionId = this.session.sessionManager?.getSessionId;
385
- const cwd = typeof getCwd === "function" ? getCwd.call(this.session.sessionManager) : getProjectDir();
386
- const sessionId = typeof getSessionId === "function" ? getSessionId.call(this.session.sessionManager) : undefined;
387
- this.#skillHudInFlight = true;
388
- void readVisibleSkillActiveState(cwd, sessionId, { tier: "hud" })
389
- .then(state => {
390
- this.#skillHudEntries = state?.active_skills ?? [];
391
- })
392
- .catch(() => {
393
- this.#skillHudEntries = [];
394
- })
395
- .finally(() => {
396
- this.#skillHudLastFetch = Date.now();
397
- this.#skillHudInFlight = false;
398
- });
399
- }
400
-
401
- /**
402
- * Background-refresh the OAuth quota report. Guarded by a 5-min TTL on both
403
- * success (cache lifetime) and error (backoff). Exposed (non-private) so
404
- * unit tests can verify the backoff invariant.
405
- */
406
- refreshUsageInBackground(): void {
407
- const now = Date.now();
408
- if (this.#usageInFlight) return;
409
- if (this.#usageFetchedAt > 0 && now - this.#usageFetchedAt < 5 * 60_000) return;
410
- const fetcher = (this.session as { fetchUsageReports?: () => Promise<unknown> }).fetchUsageReports;
411
- if (typeof fetcher !== "function") return;
412
- this.#usageInFlight = true;
413
- void fetcher
414
- .call(this.session)
415
- .then(reports => {
416
- this.#cachedUsage = this.#normalizeUsageReports(reports);
417
- this.#usageFetchedAt = Date.now();
418
- if (this.#onBranchChange) {
419
- this.#onBranchChange();
420
- }
421
- })
422
- .catch(() => {
423
- // Backoff on error: stamp the fetch time so the 5-min TTL guard
424
- // also acts as an error budget. Without this, every render
425
- // kicks off another fetch (gated only by #usageInFlight),
426
- // which hammers the endpoint during a network outage / 5xx.
427
- this.#usageFetchedAt = Date.now();
428
- })
429
- .finally(() => {
430
- this.#usageInFlight = false;
431
- });
432
- }
433
-
434
- #normalizeUsageReports(reports: unknown): SegmentContext["usage"] {
435
- if (!Array.isArray(reports)) return null;
436
- const windows: NonNullable<SegmentContext["usage"]>["windows"] = [];
437
- const seen = new Set<string>();
438
- const now = Date.now();
439
-
440
- const codexWindowLabel = (windowId: string | undefined, fallback: string): string => {
441
- if (windowId && /^\d+[hd]$/.test(windowId)) return windowId;
442
- return fallback;
443
- };
444
- const codexResetUnit = (label: string): "m" | "h" => (label.endsWith("d") ? "h" : "m");
445
-
446
- const pushWindow = (
447
- key: string,
448
- label: string,
449
- fraction: number,
450
- resetsAt: number | undefined,
451
- resetUnit: "m" | "h",
452
- ) => {
453
- if (seen.has(key)) return;
454
- seen.add(key);
455
- windows.push({
456
- label,
457
- percent: fraction * 100,
458
- resetValue:
459
- typeof resetsAt === "number"
460
- ? Math.max(0, Math.round((resetsAt - now) / (resetUnit === "m" ? 60_000 : 3_600_000)))
461
- : undefined,
462
- resetUnit,
463
- });
464
- };
465
-
466
- for (const report of reports) {
467
- if (!report || typeof report !== "object") continue;
468
- const provider = (report as { provider?: unknown }).provider;
469
- const providerId = typeof provider === "string" ? provider : undefined;
470
- const limits = (report as { limits?: unknown }).limits;
471
- if (!Array.isArray(limits)) continue;
472
- for (const limit of limits) {
473
- if (!limit || typeof limit !== "object") continue;
474
- const l = limit as {
475
- id?: unknown;
476
- scope?: { windowId?: string; tier?: string; modelId?: string };
477
- window?: { id?: string; resetsAt?: number };
478
- amount?: { usedFraction?: number };
479
- };
480
- const fraction = l.amount?.usedFraction;
481
- if (typeof fraction !== "number") continue;
482
- const id = typeof l.id === "string" ? l.id : "";
483
- const windowId = l.scope?.windowId ?? l.window?.id;
484
- const tier = l.scope?.tier;
485
- const modelId = l.scope?.modelId;
486
- const resetsAt = l.window?.resetsAt;
487
-
488
- if (providerId === "openai-codex") {
489
- if (id === "openai-codex:primary" || (!id && !!windowId && windowId !== "7d" && !modelId)) {
490
- const label = codexWindowLabel(windowId, "primary");
491
- pushWindow("codex:primary", label, fraction, resetsAt, codexResetUnit(label));
492
- } else if (id === "openai-codex:secondary" || (!id && windowId === "7d" && !modelId)) {
493
- const label = codexWindowLabel(windowId, "secondary");
494
- pushWindow("codex:secondary", label, fraction, resetsAt, codexResetUnit(label));
495
- }
496
- } else if (windowId === "5h" && !tier) {
497
- pushWindow(`${providerId ?? "provider"}:5h`, "5h", fraction, resetsAt, "m");
498
- } else if (windowId === "7d" && !tier) {
499
- pushWindow(`${providerId ?? "provider"}:7d`, "7d", fraction, resetsAt, "h");
500
- }
501
- }
502
- }
503
-
504
- return windows.length > 0 ? { windows } : null;
505
- }
506
-
507
- #buildSegmentContext(
508
- width: number,
509
- effectiveSettings: Required<
510
- Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
511
- > &
512
- StatusLineSettings,
513
- ): SegmentContext {
514
- const state = this.session.state;
515
-
516
- // Trigger background fetch (5-min TTL); render uses cached value
517
- this.refreshUsageInBackground();
518
-
519
- // Get usage statistics
520
- const aggregateUsageStats = this.session.sessionManager?.getUsageStatistics() ?? {
521
- input: 0,
522
- output: 0,
523
- cacheRead: 0,
524
- cacheWrite: 0,
525
- premiumRequests: 0,
526
- cost: 0,
527
- };
528
- const usageStats = {
529
- ...aggregateUsageStats,
530
- tokensPerSecond: this.#getTokensPerSecond(),
531
- };
532
-
533
- const contextUsage = this.session.getContextUsage?.();
534
- const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
535
- const contextPercent = contextUsage?.percent ?? null;
536
- // Suppress the inline model percentage when a standalone context_pct
537
- // segment is also rendered, so the value is not shown twice.
538
- const contextPctSegmentActive =
539
- effectiveSettings.leftSegments.includes("context_pct") ||
540
- effectiveSettings.rightSegments.includes("context_pct");
541
-
542
- return {
543
- session: this.session,
544
- width,
545
- options: effectiveSettings.segmentOptions ?? {},
546
- planMode: this.#planModeStatus,
547
- goalMode: this.#goalModeStatus,
548
- usageStats,
549
- contextPercent,
550
- contextWindow,
551
- contextPctSegmentActive,
552
- autoCompactEnabled: this.#autoCompactEnabled,
553
- subagentCount: this.#subagentCount,
554
- jobs: this.#jobs,
555
- sessionStartTime: this.#sessionStartTime,
556
- git: {
557
- branch: this.#getCurrentBranch(),
558
- status: this.#getGitStatus(),
559
- pr: this.#lookupPr(),
560
- },
561
- usage: this.#cachedUsage,
562
- };
563
- }
564
-
565
- #settingsFingerprint(): string {
566
- return JSON.stringify(this.#settings);
567
- }
568
-
569
- #resolveSettings(): Required<
570
- Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
571
- > &
572
- StatusLineSettings {
573
- const fingerprint = this.#settingsFingerprint();
574
- if (this.#resolvedSettingsCache && this.#resolvedSettingsFingerprint === fingerprint) {
575
- return this.#resolvedSettingsCache;
576
- }
577
-
578
- const preset = this.#settings.preset ?? "default";
579
- const presetDef = getPreset(preset);
580
- const useCustomSegments = preset === "custom";
581
- const mergedSegmentOptions: StatusLineSettings["segmentOptions"] = {};
582
-
583
- for (const [segment, options] of Object.entries(presetDef.segmentOptions ?? {})) {
584
- mergedSegmentOptions[segment as keyof StatusLineSegmentOptions] = { ...(options as Record<string, unknown>) };
585
- }
586
-
587
- for (const [segment, options] of Object.entries(this.#settings.segmentOptions ?? {})) {
588
- const current = mergedSegmentOptions[segment as keyof StatusLineSegmentOptions] ?? {};
589
- mergedSegmentOptions[segment as keyof StatusLineSegmentOptions] = {
590
- ...(current as Record<string, unknown>),
591
- ...(options as Record<string, unknown>),
592
- };
593
- }
594
-
595
- const leftSegments = useCustomSegments
596
- ? (this.#settings.leftSegments ?? presetDef.leftSegments)
597
- : presetDef.leftSegments;
598
- const rightSegments = useCustomSegments
599
- ? (this.#settings.rightSegments ?? presetDef.rightSegments)
600
- : presetDef.rightSegments;
601
-
602
- this.#resolvedSettingsFingerprint = fingerprint;
603
- this.#resolvedSettingsCache = {
604
- ...this.#settings,
605
- leftSegments,
606
- rightSegments,
607
- separator: this.#settings.separator ?? presetDef.separator,
608
- segmentOptions: mergedSegmentOptions,
609
- };
610
- return this.#resolvedSettingsCache;
611
- }
612
-
613
- #groupWidth(parts: string[], capWidth: number, sepWidth: number): number {
614
- if (parts.length === 0) return 0;
615
- const partsWidth = parts.reduce((sum, part) => sum + visibleWidth(part), 0);
616
- const sepTotal = Math.max(0, parts.length - 1) * (sepWidth + 2);
617
- return partsWidth + sepTotal + 2 + capWidth;
618
- }
619
-
620
- #renderStatusGroup(
621
- parts: string[],
622
- direction: "left" | "right",
623
- separatorDef: SeparatorDef,
624
- bgAnsi: string,
625
- fgAnsi: string,
626
- sepAnsi: string,
627
- ): string {
628
- if (parts.length === 0) return "";
629
- const sep = direction === "left" ? separatorDef.left : separatorDef.right;
630
- const cap = separatorDef.endCaps
631
- ? direction === "left"
632
- ? separatorDef.endCaps.right
633
- : separatorDef.endCaps.left
634
- : "";
635
- const capPrefix = separatorDef.endCaps?.useBgAsFg ? bgAnsi.replace("\x1b[48;", "\x1b[38;") : bgAnsi + sepAnsi;
636
- const capText = cap ? `${capPrefix}${cap}\x1b[0m` : "";
637
-
638
- let content = bgAnsi + fgAnsi;
639
- content += ` ${parts.join(` ${sepAnsi}${sep}${fgAnsi} `)} `;
640
- content += "\x1b[0m";
641
-
642
- if (capText) {
643
- return direction === "right" ? capText + content : content + capText;
644
- }
645
- return content;
646
- }
647
-
648
- #shrinkPathToWidth(content: string, ctx: SegmentContext, shrinkBy: number): string | null {
649
- const currentPathVW = visibleWidth(content);
650
- const minPathVW = 8; // icon + ellipsis + a few chars
651
- const shrinkable = currentPathVW - minPathVW;
652
- if (shrinkable <= 0 || shrinkBy <= 0) return null;
653
- const targetShrink = Math.min(shrinkable, shrinkBy);
654
- const currentMaxLen = ctx.options.path?.maxLength ?? 40;
655
- let newMaxLen = Math.max(4, Math.min(currentMaxLen, currentPathVW) - targetShrink);
656
- const pathCtx = (maxLen: number): SegmentContext => ({
657
- ...ctx,
658
- options: { ...ctx.options, path: { ...ctx.options.path, maxLength: maxLen } },
659
- });
660
- let reRendered = renderSegment("path", pathCtx(newMaxLen));
661
- if (!reRendered.visible || !reRendered.content) return null;
662
- // maxLength governs path text, not icon prefix; iterate to compensate.
663
- for (let i = 0; i < 8; i++) {
664
- const saved = currentPathVW - visibleWidth(reRendered.content);
665
- if (saved >= targetShrink) break;
666
- const nextMaxLen = Math.max(4, newMaxLen - (targetShrink - saved));
667
- if (nextMaxLen >= newMaxLen) break; // no progress or hit floor
668
- newMaxLen = nextMaxLen;
669
- const adjusted = renderSegment("path", pathCtx(newMaxLen));
670
- if (!adjusted.visible || !adjusted.content) break;
671
- reRendered = adjusted;
672
- }
673
- return reRendered.content;
674
- }
675
-
676
- #collectStatusSegments(
677
- width: number,
678
- effectiveSettings: Required<
679
- Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
680
- > &
681
- StatusLineSettings,
682
- ): CollectedStatusSegments {
683
- const ctx = this.#buildSegmentContext(width, effectiveSettings);
684
- const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
685
-
686
- // Use the subtle surface tone (the same elevated background as user-message
687
- // bubbles) instead of the heavy `statusLineBg` block, so the rail layers
688
- // just above the base background as a quiet zone rather than a solid bar.
689
- // Resolving through a semantic slot keeps it correct across every theme.
690
- const bgAnsi = theme.getBgAnsi("userMessageBg");
691
- const fgAnsi = theme.getFgAnsi("text");
692
- const sepAnsi = theme.getFgAnsi("statusLineSep");
693
-
694
- const previewHighlightSegment = effectiveSettings.previewHighlightSegment;
695
- const highlightSegment = (segId: StatusLineSegmentId, content: string): string =>
696
- previewHighlightSegment === segId ? `\x1b[7m${content}\x1b[27m` : content;
697
-
698
- const left: string[] = [];
699
- const leftSegIds: StatusLineSegmentId[] = [];
700
- for (const segId of effectiveSettings.leftSegments) {
701
- const rendered = renderSegment(segId, ctx);
702
- if (rendered.visible && rendered.content) {
703
- left.push(highlightSegment(segId, rendered.content));
704
- leftSegIds.push(segId);
705
- }
706
- }
707
-
708
- const right: string[] = [];
709
- for (const segId of effectiveSettings.rightSegments) {
710
- const rendered = renderSegment(segId, ctx);
711
- if (rendered.visible && rendered.content) {
712
- right.push(highlightSegment(segId, rendered.content));
713
- }
714
- }
715
-
716
- const runningBackgroundJobs =
717
- this.session.getAsyncJobSnapshot()?.running.filter(job => job.metadata?.monitor !== true).length ?? 0;
718
- if (runningBackgroundJobs > 0) {
719
- const icon = theme.icon.agents ? `${theme.icon.agents} ` : "";
720
- const label = `${formatCount("job", runningBackgroundJobs)} running`;
721
- right.push(theme.fg("statusLineSubagents", `${icon}${label}`));
722
- }
723
- if (this.#version) {
724
- right.push(theme.fg("dim", `v${this.#version}`));
725
- }
726
-
727
- return {
728
- ctx,
729
- separatorDef,
730
- bgAnsi,
731
- fgAnsi,
732
- sepAnsi,
733
- left,
734
- leftSegIds,
735
- right,
736
- previewHighlightSegment,
737
- sessionAccent: effectiveSettings.sessionAccent,
738
- leftSepWidth: visibleWidth(separatorDef.left),
739
- rightSepWidth: visibleWidth(separatorDef.right),
740
- leftCapWidth: separatorDef.endCaps ? visibleWidth(separatorDef.endCaps.right) : 0,
741
- rightCapWidth: separatorDef.endCaps ? visibleWidth(separatorDef.endCaps.left) : 0,
742
- };
743
- }
744
-
745
- #resolveMaxRows(): number {
746
- const raw = this.#settings.maxRows ?? 1;
747
- if (!Number.isFinite(raw)) return 1;
748
- return Math.max(1, Math.min(3, Math.trunc(raw)));
749
- }
750
-
751
- #buildStatusLine(width: number, precollected?: CollectedStatusSegments): string {
752
- const seg = precollected ?? this.#collectStatusSegments(width, this.#resolveSettings());
753
- const { ctx, separatorDef, bgAnsi, fgAnsi, sepAnsi, previewHighlightSegment } = seg;
754
- const { leftSepWidth, rightSepWidth, leftCapWidth, rightCapWidth } = seg;
755
- const left = [...seg.left];
756
- const leftIds = [...seg.leftSegIds];
757
- const right = [...seg.right];
758
- const topFillWidth = Math.max(0, width);
759
-
760
- let leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
761
- let rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
762
- const totalWidth = () => leftWidth + rightWidth + (left.length > 0 && right.length > 0 ? 1 : 0);
763
-
764
- if (topFillWidth > 0) {
765
- // Shrink path before dropping right-side telemetry — path is the only elastic segment,
766
- // and presets such as default-usage should not hide usage just because cwd is long.
767
- const pathIdx = leftIds.indexOf("path");
768
- if (pathIdx >= 0 && totalWidth() > topFillWidth) {
769
- const overflow = totalWidth() - topFillWidth;
770
- const shrunk = this.#shrinkPathToWidth(left[pathIdx], ctx, overflow);
771
- if (shrunk !== null) {
772
- left[pathIdx] = previewHighlightSegment === "path" ? `\x1b[7m${shrunk}\x1b[27m` : shrunk;
773
- leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
774
- }
775
- }
776
- while (totalWidth() > topFillWidth && right.length > 0) {
777
- right.pop();
778
- rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
779
- }
780
- while (totalWidth() > topFillWidth && left.length > 0) {
781
- left.pop();
782
- leftIds.pop();
783
- leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
784
- }
785
- }
786
-
787
- const leftGroup = this.#renderStatusGroup(left, "left", separatorDef, bgAnsi, fgAnsi, sepAnsi);
788
- const rightGroup = this.#renderStatusGroup(right, "right", separatorDef, bgAnsi, fgAnsi, sepAnsi);
789
- if (!leftGroup && !rightGroup) return "";
790
-
791
- if (topFillWidth === 0 || left.length === 0 || right.length === 0) {
792
- return leftGroup + (leftGroup && rightGroup ? " " : "") + rightGroup;
793
- }
794
-
795
- leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
796
- rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
797
- const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
798
- const sessionName = seg.sessionAccent !== false ? this.session.sessionManager?.getSessionName() : undefined;
799
- const accentHex = sessionName ? getSessionAccentHex(sessionName) : undefined;
800
- const gapColor = getSessionAccentAnsi(accentHex) ?? theme.getFgAnsi("border");
801
- const gapFill = `${gapColor}${theme.boxRound.horizontal.repeat(gapWidth)}\x1b[39m`;
802
- return leftGroup + gapFill + rightGroup;
803
- }
804
-
805
- /**
806
- * Multi-row status line. When `maxRows > 1` and the single-line layout would
807
- * overflow, segments wrap onto additional left-justified rows instead of
808
- * being dropped. Falls back to the polished justified single row whenever
809
- * everything fits on one line.
810
- */
811
- #buildStatusRows(width: number, maxRows: number): string[] {
812
- const effectiveSettings = this.#resolveSettings();
813
- const seg = this.#collectStatusSegments(width, effectiveSettings);
814
- const cacheKey = JSON.stringify({
815
- width,
816
- maxRows,
817
- settings: this.#resolvedSettingsFingerprint,
818
- left: seg.left,
819
- leftSegIds: seg.leftSegIds,
820
- right: seg.right,
821
- separator: effectiveSettings.separator,
822
- previewHighlightSegment: seg.previewHighlightSegment,
823
- sessionAccent: seg.sessionAccent,
824
- theme: [seg.bgAnsi, seg.fgAnsi, seg.sepAnsi],
825
- rowLayout: {
826
- separatorLeft: seg.separatorDef.left,
827
- separatorRight: seg.separatorDef.right,
828
- separatorEndCapLeft: seg.separatorDef.endCaps?.left,
829
- separatorEndCapRight: seg.separatorDef.endCaps?.right,
830
- separatorEndCapUseBgAsFg: seg.separatorDef.endCaps?.useBgAsFg,
831
- borderFgAnsi: theme.getFgAnsi("border"),
832
- boxRoundHorizontal: theme.boxRound.horizontal,
833
- },
834
- context: [seg.ctx.contextPercent, seg.ctx.contextWindow],
835
- usageStats: seg.ctx.usageStats,
836
- usage: seg.ctx.usage,
837
- git: seg.ctx.git,
838
- modes: [seg.ctx.planMode, seg.ctx.goalMode, seg.ctx.autoCompactEnabled],
839
- runtime: [seg.ctx.subagentCount, seg.ctx.jobs, seg.ctx.sessionStartTime],
840
- sessionName: seg.sessionAccent !== false ? this.session.sessionManager?.getSessionName() : undefined,
841
- asyncJobs: this.session
842
- .getAsyncJobSnapshot()
843
- ?.running.filter(job => job.metadata?.monitor !== true)
844
- .map(job => job.id ?? job.metadata ?? job)
845
- .join(","),
846
- version: this.#version,
847
- });
848
- if (this.#renderedRowsCache?.key === cacheKey) {
849
- this.#renderedRowsCacheHits++;
850
- return [...this.#renderedRowsCache.rows];
851
- }
852
- this.#renderedRowsCacheMisses++;
853
-
854
- if (seg.left.length === 0 && seg.right.length === 0) {
855
- this.#renderedRowsCache = { key: cacheKey, rows: [] };
856
- return [];
857
- }
858
-
859
- const topFillWidth = Math.max(1, width);
860
- const leftWidth = this.#groupWidth(seg.left, seg.leftCapWidth, seg.leftSepWidth);
861
- const rightWidth = this.#groupWidth(seg.right, seg.rightCapWidth, seg.rightSepWidth);
862
- const gap = seg.left.length > 0 && seg.right.length > 0 ? 1 : 0;
863
- const fitsSingleRow = leftWidth + rightWidth + gap <= topFillWidth;
864
-
865
- let rows: string[];
866
- if (maxRows <= 1 || fitsSingleRow) {
867
- const single = this.#buildStatusLine(width, seg);
868
- rows = single ? [single] : [];
869
- } else {
870
- const items: { content: string; isPath: boolean }[] = [
871
- ...seg.left.map((content, i) => ({ content, isPath: seg.leftSegIds[i] === "path" })),
872
- ...seg.right.map(content => ({ content, isPath: false })),
873
- ];
874
-
875
- for (const item of items) {
876
- if (!item.isPath) continue;
877
- const alone = this.#groupWidth([item.content], seg.leftCapWidth, seg.leftSepWidth);
878
- if (alone > topFillWidth) {
879
- const shrunk = this.#shrinkPathToWidth(item.content, seg.ctx, alone - topFillWidth);
880
- if (shrunk !== null) item.content = shrunk;
881
- }
882
- }
883
-
884
- const packedRows: string[][] = [];
885
- let current: string[] = [];
886
- for (const item of items) {
887
- if (current.length === 0) {
888
- current.push(item.content);
889
- continue;
890
- }
891
- const tentative = [...current, item.content];
892
- if (this.#groupWidth(tentative, seg.leftCapWidth, seg.leftSepWidth) <= topFillWidth) {
893
- current = tentative;
894
- } else {
895
- packedRows.push(current);
896
- current = [item.content];
897
- if (packedRows.length >= maxRows) break;
898
- }
899
- }
900
- if (packedRows.length < maxRows && current.length > 0) {
901
- packedRows.push(current);
902
- }
903
-
904
- rows = packedRows.map(row =>
905
- this.#renderStatusGroup(row, "left", seg.separatorDef, seg.bgAnsi, seg.fgAnsi, seg.sepAnsi),
906
- );
907
- }
908
-
909
- this.#renderedRowsCache = { key: cacheKey, rows };
910
- return [...rows];
911
- }
912
-
913
- getTopBorder(width: number): { content: string; width: number } {
914
- const content = this.#buildStatusLine(width);
915
- return {
916
- content,
917
- width: visibleWidth(content),
918
- };
919
- }
920
-
921
- /**
922
- * Multi-row-aware content for the settings preview: the wrapped rows joined
923
- * with newlines so a single `Text` renders them stacked. Honors the current
924
- * `maxRows`; identical to the single status line when `maxRows` is 1 or when
925
- * everything fits on one row.
926
- */
927
- getPreviewContent(width: number): string {
928
- return this.#buildStatusRows(width, this.#resolveMaxRows()).join("\n");
929
- }
930
-
931
- getCacheStatsForTest(): { rowHits: number; rowMisses: number } {
932
- return { rowHits: this.#renderedRowsCacheHits, rowMisses: this.#renderedRowsCacheMisses };
933
- }
934
-
935
- invalidateBranchForTest(): void {
936
- this.#invalidateGitCaches();
937
- }
938
-
939
- setCachedPrForTest(pr: { number: number; url: string } | null): void {
940
- const branch = this.#getCurrentBranch();
941
- this.#cachedPr = pr;
942
- this.#cachedPrContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : undefined;
943
- }
944
-
945
- render(width: number): string[] {
946
- const lines: string[] = [];
947
- this.#refreshSkillHudInBackground();
948
- const skillHud = this.#settings.showSkillHud === false ? null : renderSkillHudBar(this.#skillHudEntries, width);
949
- if (skillHud) {
950
- lines.push(skillHud);
951
- }
952
-
953
- const statusRows = this.#buildStatusRows(width, this.#resolveMaxRows());
954
- for (const statusRow of statusRows) {
955
- if (statusRow) lines.push(truncateToWidth(statusRow, width));
956
- }
957
-
958
- const showHooks = this.#settings.showHookStatus ?? true;
959
- if (showHooks && this.#hookStatuses.size > 0) {
960
- const sortedStatuses = Array.from(this.#hookStatuses.entries())
961
- .sort(([a], [b]) => a.localeCompare(b))
962
- .map(([, text]) => sanitizeStatusText(text));
963
- const hookLine = sortedStatuses.join(" ");
964
- lines.push(truncateToWidth(hookLine, width));
965
- }
966
-
967
- return lines;
968
- }
969
- }
1
+ export * from "./tool-status-header";