@aiworker/web 1.22.0 → 1.23.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 (415) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +14 -4
  3. package/bin/cli.test.js +165 -73
  4. package/bin/lib/cli-api-target.js +2 -2
  5. package/bin/lib/cli-args.js +119 -119
  6. package/bin/lib/cli-lifecycle.js +9 -9
  7. package/bin/lib/cli-network.js +3 -3
  8. package/bin/lib/cli-paths.js +3 -3
  9. package/bin/lib/cli-ports.js +2 -2
  10. package/bin/lib/cli-process.js +4 -4
  11. package/bin/lib/cli-startup.js +64 -15
  12. package/bin/lib/cli-tunnel-profiles.js +1 -1
  13. package/bin/lib/cli-tunnel-utils.js +2 -2
  14. package/bin/lib/commands-connect-url.js +19 -19
  15. package/bin/lib/commands-lifecycle.js +27 -27
  16. package/bin/lib/commands-logs.js +5 -5
  17. package/bin/lib/commands-models.js +1 -1
  18. package/bin/lib/commands-projects.js +1 -1
  19. package/bin/lib/commands-schedule.js +1 -1
  20. package/bin/lib/commands-serve.js +29 -29
  21. package/bin/lib/commands-session.js +1 -1
  22. package/bin/lib/commands-startup.js +45 -10
  23. package/bin/lib/commands-status.js +1 -1
  24. package/bin/lib/commands-tunnel.js +40 -40
  25. package/bin/lib/commands-update.js +1 -1
  26. package/bin/lib/commands-update.test.js +6 -6
  27. package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  28. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  29. package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
  30. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  31. package/dist/assets/GitView-BXsS3jG6.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
  33. package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
  34. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  35. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  36. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
  38. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  39. package/dist/assets/PlanView-CJTmOF6I.js +5 -0
  40. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  42. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  43. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  44. package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
  45. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  46. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  47. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  48. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  49. package/dist/assets/de-DCrsQ5zS.js +10 -0
  50. package/dist/assets/es-Dwv4YfH6.js +16 -0
  51. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  52. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  53. package/dist/assets/index-B68h5S5B.css +1 -0
  54. package/dist/assets/index-DVwMarHI.js +39 -0
  55. package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
  56. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
  57. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
  58. package/dist/assets/ja-Bruw3RYC.js +16 -0
  59. package/dist/assets/ko-idhXGF8j.js +16 -0
  60. package/dist/assets/main-BkyaQhJX.js +930 -0
  61. package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
  62. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  63. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
  64. package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
  65. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  66. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  67. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  68. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  69. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  70. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  71. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  72. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
  73. package/dist/assets/surface-oMvhsog3.js +1 -0
  74. package/dist/assets/tr-BK_KiO2V.js +7 -0
  75. package/dist/assets/uk-CG74xV8N.js +16 -0
  76. package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
  77. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  78. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  79. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  80. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  82. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  83. package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
  84. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  85. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  86. package/dist/index.html +21 -59
  87. package/dist/mini-chat.html +7 -7
  88. package/dist/mobile.html +5 -5
  89. package/package.json +3 -4
  90. package/server/index.d.ts +15 -0
  91. package/server/index.js +158 -89
  92. package/server/lib/agent-memory/actions.js +2 -2
  93. package/server/lib/agent-memory/feature-flag.js +1 -1
  94. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  95. package/server/lib/agent-memory/routes.js +2 -2
  96. package/server/lib/agent-tool/runtime.js +37 -54
  97. package/server/lib/agent-tool/runtime.test.js +59 -59
  98. package/server/lib/browser-control/broker.js +3 -3
  99. package/server/lib/browser-control/broker.test.js +1 -1
  100. package/server/lib/changelog/update-notes.js +82 -0
  101. package/server/lib/changelog/update-notes.test.js +59 -0
  102. package/server/lib/client-auth/remote-clients.test.js +5 -5
  103. package/server/lib/context-obligatory/runtime.js +7 -7
  104. package/server/lib/context-obligatory/runtime.test.js +5 -5
  105. package/server/lib/data-dir-migration.js +40 -0
  106. package/server/lib/data-dir-migration.test.js +59 -0
  107. package/server/lib/dev-servers/parse.js +2 -2
  108. package/server/lib/dev-servers/parse.test.js +2 -2
  109. package/server/lib/dev-servers/routes.js +1 -1
  110. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  111. package/server/lib/dev-tunnel/client.js +1 -1
  112. package/server/lib/dev-tunnel/runtime.js +5 -5
  113. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  114. package/server/lib/dictation/local/model-catalog.js +1 -1
  115. package/server/lib/dictation/local/worker-process.js +2 -2
  116. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  117. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  118. package/server/lib/event-stream/protocol.test.js +4 -4
  119. package/server/lib/event-stream/runtime.test.js +18 -18
  120. package/server/lib/fs/DOCUMENTATION.md +5 -0
  121. package/server/lib/fs/routes.js +68 -26
  122. package/server/lib/fs/routes.test.js +208 -22
  123. package/server/lib/git/DOCUMENTATION.md +32 -6
  124. package/server/lib/git/identity-storage.js +1 -1
  125. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  126. package/server/lib/git/routes.js +41 -5
  127. package/server/lib/git/service.js +300 -224
  128. package/server/lib/git/service.test.js +596 -28
  129. package/server/lib/github/auth.js +8 -8
  130. package/server/lib/github/pr-status.js +1 -1
  131. package/server/lib/github/routes.js +2 -2
  132. package/server/lib/inherited-env.js +17 -10
  133. package/server/lib/inherited-env.test.js +10 -11
  134. package/server/lib/linear/auth.js +8 -8
  135. package/server/lib/linear/auth.test.js +24 -24
  136. package/server/lib/linear/issues.test.js +6 -6
  137. package/server/lib/linear/mapping.test.js +4 -4
  138. package/server/lib/linear/oauth.js +2 -2
  139. package/server/lib/linear/oauth.test.js +14 -14
  140. package/server/lib/linear/routes.js +5 -5
  141. package/server/lib/linear/routes.test.js +16 -16
  142. package/server/lib/linear/status-runtime.test.js +9 -9
  143. package/server/lib/linear/status.js +1 -1
  144. package/server/lib/linear/status.test.js +15 -15
  145. package/server/lib/linear/teams.test.js +4 -4
  146. package/server/lib/magic-prompts/routes.js +2 -2
  147. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  148. package/server/lib/mcp-reconnect/runtime.js +122 -0
  149. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  150. package/server/lib/message-queue/DOCUMENTATION.md +161 -0
  151. package/server/lib/message-queue/runtime.js +870 -0
  152. package/server/lib/message-queue/runtime.test.js +482 -0
  153. package/server/lib/network-defaults.js +23 -0
  154. package/server/lib/network-defaults.test.js +52 -0
  155. package/server/lib/notifications/apns-runtime.js +17 -17
  156. package/server/lib/notifications/apns-runtime.test.js +11 -11
  157. package/server/lib/notifications/emitter-runtime.js +1 -1
  158. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  159. package/server/lib/notifications/push-runtime.js +7 -7
  160. package/server/lib/notifications/routes.js +1 -1
  161. package/server/lib/notifications/runtime.js +1 -1
  162. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  163. package/server/lib/openchamber-control/actions.js +24 -24
  164. package/server/lib/openchamber-control/error.js +4 -4
  165. package/server/lib/openchamber-control/routes.js +2 -2
  166. package/server/lib/openchamber-control/routes.test.js +5 -5
  167. package/server/lib/openchamber-control/screenshots.js +1 -1
  168. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  169. package/server/lib/openchamber-control/service.js +50 -50
  170. package/server/lib/openchamber-control/service.test.js +5 -5
  171. package/server/lib/openchamber-sessions/routes.js +124 -22
  172. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  173. package/server/lib/opencode/DOCUMENTATION.md +30 -6
  174. package/server/lib/opencode/auth.js +1 -1
  175. package/server/lib/opencode/bootstrap-runtime.js +8 -6
  176. package/server/lib/opencode/cli-options.js +8 -8
  177. package/server/lib/opencode/config-paths.test.js +116 -0
  178. package/server/lib/opencode/core-routes.js +8 -7
  179. package/server/lib/opencode/core-routes.test.js +11 -11
  180. package/server/lib/opencode/env-config.js +5 -5
  181. package/server/lib/opencode/env-config.test.js +7 -7
  182. package/server/lib/opencode/env-runtime.js +10 -10
  183. package/server/lib/opencode/env-runtime.test.js +18 -18
  184. package/server/lib/opencode/feature-routes-runtime.js +19 -12
  185. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  186. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  187. package/server/lib/opencode/input-history-scope.js +14 -0
  188. package/server/lib/opencode/lifecycle.js +8 -8
  189. package/server/lib/opencode/lifecycle.test.js +4 -4
  190. package/server/lib/opencode/managed-plugin-config.js +35 -0
  191. package/server/lib/opencode/managed-process-registry.js +3 -3
  192. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  193. package/server/lib/opencode/openchamber-routes.js +79 -19
  194. package/server/lib/opencode/openchamber-routes.test.js +139 -12
  195. package/server/lib/opencode/plugins.js +2 -2
  196. package/server/lib/opencode/plugins.test.js +1 -1
  197. package/server/lib/opencode/project-icon-routes.js +2 -2
  198. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  199. package/server/lib/opencode/providers.js +44 -2
  200. package/server/lib/opencode/providers.test.js +131 -4
  201. package/server/lib/opencode/proxy.js +2 -2
  202. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  203. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  204. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  205. package/server/lib/opencode/routes.js +19 -17
  206. package/server/lib/opencode/server-startup-runtime.js +6 -6
  207. package/server/lib/opencode/session-runtime.js +3 -3
  208. package/server/lib/opencode/session-runtime.test.js +6 -6
  209. package/server/lib/opencode/settings-files.js +214 -0
  210. package/server/lib/opencode/settings-helpers.js +116 -44
  211. package/server/lib/opencode/settings-helpers.test.js +354 -12
  212. package/server/lib/opencode/settings-registry.json +747 -0
  213. package/server/lib/opencode/settings-runtime.js +141 -20
  214. package/server/lib/opencode/settings-runtime.test.js +191 -4
  215. package/server/lib/opencode/shared.js +5 -2
  216. package/server/lib/opencode/shared.test.js +7 -7
  217. package/server/lib/opencode/shutdown-runtime.js +2 -0
  218. package/server/lib/opencode/skill-routes.js +2 -1
  219. package/server/lib/opencode/snippets.js +1 -2
  220. package/server/lib/opencode/startup-performance.js +1 -1
  221. package/server/lib/opencode/startup-performance.test.js +6 -6
  222. package/server/lib/opencode/static-routes-runtime.js +8 -8
  223. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  224. package/server/lib/opencode/upgrade-capability.js +2 -2
  225. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  226. package/server/lib/package-manager.js +17 -42
  227. package/server/lib/package-manager.test.js +19 -19
  228. package/server/lib/permission-auto-accept/runtime.js +1 -1
  229. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  230. package/server/lib/project-context/routes.http.test.js +18 -0
  231. package/server/lib/project-context/routes.js +16 -1
  232. package/server/lib/project-context/runtime.js +248 -23
  233. package/server/lib/project-context/runtime.test.js +106 -3
  234. package/server/lib/projects/DOCUMENTATION.md +76 -0
  235. package/server/lib/projects/project-config.js +144 -11
  236. package/server/lib/projects/project-id.js +17 -0
  237. package/server/lib/projects/project-setup.js +409 -0
  238. package/server/lib/projects/project-setup.test.js +557 -0
  239. package/server/lib/projects/routes.js +59 -0
  240. package/server/lib/quota/DOCUMENTATION.md +40 -2
  241. package/server/lib/quota/credentials/providers.js +4 -0
  242. package/server/lib/quota/credentials/store.js +4 -4
  243. package/server/lib/quota/credentials/store.test.js +8 -8
  244. package/server/lib/quota/index.js +1 -0
  245. package/server/lib/quota/providers/cline-pass.js +125 -0
  246. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  247. package/server/lib/quota/providers/exe-dev.js +75 -0
  248. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  249. package/server/lib/quota/providers/hyper.js +118 -0
  250. package/server/lib/quota/providers/hyper.test.js +126 -0
  251. package/server/lib/quota/providers/index.js +22 -0
  252. package/server/lib/quota/providers/neuralwatt.js +3 -6
  253. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  254. package/server/lib/quota/providers/ollama-cloud.js +32 -1
  255. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  256. package/server/lib/quota/providers/opencode-go.js +3 -2
  257. package/server/lib/quota/providers/opencode-go.test.js +35 -6
  258. package/server/lib/quota/providers/openrouter.js +119 -24
  259. package/server/lib/quota/providers/openrouter.test.js +330 -0
  260. package/server/lib/quota/providers/xai.js +1 -1
  261. package/server/lib/quota/routes.js +2 -0
  262. package/server/lib/quota/utils/auth.js +1 -1
  263. package/server/lib/relay/DOCUMENTATION.md +56 -1
  264. package/server/lib/relay/cross-compat.test.js +26 -0
  265. package/server/lib/relay/downstream-scheduler.js +154 -0
  266. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  267. package/server/lib/relay/e2ee.js +10 -8
  268. package/server/lib/relay/flow-control.test.js +177 -0
  269. package/server/lib/relay/host-client.js +38 -9
  270. package/server/lib/relay/host-client.test.js +9 -1
  271. package/server/lib/relay/host-lock.js +1 -1
  272. package/server/lib/relay/service.js +5 -5
  273. package/server/lib/relay/tunnel-codec.js +9 -0
  274. package/server/lib/relay/tunnel-host.js +69 -18
  275. package/server/lib/relay/tunnel-host.test.js +9 -1
  276. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  277. package/server/lib/scheduled-tasks/routes.js +5 -5
  278. package/server/lib/scheduled-tasks/service.js +25 -25
  279. package/server/lib/scheduled-tasks/service.test.js +1 -1
  280. package/server/lib/security/bind-host.js +4 -4
  281. package/server/lib/security/bind-host.test.js +1 -1
  282. package/server/lib/security/request-security.js +11 -5
  283. package/server/lib/security/request-security.test.js +14 -14
  284. package/server/lib/session-assist/runtime.js +15 -18
  285. package/server/lib/session-folders/routes.js +2 -2
  286. package/server/lib/session-folders/routes.test.js +8 -8
  287. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  288. package/server/lib/session-goal/create.js +4 -2
  289. package/server/lib/session-goal/create.test.js +1 -1
  290. package/server/lib/session-goal/objectives.js +4 -4
  291. package/server/lib/session-goal/routes.js +1 -1
  292. package/server/lib/session-goal/runtime.js +91 -30
  293. package/server/lib/session-goal/runtime.test.js +428 -2
  294. package/server/lib/session-knowledge/runtime.js +11 -11
  295. package/server/lib/session-knowledge/runtime.test.js +5 -5
  296. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  297. package/server/lib/skills-catalog/cache.test.js +2 -2
  298. package/server/lib/skills-catalog/disk-cache.js +5 -5
  299. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  300. package/server/lib/skills-catalog/install.js +3 -2
  301. package/server/lib/small-model/DOCUMENTATION.md +7 -2
  302. package/server/lib/small-model/call.js +18 -7
  303. package/server/lib/small-model/call.test.js +58 -6
  304. package/server/lib/small-model/index.js +12 -16
  305. package/server/lib/small-model/index.test.js +1 -1
  306. package/server/lib/small-model/resolve.js +1 -1
  307. package/server/lib/small-model/resolve.test.js +1 -1
  308. package/server/lib/small-model/routes.js +2 -1
  309. package/server/lib/small-model/runtime-providers.js +15 -7
  310. package/server/lib/small-model/runtime-providers.test.js +7 -1
  311. package/server/lib/system-prompt/runtime.js +3 -22
  312. package/server/lib/system-prompt/runtime.test.js +3 -3
  313. package/server/lib/terminal/DOCUMENTATION.md +14 -10
  314. package/server/lib/terminal/runtime.js +190 -31
  315. package/server/lib/terminal/runtime.test.js +572 -38
  316. package/server/lib/terminal/shells.js +20 -3
  317. package/server/lib/terminal/shells.test.js +33 -2
  318. package/server/lib/tts/base-url.js +3 -3
  319. package/server/lib/tts/routes.test.js +20 -20
  320. package/server/lib/ui-auth/ui-auth.js +12 -9
  321. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  322. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  323. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  324. package/server/lib/walkthrough/index.js +1 -0
  325. package/server/lib/walkthrough/jobs.test.js +1 -1
  326. package/server/lib/walkthrough/language.test.js +1 -1
  327. package/server/lib/walkthrough/model-settings.js +11 -15
  328. package/server/lib/walkthrough/model-settings.test.js +1 -1
  329. package/server/lib/walkthrough/prompt.js +3 -1
  330. package/server/lib/walkthrough/pull-request.test.js +5 -5
  331. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  332. package/server/lib/walkthrough/routes.test.js +26 -5
  333. package/server/lib/walkthrough/sources.js +23 -2
  334. package/server/lib/walkthrough/store.js +3 -3
  335. package/server/lib/walkthrough/store.test.js +1 -1
  336. package/server/opencode-proxy.test.js +2 -2
  337. package/server/proxy-headers.js +1 -1
  338. package/server/sse-routes.test.js +4 -4
  339. package/README.md.bak +0 -250
  340. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  341. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  342. package/dist/assets/DiffView-DluiRSla.js +0 -4
  343. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  344. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  345. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  346. package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
  347. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  348. package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
  349. package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
  350. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  351. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  352. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  353. package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
  354. package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
  355. package/dist/assets/de-C1P6X5Rs.js +0 -9
  356. package/dist/assets/es-CDgaF1_t.js +0 -15
  357. package/dist/assets/fr-Bgxqvvz_.js +0 -13
  358. package/dist/assets/index-C57jT0Sa.css +0 -1
  359. package/dist/assets/index-DW_E3rnL.js +0 -7
  360. package/dist/assets/ja-BDRrEcNP.js +0 -15
  361. package/dist/assets/ko-Ba8sQV79.js +0 -15
  362. package/dist/assets/main-Bv5Pkngh.js +0 -928
  363. package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
  364. package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
  365. package/dist/assets/pl-Dioz29BI.js +0 -15
  366. package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
  367. package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
  368. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  369. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  370. package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
  371. package/dist/assets/tr-DFnMlPxY.js +0 -6
  372. package/dist/assets/uk-ZrCNjM7Y.js +0 -15
  373. package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
  374. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  375. package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
  376. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  377. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  378. package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
  379. package/dist/assets/zh-TW-CzEUk38j.js +0 -15
  380. package/server/index.d.ts.bak +0 -39
  381. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  382. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  383. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  384. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  385. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  386. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  387. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  388. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  389. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  390. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  391. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  392. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  393. package/server/lib/notifications/APNS.md.bak +0 -135
  394. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  395. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  396. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  397. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  398. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  399. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  400. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  401. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  402. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  403. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  404. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  405. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  406. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  407. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  408. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  409. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  410. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  411. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  412. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  413. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  414. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  415. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -1,5 +1,9 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import path from 'path';
3
+ import { copyFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { execFileSync } from 'node:child_process';
6
+ import { pathToFileURL } from 'node:url';
3
7
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
8
 
5
9
  import { mintOutsideFileGrant, registerFsRoutes } from './routes.js';
@@ -116,7 +120,7 @@ const registerExec = ({ spawn }) => {
116
120
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
117
121
  buildAugmentedPath: () => '/usr/bin',
118
122
  resolveGitBinaryForSpawn: () => 'git',
119
- openchamberUserConfigRoot: '/home/user/.config',
123
+ aiworkerUserConfigRoot: '/home/user/.config',
120
124
  });
121
125
  return getRoute('POST', '/api/fs/exec');
122
126
  };
@@ -136,7 +140,7 @@ const registerWrite = (fsPromises) => {
136
140
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
137
141
  buildAugmentedPath: () => '/usr/bin',
138
142
  resolveGitBinaryForSpawn: () => 'git',
139
- openchamberUserConfigRoot: '/home/user/.config',
143
+ aiworkerUserConfigRoot: '/home/user/.config',
140
144
  });
141
145
  return getRoute('POST', '/api/fs/write');
142
146
  };
@@ -160,7 +164,7 @@ const registerUpload = (fsPromises) => {
160
164
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
161
165
  buildAugmentedPath: () => '/usr/bin',
162
166
  resolveGitBinaryForSpawn: () => 'git',
163
- openchamberUserConfigRoot: '/home/user/.config',
167
+ aiworkerUserConfigRoot: '/home/user/.config',
164
168
  });
165
169
  return getRoute('POST', '/api/fs/upload');
166
170
  };
@@ -180,7 +184,7 @@ const registerRead = (fsPromises, resolveProjectDirectory = async () => ({ direc
180
184
  resolveProjectDirectory,
181
185
  buildAugmentedPath: () => '/usr/bin',
182
186
  resolveGitBinaryForSpawn: () => 'git',
183
- openchamberUserConfigRoot: '/home/user/.config',
187
+ aiworkerUserConfigRoot: '/home/user/.config',
184
188
  });
185
189
  return getRoute('GET', '/api/fs/read');
186
190
  };
@@ -200,7 +204,7 @@ const registerRaw = (fsPromises) => {
200
204
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
201
205
  buildAugmentedPath: () => '/usr/bin',
202
206
  resolveGitBinaryForSpawn: () => 'git',
203
- openchamberUserConfigRoot: '/home/user/.config',
207
+ aiworkerUserConfigRoot: '/home/user/.config',
204
208
  });
205
209
  return getRoute('GET', '/api/fs/raw');
206
210
  };
@@ -220,7 +224,7 @@ const registerMkdir = (fsPromises) => {
220
224
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
221
225
  buildAugmentedPath: () => '/usr/bin',
222
226
  resolveGitBinaryForSpawn: () => 'git',
223
- openchamberUserConfigRoot: '/home/user/.config',
227
+ aiworkerUserConfigRoot: '/home/user/.config',
224
228
  });
225
229
  return getRoute('POST', '/api/fs/mkdir');
226
230
  };
@@ -241,7 +245,7 @@ const registerReveal = ({ fsPromises, spawn, platform = 'linux' }) => {
241
245
  resolveProjectDirectory: async () => ({ directory: '/repo' }),
242
246
  buildAugmentedPath: () => '/usr/bin',
243
247
  resolveGitBinaryForSpawn: () => 'git',
244
- openchamberUserConfigRoot: '/home/user/.config',
248
+ aiworkerUserConfigRoot: '/home/user/.config',
245
249
  });
246
250
  return getRoute('POST', '/api/fs/reveal');
247
251
  };
@@ -482,8 +486,8 @@ describe('fs upload', () => {
482
486
  });
483
487
 
484
488
  it('cleans up a partial temp file when the configured streaming limit is exceeded', async () => {
485
- const previous = process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES;
486
- process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES = '5';
489
+ const previous = process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
490
+ process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = '5';
487
491
  const write = vi.fn(async (_buffer, _offset, length) => ({ bytesWritten: length }));
488
492
  const fsPromises = {
489
493
  open: vi.fn(async () => ({ write, close: vi.fn(async () => undefined) })),
@@ -504,14 +508,14 @@ describe('fs upload', () => {
504
508
  expect(fsPromises.link).not.toHaveBeenCalled();
505
509
  expect(fsPromises.unlink).toHaveBeenCalledWith(expect.stringMatching(/^\/repo\/file\.bin\.upload-/));
506
510
  } finally {
507
- if (previous === undefined) delete process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES;
508
- else process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES = previous;
511
+ if (previous === undefined) delete process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
512
+ else process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = previous;
509
513
  }
510
514
  });
511
515
 
512
516
  it('rejects a declared oversized upload before opening a temp file', async () => {
513
- const previous = process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES;
514
- process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES = '5';
517
+ const previous = process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
518
+ process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = '5';
515
519
  const fsPromises = {
516
520
  open: vi.fn(async () => ({ write: vi.fn(), close: vi.fn() })),
517
521
  };
@@ -523,8 +527,8 @@ describe('fs upload', () => {
523
527
  expect(res.body).toEqual({ error: 'File exceeds maximum size of 5 bytes' });
524
528
  expect(fsPromises.open).not.toHaveBeenCalled();
525
529
  } finally {
526
- if (previous === undefined) delete process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES;
527
- else process.env.OPENCHAMBER_FS_UPLOAD_MAX_BYTES = previous;
530
+ if (previous === undefined) delete process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
531
+ else process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = previous;
528
532
  }
529
533
  });
530
534
 
@@ -843,11 +847,11 @@ describe('fs reveal', () => {
843
847
 
844
848
  describe('fs exec git-read cache', () => {
845
849
  beforeEach(() => {
846
- delete process.env.OPENCHAMBER_GIT_READ_CACHE_TTL_MS;
850
+ delete process.env.AIWORKER_GIT_READ_CACHE_TTL_MS;
847
851
  });
848
852
 
849
853
  afterEach(() => {
850
- delete process.env.OPENCHAMBER_GIT_READ_CACHE_TTL_MS;
854
+ delete process.env.AIWORKER_GIT_READ_CACHE_TTL_MS;
851
855
  });
852
856
 
853
857
  it('rejects background command execution', async () => {
@@ -956,7 +960,7 @@ describe('fs exec git-read cache', () => {
956
960
  });
957
961
 
958
962
  it('disables caching when TTL is 0', async () => {
959
- process.env.OPENCHAMBER_GIT_READ_CACHE_TTL_MS = '0';
963
+ process.env.AIWORKER_GIT_READ_CACHE_TTL_MS = '0';
960
964
  const command = 'git rev-parse --absolute-git-dir';
961
965
  const { spawn, calls } = createSpawn({ stdoutByCommand: { [command]: '/repo/.git\n' } });
962
966
  const handler = registerExec({ spawn });
@@ -1062,7 +1066,7 @@ describe('fs list symlink path space (issue 2627)', () => {
1062
1066
  resolveProjectDirectory: async () => ({ directory: '/workspace' }),
1063
1067
  buildAugmentedPath: () => '/usr/bin',
1064
1068
  resolveGitBinaryForSpawn: () => 'git',
1065
- openchamberUserConfigRoot: '/home/user/.config',
1069
+ aiworkerUserConfigRoot: '/home/user/.config',
1066
1070
  });
1067
1071
  return getRoute('GET', '/api/fs/list');
1068
1072
  };
@@ -1162,7 +1166,7 @@ describe('fs git-dirs', () => {
1162
1166
  resolveProjectDirectory: async () => ({ directory: '/workspace' }),
1163
1167
  buildAugmentedPath: () => '/usr/bin',
1164
1168
  resolveGitBinaryForSpawn: () => 'git',
1165
- openchamberUserConfigRoot: '/home/user/.config',
1169
+ aiworkerUserConfigRoot: '/home/user/.config',
1166
1170
  });
1167
1171
  return { handler: getRoute('GET', '/api/fs/git-dirs'), readdir };
1168
1172
  };
@@ -1385,7 +1389,11 @@ describe('fs stat directory scope (issue 3019)', () => {
1385
1389
  path: path.posix,
1386
1390
  fsPromises: {
1387
1391
  realpath: async (targetPath) => targetPath,
1388
- stat: async () => ({ isFile: () => true, size: 12 }),
1392
+ stat: async (targetPath) => (
1393
+ targetPath === '/repo-b'
1394
+ ? { isDirectory: () => true, mtimeMs: 123 }
1395
+ : { isFile: () => true, size: 12, mtimeMs: 456 }
1396
+ ),
1389
1397
  },
1390
1398
  spawn: vi.fn(),
1391
1399
  crypto: { randomUUID: () => 'job-0' },
@@ -1393,7 +1401,7 @@ describe('fs stat directory scope (issue 3019)', () => {
1393
1401
  resolveProjectDirectory: projectDirectoryRuntime.resolveProjectDirectory,
1394
1402
  buildAugmentedPath: () => '/usr/bin',
1395
1403
  resolveGitBinaryForSpawn: () => 'git',
1396
- openchamberUserConfigRoot: '/home/user/.config',
1404
+ aiworkerUserConfigRoot: '/home/user/.config',
1397
1405
  });
1398
1406
  return getRoute('GET', '/api/fs/stat');
1399
1407
  };
@@ -1428,4 +1436,182 @@ describe('fs stat directory scope (issue 3019)', () => {
1428
1436
  expect(res.statusCode).toBe(200);
1429
1437
  expect(res.body.isFile).toBe(true);
1430
1438
  });
1439
+
1440
+ });
1441
+
1442
+ describe('fs stat directory error handling', () => {
1443
+ it('loads in Node without workspace node_modules, as packaged desktop does', async () => {
1444
+ const directory = await mkdtemp(path.join(tmpdir(), 'openchamber-fs-import-'));
1445
+ try {
1446
+ await mkdir(path.join(directory, 'fs'));
1447
+ await copyFile(new URL('./routes.js', import.meta.url), path.join(directory, 'fs/routes.mjs'));
1448
+ await copyFile(new URL('../path-realpath-cache.js', import.meta.url), path.join(directory, 'path-realpath-cache.js'));
1449
+ expect(() => execFileSync('node', [
1450
+ '--input-type=module',
1451
+ '--eval',
1452
+ 'await import(process.argv[1])',
1453
+ pathToFileURL(path.join(directory, 'fs/routes.mjs')).href,
1454
+ ], { cwd: directory, stdio: 'pipe' })).not.toThrow();
1455
+ } finally {
1456
+ await rm(directory, { recursive: true, force: true });
1457
+ }
1458
+ });
1459
+
1460
+ it('returns directory-missing reasons and permission errors for directory stat', async () => {
1461
+ const { app, getRoute } = createRouteRegistry();
1462
+ const enoent = Object.assign(new Error('missing'), { code: 'ENOENT' });
1463
+ const enotdir = Object.assign(new Error('not a directory'), { code: 'ENOTDIR' });
1464
+ const eacces = Object.assign(new Error('denied'), { code: 'EACCES' });
1465
+ const stat = vi.fn(async (targetPath) => {
1466
+ if (targetPath === '/repo-b') throw enoent;
1467
+ if (targetPath === '/repo-b/file.txt/child') throw enotdir;
1468
+ if (targetPath === '/repo-b/protected') throw eacces;
1469
+ if (targetPath === '/repo-b/file.txt') return { isDirectory: () => false };
1470
+ if (targetPath === '/repo-b/failure') throw new Error('unavailable');
1471
+ return { isDirectory: () => true, mtimeMs: 1 };
1472
+ });
1473
+ const readdir = vi.fn(async () => []);
1474
+ const callStat = async (handler, { headers = {}, query }) => {
1475
+ const res = createMockResponse();
1476
+ const req = {
1477
+ url: `/api/fs/directory-stat?${new URLSearchParams(query)}`,
1478
+ query,
1479
+ get: (name) => headers[name.toLowerCase()] ?? undefined,
1480
+ };
1481
+ await handler(req, res);
1482
+ return res;
1483
+ };
1484
+ registerFsRoutes(app, {
1485
+ os: { homedir: () => '/home/user' },
1486
+ path: path.posix,
1487
+ fsPromises: {
1488
+ realpath: async (targetPath) => targetPath,
1489
+ stat,
1490
+ readdir,
1491
+ },
1492
+ spawn: vi.fn(),
1493
+ crypto: { randomUUID: () => 'job-0' },
1494
+ normalizeDirectoryPath: (p) => p,
1495
+ resolveProjectDirectory: async () => ({ directory: '/repo' }),
1496
+ buildAugmentedPath: () => '/usr/bin',
1497
+ resolveGitBinaryForSpawn: () => 'git',
1498
+ aiworkerUserConfigRoot: '/home/user/.config',
1499
+ });
1500
+ const handler = getRoute('GET', '/api/fs/directory-stat');
1501
+
1502
+ const available = await callStat(handler, { query: { path: '/other-project' } });
1503
+ expect(available.statusCode).toBe(200);
1504
+ expect(available.body).toEqual({ isDirectory: true });
1505
+ expect(available.getHeader('Cache-Control')).toBe('no-store');
1506
+ expect(stat).toHaveBeenCalledTimes(1);
1507
+
1508
+ const invalid = await callStat(handler, { query: { path: ' ' } });
1509
+ expect(invalid.statusCode).toBe(400);
1510
+ expect(stat).toHaveBeenCalledTimes(1);
1511
+
1512
+ for (const query of ['path=/repo&path=/other', 'path[]=/repo', '']) {
1513
+ const malformed = createMockResponse();
1514
+ await handler({ url: `/api/fs/directory-stat?${query}` }, malformed);
1515
+ expect(malformed.statusCode).toBe(400);
1516
+ }
1517
+ expect(stat).toHaveBeenCalledTimes(1);
1518
+
1519
+ const missing = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b', directory: 'true' } });
1520
+ expect(missing.statusCode).toBe(404);
1521
+ expect(missing.body).toEqual({ error: 'Directory not found', reason: 'not-found' });
1522
+
1523
+ const notDir = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b/file.txt/child', directory: 'true' } });
1524
+ expect(notDir.statusCode).toBe(400);
1525
+ expect(notDir.body).toEqual({ error: 'Specified path is not a directory', reason: 'not-directory' });
1526
+
1527
+ const denied = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b/protected', directory: 'true' } });
1528
+ expect(denied.statusCode).toBe(403);
1529
+ expect(denied.body).toEqual({ error: 'Access to directory denied', reason: 'os-permission' });
1530
+
1531
+ const file = await callStat(handler, { query: { path: '/repo-b/file.txt' } });
1532
+ expect(file.statusCode).toBe(400);
1533
+ expect(file.body.reason).toBe('not-directory');
1534
+
1535
+ const failure = await callStat(handler, { query: { path: '/repo-b/failure' } });
1536
+ expect(failure.statusCode).toBe(500);
1537
+ expect(failure.body).toEqual({ error: 'Failed to stat directory' });
1538
+ expect(readdir).not.toHaveBeenCalled();
1539
+ });
1540
+ });
1541
+
1542
+ describe('fs managed chats root', () => {
1543
+ const registerWithChatsRoot = ({ managedChatsRoot, fsPromises = {} } = {}) => {
1544
+ const { app, getRoute } = createRouteRegistry();
1545
+ registerFsRoutes(app, {
1546
+ os: { homedir: () => '/home/user' },
1547
+ path: path.posix,
1548
+ fsPromises: {
1549
+ realpath: async (targetPath) => targetPath,
1550
+ mkdir: async () => undefined,
1551
+ ...fsPromises,
1552
+ },
1553
+ spawn: vi.fn(),
1554
+ crypto: { randomUUID: () => 'job-0' },
1555
+ normalizeDirectoryPath: (p) => p,
1556
+ resolveProjectDirectory: async () => ({ directory: '/repo' }),
1557
+ buildAugmentedPath: () => '/usr/bin',
1558
+ resolveGitBinaryForSpawn: () => 'git',
1559
+ aiworkerUserConfigRoot: '/home/user/.config/openchamber',
1560
+ managedChatsRoot,
1561
+ });
1562
+ return {
1563
+ home: getRoute('GET', '/api/fs/home'),
1564
+ mkdir: getRoute('POST', '/api/fs/mkdir'),
1565
+ };
1566
+ };
1567
+
1568
+ it('exposes the default chats root next to the home directory', async () => {
1569
+ const { home } = registerWithChatsRoot();
1570
+
1571
+ const res = createMockResponse();
1572
+ await home(undefined, res);
1573
+
1574
+ expect(res.statusCode).toBe(200);
1575
+ expect(res.body.home).toBe('/home/user');
1576
+ expect(res.body.chatsRoot).toBe('/home/user/.config/openchamber/chats');
1577
+ });
1578
+
1579
+ it('exposes a relocated chats root when AIWORKER_CHATS_DIR is configured upstream', async () => {
1580
+ const { home } = registerWithChatsRoot({ managedChatsRoot: '/srv/openchamber-chats' });
1581
+
1582
+ const res = createMockResponse();
1583
+ await home(undefined, res);
1584
+
1585
+ expect(res.statusCode).toBe(200);
1586
+ expect(res.body.chatsRoot).toBe('/srv/openchamber-chats');
1587
+ });
1588
+
1589
+ it('allows mkdir inside the relocated chats root outside the active workspace', async () => {
1590
+ const mkdirCalls = [];
1591
+ const { mkdir } = registerWithChatsRoot({
1592
+ managedChatsRoot: '/srv/openchamber-chats',
1593
+ fsPromises: {
1594
+ mkdir: async (targetPath) => {
1595
+ mkdirCalls.push(targetPath);
1596
+ },
1597
+ },
1598
+ });
1599
+
1600
+ const res = createMockResponse();
1601
+ await mkdir({ body: { path: '/srv/openchamber-chats/2026-08-25/session-a' } }, res);
1602
+
1603
+ expect(res.statusCode).toBe(200);
1604
+ expect(res.body.success).toBe(true);
1605
+ expect(mkdirCalls).toEqual(['/srv/openchamber-chats/2026-08-25/session-a']);
1606
+ });
1607
+
1608
+ it('still rejects mkdir outside the workspace and all managed roots', async () => {
1609
+ const { mkdir } = registerWithChatsRoot({ managedChatsRoot: '/srv/openchamber-chats' });
1610
+
1611
+ const res = createMockResponse();
1612
+ await mkdir({ body: { path: '/etc/passwd-holder' } }, res);
1613
+
1614
+ expect(res.statusCode).toBe(400);
1615
+ expect(res.body).toEqual({ error: 'Path is outside of active workspace' });
1616
+ });
1431
1617
  });
@@ -25,9 +25,11 @@ The following functions are exported and used by the web server:
25
25
 
26
26
  ### Status and Diff Operations
27
27
  - `getStatus(directory)`: Get comprehensive Git status including current branch, tracking, ahead/behind, file changes, diff stats, merge/rebase state.
28
- - `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree. Untracked symbolic links are represented as link entries without following their targets.
29
- - `getRangeDiff(directory, { base, head, path, contextLines })`: Get diff between two refs. Uses three-dot `base...head` semantics, so work merged into `head` from `base` is excluded and only the branch's own changes are returned. Prefers `origin/<base>` when that remote-tracking ref exists, so a stale local base branch does not resurface already-merged commits. Exposed as `GET /api/git/range-diff` (`path` optional; omit it for the whole range).
30
- - `getRangeFiles(directory, { base, head })`: Get list of changed files between two refs.
28
+ - `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree with full Git blob identities. Untracked symbolic links are represented as link entries without following their targets.
29
+ - `getRangeDiff(directory, { base, head, path, contextLines, includeWorkingTree })`: Compare the merge base of the exact selected refs with `head`. With `includeWorkingTree: true`, compare with the checked-out branch's current files instead, including committed, staged, unstaged, and untracked work in one net diff. This mode rejects a head that is not the checked-out branch. Exposed as `GET /api/git/range-diff`; omit `path` for the whole comparison.
30
+ - `getRangeFiles(directory, { base, head, includeWorkingTree })`: List changed paths using the same comparison as `getRangeDiff`. A successful empty list means the final files match the merge base, even if staging and working-tree changes cancel each other out.
31
+ - Both range operations honor refs literally. A local `main` is never replaced with `origin/main`, and an unavailable ref fails rather than choosing a different remote. The UI picker sends qualified refs to distinguish local and remote branches with matching display names.
32
+ - Working-tree comparisons use the real index read-only. When untracked paths exist, a temporary copy of the index receives intent-to-add entries so Git computes additions, deletions, recreations, and renames together. Current contents come from the working tree, symlinks remain links, ignored files stay excluded, and temporary files are removed on success or failure.
31
33
  - `getFileDiff(directory, { path, staged })`: Get original and modified file contents for a single file (handles images as data URLs and symbolic links as their link-target text).
32
34
  - `listUntrackedPaths(directory)`: List individual untracked file paths honoring ignore rules. Much cheaper than `getStatus` when that is all a caller needs. Deliberately not `--directory`: collapsed directory entries end in a slash and are rejected by the per-file diff helpers, so a caller would silently lose every file inside a new directory.
33
35
  - `getUntrackedDiffs(directory, filePaths, { concurrency, contextLines })`: Diffs for untracked files against an empty tree. Resolves the repository context once instead of per file (`getDiff` re-resolves every call, costing an extra `rev-parse` each time) and bounds how many diff processes run at once. Returns one entry per input path in order; unreadable paths yield `''` rather than failing the batch.
@@ -35,10 +37,12 @@ The following functions are exported and used by the web server:
35
37
  - `revertFile(directory, filePath, options)`: Revert a file. Default scope `all` discards staged and working-tree changes; scope `working` discards only unstaged/working-tree changes.
36
38
  - `stageFile(directory, filePath)`: Add one file path to the index.
37
39
  - `unstageFile(directory, filePath)`: Remove one file path from the index while preserving working-tree content.
38
- - `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree). The patch is written to a temp file; a `--check` runs first so a stale hunk fails with a clear "refresh and try again" error instead of a partial mutation. The patch target path must match the requested file.
40
+ - `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree). Inside the index mutation queue, the server verifies that the complete patch exactly matches one current three-context-line hunk for that file and scope, then runs `--check` before applying. Applicability alone cannot prove an unstaged change: old staged or committed hunks can reverse cleanly too. Stale, historical and multi-file patches fail with a refresh error. Temporary patch files are removed on success and failure; hunk content retains CRLF bytes.
39
41
 
40
42
  ### Branch Operations
43
+ - `getBranchBase(directory, branch)`: Read a named creation source from reflog. After a rebase, the creation source is no longer a current parent record, so return `null` and let the user choose a base. Explicit per-runtime, directory, and branch choices in the shared UI outrank detection.
41
44
  - `getBranches(directory)`: Get list of local and remote branches (filtered to active remote branches).
45
+ - `getUnpushedBranchCounts(directory, branchNames)`: Count commits ahead of each locally known upstream for up to five supplied local branches. This reads local refs only and omits branches without an upstream.
42
46
  - `createBranch(directory, branchName, options)`: Create and checkout a new branch.
43
47
  - `checkoutBranch(directory, branchName)`: Checkout an existing branch. A remote-tracking name (`origin/main`, or the `remotes/`-prefixed form) resolves to the local branch of that name, created with `--track` when it does not exist yet, because the branch selector offers remote branches as places to work rather than commits to inspect — a literal checkout of the remote ref would detach HEAD. A local branch whose own name looks like a remote ref wins over that resolution, and anything unresolvable is checked out as requested. The returned `branch` is the branch that was actually checked out, which callers should report instead of the requested name.
44
48
  - `deleteBranch(directory, branch, options)`: Delete a branch (supports force flag).
@@ -48,7 +52,7 @@ The following functions are exported and used by the web server:
48
52
  ### Worktree Operations
49
53
  - `getWorktrees(directory)`: List all git worktrees for a repository.
50
54
  - `validateWorktreeCreate(directory, input)`: Validate worktree creation parameters (mode, branchName, startRef, upstream config).
51
- - `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup). After populating the worktree, the repository's `post-checkout` hook runs once with git's standard arguments (null ref as previous HEAD, the checked-out HEAD, and flag `1`) from the worktree directory, mirroring `git worktree add` without `--no-checkout`; a missing or non-executable hook is skipped and a failing hook is logged as a warning, never failing worktree creation or the session bootstrap.
55
+ - `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup). When the current tracked branch has no unpublished commits, the UI supplies its remote-tracking ref and this operation fetches that branch once before creating the worktree. A failed fetch falls back to the local branch and reports `sourceFetchFailed`; other remote start refs still require an existing local ref when their fetch fails. After populating the worktree, the repository's `post-checkout` hook runs once with git's standard arguments (null ref as previous HEAD, the checked-out HEAD, and flag `1`) from the worktree directory, mirroring `git worktree add` without `--no-checkout`; a missing or non-executable hook is skipped and a failing hook is logged as a warning, never failing worktree creation or the session bootstrap.
52
56
  - `removeWorktree(directory, input)`: Remove a worktree (optionally delete local branch).
53
57
  - `isLinkedWorktree(directory)`: Check if directory is a linked worktree (not primary).
54
58
 
@@ -70,7 +74,8 @@ bootstrap, tracking is left unset rather than writing `branch.*.remote` /
70
74
 
71
75
  ### Log Operations
72
76
  - `getLog(directory, options)`: Get commit history with stats (supports maxCount, from, to, file filters).
73
- - `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit.
77
+ - `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit relative to its first parent, or the empty tree for a root commit. NUL-delimited paths preserve whitespace; renamed files return their destination in `path` and source in `previousPath`.
78
+ - `getCommitDiff(directory, { hash, path, previousPath, contextLines })`: Get the same commit's patch, with optional file filtering and context depth. `previousPath` keeps a rename's old and new paths in the per-file patch. Reads committed objects only, never the working tree. Exposed as `GET /api/git/commit-diff`; an unavailable hash fails rather than returning an empty diff.
74
79
  - `getCommitFileDiff(directory, hash, filePath, isBinary)`: Get before/after content for a specific file in a commit. Returns `{ original, modified, isBinary }`. Runs `git show <hash>^:<path>` and `git show <hash>:<path>` in parallel; returns empty strings on failure (added/deleted/root-commit edge cases).
75
80
 
76
81
  ### Merge and Rebase Operations
@@ -129,8 +134,28 @@ The following functions are internal helpers used by exported functions:
129
134
 
130
135
  ### Runtime availability of range diffs
131
136
  - `GET /api/git/range-diff` is served by the AiWorker web server, so it is available to web, desktop, and mobile clients. The shared `GitAPI.getGitRangeDiff` is therefore optional: web supplies the HTTP implementation, and VS Code does not implement it because the extension host serves Git through its own bridge rather than these routes. Features built on range diffs (currently the AI diff walkthrough) are not offered in VS Code.
137
+ - Commit comparison uses the same server boundary through optional `GitAPI.getGitCommitDiff`. Desktop Changes, mobile Changes, and the existing walkthrough surface share branch/commit comparison semantics. Mobile Changes uses the same selectors and `useGitComparison` file-list owner, with a read-only list-to-detail flow. VS Code keeps its existing modes because its Git bridge does not provide these comparison operations. The HTTP operations are available to web, Electron, hosted mobile, and Capacitor clients.
132
138
 
133
139
  ### Staged and unstaged change handling
140
+ - Desktop Changes floats a compact action capsule after each hunk's last changed row,
141
+ including single-hunk files. Whole-file controls remain in the Git panel.
142
+ `getPatchHunkAnchors` uses the canonical patch's final changed row and side, so a hunk
143
+ ending in deletions is anchored after those deletions rather than above them.
144
+ Zero-height Pierre annotation slots anchor the capsule over following context
145
+ without a separate band. At EOF the capsule lifts inside the code column;
146
+ a one-line code column has a minimum hit-target height. React controls mount only
147
+ for currently rendered slots and only after their rendered diff and anchor
148
+ identities match the current props. Comment annotations remain independent.
149
+ - The canonical three-line-context action patch stays separate from the full-file
150
+ display patch. Their bytes must be identical, or their file headers and full
151
+ blob identities must match, including when reusing a cached action patch.
152
+ Mismatch leaves actions unavailable until Retry obtains a matching pair.
153
+ Successful hunk mutations invalidate
154
+ every mounted view of that path through `sessionEvents.requestGitRefresh`.
155
+ Actions remain unavailable until the refresh succeeds. Last turn, Branch and
156
+ Commit snapshots never expose hunk mutations. Mobile uses its separate Changes
157
+ surface and VS Code does not mount these controls.
158
+ - Untracked patches from `getDiff` and `getUntrackedDiffs` use `git diff --no-index` with separate stdout, stderr, and process exit status. Exit codes 0 and 1 return stdout only, so line-ending warnings never become patch text or request failures. Other exits and process failures reject the single-file request; the batch keeps an empty entry for the failed path and preserves the other results.
134
159
  - `status.files` exposes both `index` and `working_dir` codes. Shared UI uses these as separate scopes: staged rows are derived from non-empty `index` statuses, while unstaged rows are derived from `working_dir` statuses and untracked files.
135
160
  - A file with both staged and unstaged changes can appear in both UI sections. Staged rows request diffs with `staged: true`; unstaged rows request normal working-tree diffs.
136
161
  - The shared Git panel exposes explicit staging actions. Unstaged rows use `stageFile`, staged rows use `unstageFile`, and commits operate on the current staged index.
@@ -142,6 +167,7 @@ The following functions are internal helpers used by exported functions:
142
167
  - `path`: Absolute path to worktree directory.
143
168
  - `directoryCreated`: Present when create returned after the target directory exists while background Git/bootstrap work continues.
144
169
  - `bootstrapStatus`: Background setup state. The legacy `status` remains `pending`, `ready`, or `failed`, while `phase` reports `directory-created`, `git-ready`, or `setup-ready`. Fast create starts at `pending`/`directory-created`; population and upstream Git completion advances to `pending`/`git-ready` before setup/start scripts; completed setup is `ready`/`setup-ready`. A missing in-memory state falls back to `ready`/`setup-ready`; clients continue to accept legacy status responses that omit `phase`.
170
+ - `sourceFetchFailed`: Present when the automatic source-branch fetch failed and creation fell back to the tracked local branch.
145
171
  - Fast-create background failures remove OpenCode sandbox metadata for directories that never became Git worktrees, and remove the pre-created directory only if it is still empty. User-created files are never recursively deleted by this cleanup.
146
172
  - Worktree removal waits for any active create/bootstrap task for that directory before deleting it, preventing a background Git or setup task from restoring removed state or racing filesystem cleanup.
147
173
  - Worktree bootstrap retries transient `index.lock` conflicts. If the lock remains byte-for-byte and metadata-identical across the retry window, it is treated as stale, removed, and population continues automatically; changing locks are left untouched and reported as failures.
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
 
5
- const STORAGE_DIR = path.join(os.homedir(), '.config', 'openchamber');
5
+ const STORAGE_DIR = path.join(os.homedir(), '.config', 'aiworker');
6
6
  const STORAGE_FILE = path.join(STORAGE_DIR, 'git-identities.json');
7
7
 
8
8
  function ensureStorageDir() {
@@ -12,11 +12,11 @@ import {
12
12
  } from './service.js';
13
13
 
14
14
  // ---------------------------------------------------------------------------
15
- // Regression for https://github.com/openchamber/openchamber/issues/2746
15
+ // Regression for https://roweb.cn/roweb/openchamber/issues/2746
16
16
  //
17
17
  // "[Bug] new worktree, Filename too long"
18
18
  //
19
- // OpenChamber places worktrees under:
19
+ // AiWorker places worktrees under:
20
20
  // <XDG_DATA_HOME>/opencode/worktree/<40-char root commit hash>/<worktree name>
21
21
  // and populates them with `git reset --hard`. On Windows, that deep prefix plus
22
22
  // a deeply nested repo file (e.g. yudao ~173 chars) exceeds MAX_PATH (260) and
@@ -95,7 +95,7 @@ describe('issue #2746 - worktree long path support', () => {
95
95
  const created = await createWorktree(repo, {
96
96
  mode: 'new',
97
97
  worktreeName: 'issue-2746',
98
- branchName: 'openchamber/issue-2746',
98
+ branchName: 'aiworker/issue-2746',
99
99
  });
100
100
  expect(created.directoryCreated).toBe(true);
101
101
 
@@ -162,7 +162,7 @@ describe('issue #2746 - worktree long path support', () => {
162
162
  const created = await createWorktree(repo, {
163
163
  mode: 'new',
164
164
  worktreeName: 'issue-2746-namemax',
165
- branchName: 'openchamber/issue-2746-namemax',
165
+ branchName: 'aiworker/issue-2746-namemax',
166
166
  });
167
167
  expect(created.directoryCreated).toBe(true);
168
168
 
@@ -7,13 +7,13 @@ export function registerGitRoutes(app) {
7
7
  return gitLibraries;
8
8
  };
9
9
 
10
- const resolveDirectoryQuery = (value) => {
10
+ const resolveDirectoryQuery = (value, preserveWhitespace = false) => {
11
11
  const raw = Array.isArray(value) ? value[0] : value;
12
12
  if (typeof raw !== 'string') {
13
13
  return null;
14
14
  }
15
- const trimmed = raw.trim();
16
- return trimmed || null;
15
+ const normalized = preserveWhitespace ? raw : raw.trim();
16
+ return normalized || null;
17
17
  };
18
18
 
19
19
  const extractGitErrorText = (error) => {
@@ -417,6 +417,7 @@ export function registerGitRoutes(app) {
417
417
  const diff = await getRangeDiff(directory, {
418
418
  base,
419
419
  head,
420
+ includeWorkingTree: req.query.includeWorkingTree === 'true',
420
421
  path: pathParam,
421
422
  contextLines: Number.isFinite(context) ? context : 3,
422
423
  });
@@ -463,7 +464,7 @@ export function registerGitRoutes(app) {
463
464
  return res.status(400).json({ error: 'base and head parameters are required' });
464
465
  }
465
466
 
466
- const files = await getRangeFiles(directory, { base, head });
467
+ const files = await getRangeFiles(directory, { base, head, includeWorkingTree: req.query.includeWorkingTree === 'true' });
467
468
  res.json({ files });
468
469
  } catch (error) {
469
470
  console.error('Failed to get git range files:', error);
@@ -873,6 +874,22 @@ export function registerGitRoutes(app) {
873
874
  }
874
875
  });
875
876
 
877
+ app.post('/api/git/branch-push-status', async (req, res) => {
878
+ const { getUnpushedBranchCounts } = await getGitLibraries();
879
+ try {
880
+ const directory = req.query.directory;
881
+ const branches = req.body?.branches;
882
+ if (!directory) return res.status(400).json({ error: 'directory parameter is required' });
883
+ if (!Array.isArray(branches) || branches.some((branch) => typeof branch !== 'string')) {
884
+ return res.status(400).json({ error: 'branches must be an array of branch names' });
885
+ }
886
+ res.json(await getUnpushedBranchCounts(directory, branches));
887
+ } catch (error) {
888
+ console.error('Failed to get branch push status:', error);
889
+ res.status(500).json({ error: error.message || 'Failed to get branch push status' });
890
+ }
891
+ });
892
+
876
893
  app.post('/api/git/branches', async (req, res) => {
877
894
  const { createBranch } = await getGitLibraries();
878
895
  try {
@@ -1074,7 +1091,7 @@ export function registerGitRoutes(app) {
1074
1091
  // Worktrees are an optional feature. Avoid repeated 500s (and repeated client retries)
1075
1092
  // when the directory isn't a git repo or uses shell shorthand like "~/".
1076
1093
  console.warn('Failed to get worktrees, returning empty list:', error?.message || error);
1077
- res.setHeader('X-OpenChamber-Warning', 'git worktrees unavailable');
1094
+ res.setHeader('X-AiWorker-Warning', 'git worktrees unavailable');
1078
1095
  res.json([]);
1079
1096
  }
1080
1097
  });
@@ -1284,6 +1301,25 @@ export function registerGitRoutes(app) {
1284
1301
  }
1285
1302
  });
1286
1303
 
1304
+ app.get('/api/git/commit-diff', async (req, res) => {
1305
+ const { getCommitDiff } = await getGitLibraries();
1306
+ try {
1307
+ const directory = resolveDirectoryQuery(req.query.directory);
1308
+ const hash = resolveDirectoryQuery(req.query.hash);
1309
+ if (!directory || !hash) return res.status(400).json({ error: 'directory and hash are required' });
1310
+ const context = Number(req.query.context ?? 3);
1311
+ const diff = await getCommitDiff(directory, {
1312
+ hash,
1313
+ path: resolveDirectoryQuery(req.query.path, true) ?? undefined,
1314
+ previousPath: resolveDirectoryQuery(req.query.previousPath, true) ?? undefined,
1315
+ contextLines: Number.isFinite(context) ? context : 3,
1316
+ });
1317
+ res.json({ diff });
1318
+ } catch (error) {
1319
+ res.status(500).json({ error: error.message || 'Failed to get commit diff' });
1320
+ }
1321
+ });
1322
+
1287
1323
  app.get('/api/git/commit-file-diff', async (req, res) => {
1288
1324
  const { getCommitFileDiff } = await getGitLibraries();
1289
1325
  try {