@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
package/src/utils/jj.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as fs from "node:fs/promises";
1
+ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { $which } from "@oh-my-pi/pi-utils";
4
4
  import { LRUCache } from "lru-cache/raw";
@@ -63,6 +63,8 @@ export class JjCommandError extends Error {
63
63
  // ════════════════════════════════════════════════════════════════════════════
64
64
  // Internal: Core execution
65
65
  // ════════════════════════════════════════════════════════════════════════════
66
+ const WORKING_COPY_LABEL_REVSET = "@ | heads(::@ & bookmarks())";
67
+ const WORKING_COPY_LABEL_TEMPLATE = 'change_id.shortest(8) ++ "|" ++ local_bookmarks ++ "\\n"';
66
68
 
67
69
  function ensureAvailable(): void {
68
70
  if (!$which("jj")) {
@@ -121,6 +123,19 @@ async function runText(cwd: string, args: readonly string[], options: CommandOpt
121
123
  return (await runChecked(cwd, args, options)).stdout;
122
124
  }
123
125
 
126
+ async function runOptionalText(
127
+ cwd: string,
128
+ args: readonly string[],
129
+ options: CommandOptions = {},
130
+ ): Promise<string | null> {
131
+ try {
132
+ const result = await jj(cwd, args, options);
133
+ return result.exitCode === 0 ? result.stdout : null;
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
124
139
  function splitLines(text: string): string[] {
125
140
  return text
126
141
  .split("\n")
@@ -135,6 +150,30 @@ function buildDiffArgs(options: DiffOptions): string[] {
135
150
  return args;
136
151
  }
137
152
 
153
+ function parseWorkingCopyLabel(raw: string): string | null {
154
+ let changeId: string | null = null;
155
+ for (const line of raw.split("\n")) {
156
+ const sep = line.indexOf("|");
157
+ const change = (sep === -1 ? line : line.slice(0, sep)).trim();
158
+ const bookmarks = sep === -1 ? "" : line.slice(sep + 1).trim();
159
+ if (changeId === null && change) changeId = change;
160
+ if (bookmarks) return bookmarks.replace(/\s+/g, " ");
161
+ }
162
+ return changeId;
163
+ }
164
+
165
+ function parseStatusSummary(raw: string): git.GitStatusSummary {
166
+ let unstaged = 0;
167
+ let untracked = 0;
168
+ for (const line of raw.split("\n")) {
169
+ const type = line.trim()[0];
170
+ if (!type) continue;
171
+ if (type === "A") untracked++;
172
+ else unstaged++;
173
+ }
174
+ return { staged: 0, unstaged, untracked };
175
+ }
176
+
138
177
  // ════════════════════════════════════════════════════════════════════════════
139
178
  // Internal: Repository resolution
140
179
  // ════════════════════════════════════════════════════════════════════════════
@@ -153,7 +192,16 @@ async function hasJjWorkspaceMetadata(dir: string): Promise<boolean> {
153
192
  // of the default workspace. Either form is a real workspace, so match on
154
193
  // `.jj/repo` presence rather than the inner `store/` directory.
155
194
  try {
156
- await fs.stat(path.join(dir, ".jj", "repo"));
195
+ await fs.promises.stat(path.join(dir, ".jj", "repo"));
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+
202
+ function hasJjWorkspaceMetadataSync(dir: string): boolean {
203
+ try {
204
+ fs.statSync(path.join(dir, ".jj", "repo"));
157
205
  return true;
158
206
  } catch {
159
207
  return false;
@@ -180,6 +228,21 @@ async function findWorkspaceRoot(cwd: string): Promise<string | undefined> {
180
228
  return undefined;
181
229
  }
182
230
 
231
+ function findWorkspaceRootSync(cwd: string): string | undefined {
232
+ const key = path.resolve(cwd);
233
+ if (workspaceRootCache.has(key)) return workspaceRootCache.get(key)?.root;
234
+
235
+ for (let dir: string | undefined = key; dir; dir = parentOf(dir)) {
236
+ if (hasJjWorkspaceMetadataSync(dir)) {
237
+ workspaceRootCache.set(key, { root: dir });
238
+ return dir;
239
+ }
240
+ }
241
+
242
+ workspaceRootCache.set(key, {});
243
+ return undefined;
244
+ }
245
+
183
246
  /**
184
247
  * Resolve the `.jj/repo` directory backing a workspace root, following the file
185
248
  * indirection used by non-default workspaces. `jj workspace add` writes a FILE at
@@ -190,8 +253,8 @@ async function findWorkspaceRoot(cwd: string): Promise<string | undefined> {
190
253
  async function resolveRepoDir(root: string): Promise<string> {
191
254
  const jjDir = path.join(root, ".jj");
192
255
  const repoPath = path.join(jjDir, "repo");
193
- if ((await fs.stat(repoPath)).isFile()) {
194
- const target = (await fs.readFile(repoPath, "utf8")).trim();
256
+ if ((await fs.promises.stat(repoPath)).isFile()) {
257
+ const target = (await fs.promises.readFile(repoPath, "utf8")).trim();
195
258
  return path.resolve(jjDir, target);
196
259
  }
197
260
  return repoPath;
@@ -221,6 +284,56 @@ export const diff = Object.assign(
221
284
  },
222
285
  );
223
286
 
287
+ // ════════════════════════════════════════════════════════════════════════════
288
+ // API: working copy
289
+ // ════════════════════════════════════════════════════════════════════════════
290
+
291
+ /** Jujutsu working-copy metadata used by status displays. */
292
+ export const workingCopy = {
293
+ /**
294
+ * Label `@` with its nearest bookmark, falling back to its short change ID.
295
+ * Returns `null` when `jj` is unavailable or the query fails.
296
+ */
297
+ async label(cwd: string, signal?: AbortSignal): Promise<string | null> {
298
+ const raw = await runOptionalText(
299
+ cwd,
300
+ [
301
+ "log",
302
+ "--no-graph",
303
+ "--ignore-working-copy",
304
+ "-r",
305
+ WORKING_COPY_LABEL_REVSET,
306
+ "-T",
307
+ WORKING_COPY_LABEL_TEMPLATE,
308
+ ],
309
+ { signal },
310
+ );
311
+ return raw === null ? null : parseWorkingCopyLabel(raw);
312
+ },
313
+
314
+ /** Parse working-copy label query output. */
315
+ parseLabel: parseWorkingCopyLabel,
316
+ };
317
+
318
+ // ════════════════════════════════════════════════════════════════════════════
319
+ // API: status
320
+ // ════════════════════════════════════════════════════════════════════════════
321
+
322
+ /** Jujutsu working-copy status derived from the changes in `@`. */
323
+ export const status = {
324
+ /**
325
+ * Count changes in `@` relative to its parent using the Git status shape.
326
+ * Jujutsu has no index, so `staged` is always zero.
327
+ */
328
+ async summary(cwd: string, signal?: AbortSignal): Promise<git.GitStatusSummary | null> {
329
+ const raw = await runOptionalText(cwd, ["diff", "-r", "@", "--summary", "--ignore-working-copy"], { signal });
330
+ return raw === null ? null : parseStatusSummary(raw);
331
+ },
332
+
333
+ /** Parse `jj diff --summary` output into status counts. */
334
+ parse: parseStatusSummary,
335
+ };
336
+
224
337
  // ════════════════════════════════════════════════════════════════════════════
225
338
  // API: repo
226
339
  // ════════════════════════════════════════════════════════════════════════════
@@ -231,6 +344,14 @@ export const repo = {
231
344
  workspaceRootCache.clear();
232
345
  },
233
346
 
347
+ /**
348
+ * Resolve the current workspace root synchronously from on-disk metadata.
349
+ * Intended for render paths that cannot await filesystem I/O.
350
+ */
351
+ rootSync(cwd: string): string | null {
352
+ return findWorkspaceRootSync(cwd) ?? null;
353
+ },
354
+
234
355
  /** Resolve the current Jujutsu workspace root, or `null` when `cwd` is not in a JJ repository. */
235
356
  async root(cwd: string): Promise<string | null> {
236
357
  return (await findWorkspaceRoot(cwd)) ?? null;
@@ -102,6 +102,9 @@ const EXTENSION_LANG: Record<string, readonly [string, string]> = {
102
102
  md: ["markdown", "markdown"],
103
103
  markdown: ["markdown", "markdown"],
104
104
  mdx: ["markdown", "markdown"],
105
+ mdc: ["markdown", "markdown"],
106
+ mkd: ["markdown", "markdown"],
107
+ mdown: ["markdown", "markdown"],
105
108
  rst: ["restructuredtext", "restructuredtext"],
106
109
  adoc: ["asciidoc", "asciidoc"],
107
110
  tex: ["latex", "latex"],
@@ -217,6 +220,10 @@ export function getLanguageFromPath(filePath: string): string | undefined {
217
220
  return undefined;
218
221
  }
219
222
 
223
+ export function isMarkdownPath(filePath: string): boolean {
224
+ return getLanguageFromPath(filePath) === "markdown";
225
+ }
226
+
220
227
  /**
221
228
  * LSP language identifier; falls back to `plaintext`.
222
229
  */
@@ -387,7 +387,143 @@ export function setTerminalTitle(title: string): void {
387
387
  }
388
388
 
389
389
  export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: string): void {
390
- setTerminalTitle(formatSessionTerminalTitle(sessionName, cwd));
390
+ // An authoritative session title (rename, new session, focus swap) supersedes
391
+ // any extension override so the base title tracks the real session again.
392
+ terminalTitleRuntime.extensionOverride = undefined;
393
+ terminalTitleRuntime.label = sanitizeTerminalTitlePart(sessionName) ?? getFallbackTerminalTitle(cwd);
394
+ emitTerminalTitle();
395
+ }
396
+
397
+ /**
398
+ * Set a terminal title from an extension's `setTitle()`. Unlike the session base
399
+ * title, this owns the terminal verbatim: the run-state separator will not rewrite
400
+ * it on the next spinner tick or state change. Cleared when the app next sets an
401
+ * authoritative session title via {@link setSessionTerminalTitle}.
402
+ */
403
+ export function setExtensionTerminalTitle(title: string): void {
404
+ terminalTitleRuntime.extensionOverride = title;
405
+ emitTerminalTitle();
406
+ }
407
+
408
+ export type TerminalTitleState = "idle" | "working" | "attention";
409
+
410
+ /** Separator glyphs carrying the run state between the `π` brand and the session
411
+ * label — the brand itself stays bare (no prefix glyph). Spinner frames animate
412
+ * the separator while working; they are self-contained (not the theme's symbol
413
+ * set) to avoid a utils→modes import cycle; OSC titles render in tab/window bars
414
+ * that handle Unicode. */
415
+ const TITLE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
416
+ const TITLE_SPINNER_INTERVAL_MS = 80;
417
+ /** The user's turn: the title reads like a shell prompt awaiting input. */
418
+ const TITLE_IDLE_SEPARATOR = ">";
419
+ /** Agent blocked on the user (ask / approval prompt). */
420
+ const TITLE_ATTENTION_SEPARATOR = "!";
421
+
422
+ const terminalTitleRuntime: {
423
+ label: string | undefined;
424
+ state: TerminalTitleState;
425
+ frame: number;
426
+ enabled: boolean;
427
+ timer: NodeJS.Timeout | undefined;
428
+ lastEmitted: string | undefined;
429
+ /** A title an extension set via `setTitle()`. While set, it owns the terminal
430
+ * title verbatim: the run-state separator never rewrites it. Cleared when the
431
+ * app next establishes an authoritative session title (rename, new session,
432
+ * focus swap) via `setSessionTerminalTitle`. */
433
+ extensionOverride: string | undefined;
434
+ } = {
435
+ label: undefined,
436
+ state: "idle",
437
+ frame: 0,
438
+ enabled: true,
439
+ timer: undefined,
440
+ lastEmitted: undefined,
441
+ extensionOverride: undefined,
442
+ };
443
+
444
+ /**
445
+ * Compose the terminal title from the `π` brand, a state-carrying SEPARATOR, and
446
+ * the session label. The brand never gains a prefix glyph — the separator slot
447
+ * expresses the run state instead. Pure (no I/O) so the state→separator contract
448
+ * is unit-testable:
449
+ * - `idle` (user's turn): `π > label` — reads like a prompt awaiting input;
450
+ * - `working`: `π ⠋ label` — spinner frames animate the separator;
451
+ * - `attention`: `π ! label` — agent blocked on the user;
452
+ * - disabled: `π: label` — the pre-state layout.
453
+ * Without a label the separator trails the brand (`π >`) so the state stays visible.
454
+ */
455
+ export function buildTerminalTitleWithState(
456
+ label: string | undefined,
457
+ state: TerminalTitleState,
458
+ frame: number,
459
+ enabled: boolean,
460
+ ): string {
461
+ if (!enabled) return label ? `${DEFAULT_TERMINAL_TITLE}: ${label}` : DEFAULT_TERMINAL_TITLE;
462
+ const separator =
463
+ state === "working"
464
+ ? TITLE_SPINNER_FRAMES[frame % TITLE_SPINNER_FRAMES.length]
465
+ : state === "attention"
466
+ ? TITLE_ATTENTION_SEPARATOR
467
+ : TITLE_IDLE_SEPARATOR;
468
+ return label ? `${DEFAULT_TERMINAL_TITLE} ${separator} ${label}` : `${DEFAULT_TERMINAL_TITLE} ${separator}`;
469
+ }
470
+
471
+ function emitTerminalTitle(): void {
472
+ // An extension override owns the terminal verbatim; the run-state separator and
473
+ // spinner ticks must not clobber it (still deduped via lastEmitted).
474
+ const next =
475
+ terminalTitleRuntime.extensionOverride ??
476
+ buildTerminalTitleWithState(
477
+ terminalTitleRuntime.label,
478
+ terminalTitleRuntime.state,
479
+ terminalTitleRuntime.frame,
480
+ terminalTitleRuntime.enabled,
481
+ );
482
+ if (next === terminalTitleRuntime.lastEmitted) return;
483
+ terminalTitleRuntime.lastEmitted = next;
484
+ setTerminalTitle(next);
485
+ }
486
+
487
+ function stopTerminalTitleSpinner(): void {
488
+ if (terminalTitleRuntime.timer) {
489
+ clearInterval(terminalTitleRuntime.timer);
490
+ terminalTitleRuntime.timer = undefined;
491
+ }
492
+ }
493
+
494
+ function startTerminalTitleSpinner(): void {
495
+ if (terminalTitleRuntime.timer || !process.stdout.isTTY) return;
496
+ terminalTitleRuntime.timer = setInterval(() => {
497
+ terminalTitleRuntime.frame = (terminalTitleRuntime.frame + 1) % TITLE_SPINNER_FRAMES.length;
498
+ emitTerminalTitle();
499
+ }, TITLE_SPINNER_INTERVAL_MS);
500
+ // Never keep the event loop alive for a cosmetic animation.
501
+ terminalTitleRuntime.timer.unref?.();
502
+ }
503
+
504
+ /**
505
+ * Reflect the agent run state in the terminal title's separator: `working`
506
+ * animates spinner frames in the separator slot, `idle` shows `>` (your turn),
507
+ * `attention` shows `!` (agent blocked on you). Gated off by `tui.titleState`.
508
+ */
509
+ export function setTerminalTitleState(state: TerminalTitleState): void {
510
+ terminalTitleRuntime.state = state;
511
+ if (state === "working" && terminalTitleRuntime.enabled) startTerminalTitleSpinner();
512
+ else stopTerminalTitleSpinner();
513
+ emitTerminalTitle();
514
+ }
515
+
516
+ /** Enable/disable the run-state separator (driven by the `tui.titleState` setting). */
517
+ export function setTerminalTitleStateEnabled(enabled: boolean): void {
518
+ terminalTitleRuntime.enabled = enabled;
519
+ if (enabled && terminalTitleRuntime.state === "working") startTerminalTitleSpinner();
520
+ else stopTerminalTitleSpinner();
521
+ emitTerminalTitle();
522
+ }
523
+
524
+ /** Stop the spinner timer; call on session/UI teardown. */
525
+ export function disposeTerminalTitleState(): void {
526
+ stopTerminalTitleSpinner();
391
527
  }
392
528
 
393
529
  /**