@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.20

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 (389) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-cli-instructions.md +6 -2
  4. package/dist/agent-cli.js +15 -7
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +3 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.js +16 -4
  9. package/dist/agent-whats-new.js.map +1 -1
  10. package/dist/auth-api.d.ts +3 -1
  11. package/dist/auth-api.js +17 -1
  12. package/dist/auth-api.js.map +1 -1
  13. package/dist/backend/antigravity.d.ts +33 -0
  14. package/dist/backend/antigravity.js +126 -18
  15. package/dist/backend/antigravity.js.map +1 -1
  16. package/dist/backend/claude-carry.d.ts +22 -0
  17. package/dist/backend/claude-carry.js +170 -0
  18. package/dist/backend/claude-carry.js.map +1 -0
  19. package/dist/backend/claude-code.d.ts +65 -2
  20. package/dist/backend/claude-code.js +226 -33
  21. package/dist/backend/claude-code.js.map +1 -1
  22. package/dist/backend/codex-metadata.d.ts +33 -0
  23. package/dist/backend/codex-metadata.js +122 -18
  24. package/dist/backend/codex-metadata.js.map +1 -1
  25. package/dist/backend/codex.d.ts +3 -0
  26. package/dist/backend/codex.js +17 -1
  27. package/dist/backend/codex.js.map +1 -1
  28. package/dist/backend/credential-profile.d.ts +40 -3
  29. package/dist/backend/credential-profile.js +113 -5
  30. package/dist/backend/credential-profile.js.map +1 -1
  31. package/dist/backend/effort-metadata.d.ts +37 -1
  32. package/dist/backend/effort-metadata.js +86 -15
  33. package/dist/backend/effort-metadata.js.map +1 -1
  34. package/dist/backend/grok.d.ts +4 -0
  35. package/dist/backend/grok.js +14 -0
  36. package/dist/backend/grok.js.map +1 -1
  37. package/dist/backend/kiro.d.ts +27 -1
  38. package/dist/backend/kiro.js +63 -10
  39. package/dist/backend/kiro.js.map +1 -1
  40. package/dist/backend/launch-effort.d.ts +61 -0
  41. package/dist/backend/launch-effort.js +170 -0
  42. package/dist/backend/launch-effort.js.map +1 -0
  43. package/dist/backend/muse.d.ts +84 -0
  44. package/dist/backend/muse.js +449 -53
  45. package/dist/backend/muse.js.map +1 -1
  46. package/dist/backend/types.d.ts +48 -2
  47. package/dist/backend/types.js +12 -1
  48. package/dist/backend/types.js.map +1 -1
  49. package/dist/backend-command.d.ts +91 -0
  50. package/dist/backend-command.js +122 -0
  51. package/dist/backend-command.js.map +1 -0
  52. package/dist/background-work-ended.d.ts +18 -0
  53. package/dist/background-work-ended.js +30 -0
  54. package/dist/background-work-ended.js.map +1 -0
  55. package/dist/cache-service.d.ts +2 -0
  56. package/dist/cache-service.js +2 -1
  57. package/dist/cache-service.js.map +1 -1
  58. package/dist/channel/adapters/discord.d.ts +26 -1
  59. package/dist/channel/adapters/discord.js +103 -3
  60. package/dist/channel/adapters/discord.js.map +1 -1
  61. package/dist/channel/adapters/telegram.d.ts +35 -3
  62. package/dist/channel/adapters/telegram.js +122 -23
  63. package/dist/channel/adapters/telegram.js.map +1 -1
  64. package/dist/channel/mcp-tools.js +5 -3
  65. package/dist/channel/mcp-tools.js.map +1 -1
  66. package/dist/channel/message-edit-errors.d.ts +5 -0
  67. package/dist/channel/message-edit-errors.js +24 -0
  68. package/dist/channel/message-edit-errors.js.map +1 -0
  69. package/dist/channel/telegram-markdown.d.ts +6 -0
  70. package/dist/channel/telegram-markdown.js +104 -0
  71. package/dist/channel/telegram-markdown.js.map +1 -0
  72. package/dist/channel/tool-router.d.ts +2 -0
  73. package/dist/channel/tool-router.js +6 -5
  74. package/dist/channel/tool-router.js.map +1 -1
  75. package/dist/channel/types.d.ts +46 -4
  76. package/dist/cli.js +184 -34
  77. package/dist/cli.js.map +1 -1
  78. package/dist/command-table.d.ts +2 -2
  79. package/dist/command-table.js +5 -2
  80. package/dist/command-table.js.map +1 -1
  81. package/dist/compact-watch.d.ts +86 -0
  82. package/dist/compact-watch.js +288 -0
  83. package/dist/compact-watch.js.map +1 -0
  84. package/dist/config-validator.js +55 -5
  85. package/dist/config-validator.js.map +1 -1
  86. package/dist/config.d.ts +1 -0
  87. package/dist/config.js +2 -1
  88. package/dist/config.js.map +1 -1
  89. package/dist/cross-instance-envelope.js +2 -0
  90. package/dist/cross-instance-envelope.js.map +1 -1
  91. package/dist/daemon.d.ts +129 -1
  92. package/dist/daemon.js +754 -94
  93. package/dist/daemon.js.map +1 -1
  94. package/dist/delivery-outbox.d.ts +64 -8
  95. package/dist/delivery-outbox.js +315 -24
  96. package/dist/delivery-outbox.js.map +1 -1
  97. package/dist/delivery-reconciliation.d.ts +18 -1
  98. package/dist/delivery-reconciliation.js +67 -2
  99. package/dist/delivery-reconciliation.js.map +1 -1
  100. package/dist/doctor.js +7 -0
  101. package/dist/doctor.js.map +1 -1
  102. package/dist/fleet-context.d.ts +12 -1
  103. package/dist/fleet-manager.d.ts +392 -19
  104. package/dist/fleet-manager.js +2030 -275
  105. package/dist/fleet-manager.js.map +1 -1
  106. package/dist/forged-envelope-scan.d.ts +4 -3
  107. package/dist/forged-envelope-scan.js +4 -3
  108. package/dist/forged-envelope-scan.js.map +1 -1
  109. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +84 -16
  110. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +16 -1
  111. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  112. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
  113. package/dist/host-memory.d.ts +10 -0
  114. package/dist/host-memory.js +33 -1
  115. package/dist/host-memory.js.map +1 -1
  116. package/dist/human-delivery-receipts.d.ts +83 -0
  117. package/dist/human-delivery-receipts.js +194 -0
  118. package/dist/human-delivery-receipts.js.map +1 -0
  119. package/dist/install-lock.d.ts +18 -1
  120. package/dist/install-lock.js +30 -3
  121. package/dist/install-lock.js.map +1 -1
  122. package/dist/instance-avatar.d.ts +5 -4
  123. package/dist/instance-avatar.js +12 -8
  124. package/dist/instance-avatar.js.map +1 -1
  125. package/dist/instance-config-impact.js +1 -0
  126. package/dist/instance-config-impact.js.map +1 -1
  127. package/dist/instance-lifecycle.d.ts +25 -6
  128. package/dist/instance-lifecycle.js +76 -27
  129. package/dist/instance-lifecycle.js.map +1 -1
  130. package/dist/instructions.d.ts +1 -1
  131. package/dist/instructions.js +3 -3
  132. package/dist/instructions.js.map +1 -1
  133. package/dist/kiro-compact-pane.d.ts +38 -0
  134. package/dist/kiro-compact-pane.js +57 -0
  135. package/dist/kiro-compact-pane.js.map +1 -0
  136. package/dist/kiro-log-gate.d.ts +42 -0
  137. package/dist/kiro-log-gate.js +39 -0
  138. package/dist/kiro-log-gate.js.map +1 -0
  139. package/dist/launchd-handoff.d.ts +43 -0
  140. package/dist/launchd-handoff.js +56 -0
  141. package/dist/launchd-handoff.js.map +1 -0
  142. package/dist/locale.js +268 -42
  143. package/dist/locale.js.map +1 -1
  144. package/dist/log-service.d.ts +99 -0
  145. package/dist/log-service.js +243 -0
  146. package/dist/log-service.js.map +1 -0
  147. package/dist/log-steps.d.ts +169 -0
  148. package/dist/log-steps.js +858 -0
  149. package/dist/log-steps.js.map +1 -0
  150. package/dist/login-controller.d.ts +20 -3
  151. package/dist/login-controller.js +71 -19
  152. package/dist/login-controller.js.map +1 -1
  153. package/dist/login-flows.d.ts +5 -2
  154. package/dist/login-flows.js +5 -4
  155. package/dist/login-flows.js.map +1 -1
  156. package/dist/login-shell.d.ts +21 -0
  157. package/dist/login-shell.js +98 -0
  158. package/dist/login-shell.js.map +1 -0
  159. package/dist/memory-pressure.d.ts +13 -1
  160. package/dist/memory-pressure.js +50 -10
  161. package/dist/memory-pressure.js.map +1 -1
  162. package/dist/needs-you-hub.js +2 -0
  163. package/dist/needs-you-hub.js.map +1 -1
  164. package/dist/needs-you-live.d.ts +6 -0
  165. package/dist/needs-you-live.js +23 -3
  166. package/dist/needs-you-live.js.map +1 -1
  167. package/dist/outbound-handlers.d.ts +100 -0
  168. package/dist/outbound-handlers.js +277 -93
  169. package/dist/outbound-handlers.js.map +1 -1
  170. package/dist/outbound-schemas.d.ts +30 -15
  171. package/dist/outbound-schemas.js +23 -5
  172. package/dist/outbound-schemas.js.map +1 -1
  173. package/dist/provider-probe.d.ts +29 -0
  174. package/dist/provider-probe.js +198 -2
  175. package/dist/provider-probe.js.map +1 -1
  176. package/dist/public-link-progress.d.ts +1 -1
  177. package/dist/public-link-progress.js +8 -2
  178. package/dist/public-link-progress.js.map +1 -1
  179. package/dist/public-preview.d.ts +101 -0
  180. package/dist/public-preview.js +236 -0
  181. package/dist/public-preview.js.map +1 -0
  182. package/dist/public-web-gateway.d.ts +2 -0
  183. package/dist/public-web-gateway.js +20 -6
  184. package/dist/public-web-gateway.js.map +1 -1
  185. package/dist/public-web-link.d.ts +24 -1
  186. package/dist/public-web-link.js +63 -9
  187. package/dist/public-web-link.js.map +1 -1
  188. package/dist/quickstart-api.d.ts +1 -1
  189. package/dist/quickstart-api.js +8 -6
  190. package/dist/quickstart-api.js.map +1 -1
  191. package/dist/quickstart.d.ts +12 -0
  192. package/dist/quickstart.js +36 -6
  193. package/dist/quickstart.js.map +1 -1
  194. package/dist/reply-buttons-controller.d.ts +89 -5
  195. package/dist/reply-buttons-controller.js +273 -8
  196. package/dist/reply-buttons-controller.js.map +1 -1
  197. package/dist/reply-buttons.d.ts +48 -3
  198. package/dist/reply-buttons.js +105 -10
  199. package/dist/reply-buttons.js.map +1 -1
  200. package/dist/reply-polls.d.ts +85 -0
  201. package/dist/reply-polls.js +154 -0
  202. package/dist/reply-polls.js.map +1 -0
  203. package/dist/restart-guard.d.ts +6 -3
  204. package/dist/restart-guard.js +6 -3
  205. package/dist/restart-guard.js.map +1 -1
  206. package/dist/restart-progress.d.ts +33 -2
  207. package/dist/restart-progress.js +30 -2
  208. package/dist/restart-progress.js.map +1 -1
  209. package/dist/sd-notify.d.ts +22 -1
  210. package/dist/sd-notify.js +63 -10
  211. package/dist/sd-notify.js.map +1 -1
  212. package/dist/service-activation.d.ts +15 -7
  213. package/dist/service-activation.js +42 -26
  214. package/dist/service-activation.js.map +1 -1
  215. package/dist/service-installer.d.ts +55 -0
  216. package/dist/service-installer.js +90 -3
  217. package/dist/service-installer.js.map +1 -1
  218. package/dist/service-plan.d.ts +11 -0
  219. package/dist/service-plan.js +23 -0
  220. package/dist/service-plan.js.map +1 -1
  221. package/dist/settings-api.js +14 -4
  222. package/dist/settings-api.js.map +1 -1
  223. package/dist/settings-change.js +4 -2
  224. package/dist/settings-change.js.map +1 -1
  225. package/dist/settings-effect.js +2 -0
  226. package/dist/settings-effect.js.map +1 -1
  227. package/dist/spawn-gate.js +5 -1
  228. package/dist/spawn-gate.js.map +1 -1
  229. package/dist/spawn-hold-reply.d.ts +12 -0
  230. package/dist/spawn-hold-reply.js +39 -0
  231. package/dist/spawn-hold-reply.js.map +1 -0
  232. package/dist/systemd-peer-bus.d.ts +95 -0
  233. package/dist/systemd-peer-bus.js +430 -0
  234. package/dist/systemd-peer-bus.js.map +1 -0
  235. package/dist/systemd-runtime.d.ts +27 -1
  236. package/dist/systemd-runtime.js +69 -7
  237. package/dist/systemd-runtime.js.map +1 -1
  238. package/dist/tips.js +1 -1
  239. package/dist/tips.js.map +1 -1
  240. package/dist/tool-permissions.js +3 -3
  241. package/dist/tool-permissions.js.map +1 -1
  242. package/dist/tool-progress.js +3 -2
  243. package/dist/tool-progress.js.map +1 -1
  244. package/dist/topic-commands.d.ts +71 -4
  245. package/dist/topic-commands.js +167 -34
  246. package/dist/topic-commands.js.map +1 -1
  247. package/dist/transcript-delta-reader.d.ts +3 -3
  248. package/dist/transcript-delta-reader.js +8 -8
  249. package/dist/transcript-delta-reader.js.map +1 -1
  250. package/dist/transcript-jsonl.d.ts +85 -3
  251. package/dist/transcript-jsonl.js +156 -13
  252. package/dist/transcript-jsonl.js.map +1 -1
  253. package/dist/transcript-monitor.d.ts +11 -0
  254. package/dist/transcript-monitor.js +61 -13
  255. package/dist/transcript-monitor.js.map +1 -1
  256. package/dist/transcript-sources.d.ts +24 -0
  257. package/dist/transcript-sources.js +211 -56
  258. package/dist/transcript-sources.js.map +1 -1
  259. package/dist/tunnel/cloudflared.d.ts +7 -15
  260. package/dist/tunnel/cloudflared.js +36 -1
  261. package/dist/tunnel/cloudflared.js.map +1 -1
  262. package/dist/tunnel/purpose-lane.d.ts +5 -1
  263. package/dist/tunnel/purpose-lane.js +14 -7
  264. package/dist/tunnel/purpose-lane.js.map +1 -1
  265. package/dist/tunnel/types.d.ts +5 -1
  266. package/dist/tunnel/types.js.map +1 -1
  267. package/dist/turn-resume.d.ts +2 -0
  268. package/dist/turn-resume.js +1 -1
  269. package/dist/turn-resume.js.map +1 -1
  270. package/dist/types.d.ts +22 -0
  271. package/dist/ui/chat-commands.js +1 -0
  272. package/dist/ui/chat-mentions.js +63 -0
  273. package/dist/ui/chat-render.js +80 -10
  274. package/dist/ui/chat-store.js +70 -6
  275. package/dist/ui/chat-strings.js +62 -32
  276. package/dist/ui/chat-thread.js +199 -24
  277. package/dist/ui/chat-time.js +80 -0
  278. package/dist/ui/chat-tour.js +17 -14
  279. package/dist/ui/panel-chat.js +45 -21
  280. package/dist/ui/panel-details.js +2 -0
  281. package/dist/ui/panel-fleet.js +213 -58
  282. package/dist/ui/panel-log.js +428 -0
  283. package/dist/ui/panel-needs.js +14 -10
  284. package/dist/ui/panel-settings.js +82 -17
  285. package/dist/ui/preview-panel.js +55 -15
  286. package/dist/ui/preview-status.js +28 -0
  287. package/dist/ui/preview.js +44 -3
  288. package/dist/ui/settings-dialogs.js +11 -6
  289. package/dist/ui/settings-model.js +3 -1
  290. package/dist/ui/settings-strings.js +32 -12
  291. package/dist/ui/settings-token.js +8 -1
  292. package/dist/ui/settings-wizard.js +5 -3
  293. package/dist/ui/shared/app-i18n.js +41 -15
  294. package/dist/ui/shared/app-prefs.js +250 -0
  295. package/dist/ui/shared/app-route.js +6 -1
  296. package/dist/ui/shared/app-shell.js +165 -8
  297. package/dist/ui/shared/app-store.js +5 -1
  298. package/dist/ui/shared/app-stream.js +26 -5
  299. package/dist/ui/shared/app.css +170 -30
  300. package/dist/ui/shared/app.js +11 -1
  301. package/dist/ui/shared/fonts/README.md +1 -1
  302. package/dist/ui/shared/header-tools.js +83 -13
  303. package/dist/ui/shared/instance-nav.js +178 -51
  304. package/dist/ui/shared/instance-switch.js +8 -5
  305. package/dist/ui/shared/panel-view.js +21 -4
  306. package/dist/ui/shared/signin.js +15 -9
  307. package/dist/ui/shared/theme.js +39 -7
  308. package/dist/ui/shared/tokens.css +1 -1
  309. package/dist/ui/shared/ui-icons.js +2 -0
  310. package/dist/ui/shared/usage-dialog.js +23 -5
  311. package/dist/ui/shared/view-strings.js +40 -4
  312. package/dist/ui/signin.html +8 -3
  313. package/dist/unrecognised-prompt.d.ts +23 -0
  314. package/dist/unrecognised-prompt.js +74 -0
  315. package/dist/unrecognised-prompt.js.map +1 -0
  316. package/dist/update-check.d.ts +31 -0
  317. package/dist/update-check.js +47 -0
  318. package/dist/update-check.js.map +1 -1
  319. package/dist/update-install.d.ts +13 -1
  320. package/dist/update-install.js +16 -4
  321. package/dist/update-install.js.map +1 -1
  322. package/dist/update-launch.d.ts +48 -1
  323. package/dist/update-launch.js +159 -5
  324. package/dist/update-launch.js.map +1 -1
  325. package/dist/update-marker.d.ts +51 -0
  326. package/dist/update-marker.js +61 -0
  327. package/dist/update-marker.js.map +1 -1
  328. package/dist/usage/i18n-keys.d.ts +1 -1
  329. package/dist/usage/i18n-keys.js +1 -1
  330. package/dist/usage/i18n-keys.js.map +1 -1
  331. package/dist/usage/providers.d.ts +62 -13
  332. package/dist/usage/providers.js +256 -68
  333. package/dist/usage/providers.js.map +1 -1
  334. package/dist/usage/statusline-usage.d.ts +6 -2
  335. package/dist/usage/statusline-usage.js +10 -4
  336. package/dist/usage/statusline-usage.js.map +1 -1
  337. package/dist/view-api.js +31 -9
  338. package/dist/view-api.js.map +1 -1
  339. package/dist/web-api.d.ts +82 -4
  340. package/dist/web-api.js +632 -88
  341. package/dist/web-api.js.map +1 -1
  342. package/dist/web-auth.js +5 -2
  343. package/dist/web-auth.js.map +1 -1
  344. package/dist/web-chat-history.d.ts +26 -1
  345. package/dist/web-chat-history.js +47 -8
  346. package/dist/web-chat-history.js.map +1 -1
  347. package/dist/web-commands.d.ts +41 -5
  348. package/dist/web-commands.js +25 -7
  349. package/dist/web-commands.js.map +1 -1
  350. package/dist/web-login.d.ts +14 -0
  351. package/dist/web-login.js +4 -2
  352. package/dist/web-login.js.map +1 -1
  353. package/dist/web-mentions.d.ts +44 -0
  354. package/dist/web-mentions.js +75 -0
  355. package/dist/web-mentions.js.map +1 -0
  356. package/dist/web-prefs.d.ts +81 -0
  357. package/dist/web-prefs.js +297 -0
  358. package/dist/web-prefs.js.map +1 -0
  359. package/dist/web-preview.d.ts +14 -1
  360. package/dist/web-preview.js +35 -13
  361. package/dist/web-preview.js.map +1 -1
  362. package/dist/web-profile.d.ts +32 -0
  363. package/dist/web-profile.js +132 -0
  364. package/dist/web-profile.js.map +1 -0
  365. package/dist/web-reply-files.d.ts +45 -0
  366. package/dist/web-reply-files.js +223 -0
  367. package/dist/web-reply-files.js.map +1 -0
  368. package/dist/web-schedules.d.ts +12 -0
  369. package/dist/web-schedules.js +34 -0
  370. package/dist/web-schedules.js.map +1 -0
  371. package/dist/web-server-drain.d.ts +10 -0
  372. package/dist/web-server-drain.js +50 -0
  373. package/dist/web-server-drain.js.map +1 -0
  374. package/dist/web-session.d.ts +14 -0
  375. package/dist/web-session.js +11 -1
  376. package/dist/web-session.js.map +1 -1
  377. package/dist/web-settings-patch.d.ts +3 -1
  378. package/dist/web-settings-patch.js +4 -1
  379. package/dist/web-settings-patch.js.map +1 -1
  380. package/dist/web-shell-routes.d.ts +4 -0
  381. package/dist/web-shell-routes.js +9 -0
  382. package/dist/web-shell-routes.js.map +1 -1
  383. package/dist/web-upload.d.ts +135 -7
  384. package/dist/web-upload.js +315 -51
  385. package/dist/web-upload.js.map +1 -1
  386. package/launcher/install-admission.cjs +61 -6
  387. package/package.json +3 -3
  388. package/templates/launchd.plist.ejs +3 -1
  389. package/templates/systemd.service.ejs +2 -1
@@ -0,0 +1,170 @@
1
+ /**
2
+ * #1753: carry a Claude Code conversation across a credential-profile switch.
3
+ *
4
+ * Each profile is its own `CLAUDE_CONFIG_DIR`, and a conversation is a transcript under it:
5
+ * `<config>/projects/<cwd key>/<session id>.jsonl`, with its `<session id>/` folder (subagents, tool results) beside it.
6
+ * The new profile has no conversation for this directory, so a switch used to start fresh with a handover. Copying the
7
+ * session into the new profile and making it the newest there lets the relaunch's `--continue` pick it up — verified on
8
+ * a real account with claude 2.1.296 (#1753).
9
+ *
10
+ * Copy, never move: the old profile keeps its original untouched, so switching back still has it. Everything is written
11
+ * under a temporary name and renamed into place, so claude never sees a half-copied transcript. A failure says why, and
12
+ * the caller then starts fresh with a handover, as before. Up to the rename into place the target is left as it was;
13
+ * the one step after it (dating the copy as the newest) can fail with the copy already there — or, switching back,
14
+ * already in place of the older one — which the fresh start leaves unused.
15
+ */
16
+ import { constants as fsConstants } from "node:fs";
17
+ import { copyFile, cp, mkdir, open, readdir, rename, rm, stat, statfs, utimes } from "node:fs/promises";
18
+ import { join } from "node:path";
19
+ import { claudeProjectKey } from "./claude-code.js";
20
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
21
+ /** Room kept free beyond the copy itself. */
22
+ const SPACE_MARGIN = 64 * 1024 * 1024;
23
+ /** How much of an existing copy is compared, at its start and at its end, to tell an older copy from another history. */
24
+ const PREFIX_PROBE = 64 * 1024;
25
+ async function defaultFreeBytes(dir) {
26
+ const s = await statfs(dir);
27
+ return Number(s.bavail) * Number(s.bsize);
28
+ }
29
+ async function sizeOf(path) {
30
+ try {
31
+ return (await stat(path)).size;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ async function treeBytes(dir) {
38
+ let total = 0;
39
+ let entries;
40
+ try {
41
+ entries = await readdir(dir, { withFileTypes: true });
42
+ }
43
+ catch {
44
+ return 0;
45
+ }
46
+ for (const e of entries) {
47
+ const p = join(dir, e.name);
48
+ if (e.isDirectory())
49
+ total += await treeBytes(p);
50
+ else if (e.isFile())
51
+ total += (await sizeOf(p)) ?? 0;
52
+ }
53
+ return total;
54
+ }
55
+ async function readAt(path, offset, length) {
56
+ const fh = await open(path, "r");
57
+ try {
58
+ const buf = Buffer.alloc(length);
59
+ const { bytesRead } = await fh.read(buf, 0, length, offset);
60
+ return buf.subarray(0, bytesRead);
61
+ }
62
+ finally {
63
+ await fh.close();
64
+ }
65
+ }
66
+ /**
67
+ * Is `target` an older copy of `source` — the same conversation, cut shorter? (Switching back: A → B → A finds A's
68
+ * original, which B's copy has since grown past.) Its start and its end must match the source at the same offsets.
69
+ */
70
+ async function isOlderCopy(target, targetSize, source, sourceSize) {
71
+ if (targetSize > sourceSize)
72
+ return false;
73
+ const head = Math.min(PREFIX_PROBE, targetSize);
74
+ const tailAt = Math.max(0, targetSize - PREFIX_PROBE);
75
+ const [th, sh, tt, st] = await Promise.all([
76
+ readAt(target, 0, head), readAt(source, 0, head),
77
+ readAt(target, tailAt, targetSize - tailAt), readAt(source, tailAt, targetSize - tailAt),
78
+ ]);
79
+ return th.equals(sh) && tt.equals(st);
80
+ }
81
+ /** The newest mtime (ms) among the other transcripts in the target's project folder. */
82
+ async function newestOtherTranscript(dir, except) {
83
+ let newest = 0;
84
+ try {
85
+ for (const e of await readdir(dir, { withFileTypes: true })) {
86
+ if (!e.isFile() || !e.name.endsWith(".jsonl") || e.name === except)
87
+ continue;
88
+ const m = (await stat(join(dir, e.name))).mtimeMs;
89
+ if (m > newest)
90
+ newest = m;
91
+ }
92
+ }
93
+ catch { /* no folder yet */ }
94
+ return newest;
95
+ }
96
+ export async function carryClaudeConversation(req) {
97
+ const started = performance.now();
98
+ const sid = (req.sessionId ?? "").trim();
99
+ if (!SESSION_ID.test(sid))
100
+ return { carried: false, reason: "no session id to carry" };
101
+ if (!req.workingDirectory.trim())
102
+ return { carried: false, reason: "no working directory" };
103
+ if (req.fromConfigDir === req.toConfigDir)
104
+ return { carried: false, reason: "same config dir" };
105
+ const key = claudeProjectKey(req.workingDirectory);
106
+ const fromDir = join(req.fromConfigDir, "projects", key);
107
+ const toDir = join(req.toConfigDir, "projects", key);
108
+ const name = `${sid}.jsonl`;
109
+ const source = join(fromDir, name), target = join(toDir, name);
110
+ const sourceSize = await sizeOf(source);
111
+ if (sourceSize === null)
112
+ return { carried: false, reason: "the session's transcript is not in the old profile" };
113
+ const targetSize = await sizeOf(target);
114
+ let replacedOlderCopy = false;
115
+ if (targetSize !== null) {
116
+ if (!await isOlderCopy(target, targetSize, source, sourceSize).catch(() => false)) {
117
+ return { carried: false, reason: "the new profile already holds a different version of this conversation" };
118
+ }
119
+ replacedOlderCopy = true;
120
+ }
121
+ const folderBytes = await treeBytes(join(fromDir, sid));
122
+ const needed = sourceSize + folderBytes + SPACE_MARGIN;
123
+ try {
124
+ await mkdir(toDir, { recursive: true });
125
+ const free = await (req.freeBytes ?? defaultFreeBytes)(toDir);
126
+ if (free < needed)
127
+ return { carried: false, reason: `not enough free space (${needed} bytes needed, ${free} free)` };
128
+ }
129
+ catch (err) {
130
+ return { carried: false, reason: `the new profile's folder is not usable: ${err.message}` };
131
+ }
132
+ const tag = `.carry-${process.pid}-${Math.floor(started)}`;
133
+ const tmpFile = join(toDir, `.${name}${tag}`);
134
+ const tmpFolder = join(toDir, `.${sid}${tag}`);
135
+ try {
136
+ // A copy-on-write clone where the filesystem has one (btrfs, xfs, APFS), a plain copy elsewhere.
137
+ await copyFile(source, tmpFile, fsConstants.COPYFILE_FICLONE);
138
+ // The session's folder first, so the transcript never appears before the files it refers to.
139
+ if (await sizeOf(join(fromDir, sid)) !== null) {
140
+ await cp(join(fromDir, sid), tmpFolder, { recursive: true, errorOnExist: false, force: true });
141
+ if (await sizeOf(join(toDir, sid)) === null)
142
+ await rename(tmpFolder, join(toDir, sid));
143
+ else {
144
+ // Switching back: the folder is there already. It takes the copy's files; its own extra files stay.
145
+ await cp(tmpFolder, join(toDir, sid), { recursive: true, errorOnExist: false, force: true });
146
+ await rm(tmpFolder, { recursive: true, force: true });
147
+ }
148
+ }
149
+ await rename(tmpFile, target);
150
+ }
151
+ catch (err) {
152
+ await rm(tmpFile, { force: true }).catch(() => undefined);
153
+ await rm(tmpFolder, { recursive: true, force: true }).catch(() => undefined);
154
+ return { carried: false, reason: `copy failed: ${err.message}` };
155
+ }
156
+ // Newest in its folder, so `--continue` picks it — even past another transcript dated ahead of this clock.
157
+ const now = (req.now ?? Date.now)();
158
+ const mtime = Math.max(now, (await newestOtherTranscript(toDir, name)) + 1_000);
159
+ try {
160
+ await utimes(target, mtime / 1000, mtime / 1000);
161
+ }
162
+ catch (err) {
163
+ return { carried: false, reason: `could not date the copy as the newest: ${err.message}` };
164
+ }
165
+ // `/rewind` reaches edits from before the switch: the session's file history, merged, never overwriting. Best effort.
166
+ await cp(join(req.fromConfigDir, "file-history", sid), join(req.toConfigDir, "file-history", sid), { recursive: true, errorOnExist: false, force: false })
167
+ .catch(() => undefined);
168
+ return { carried: true, sessionId: sid, bytes: sourceSize + folderBytes, ms: Math.round(performance.now() - started), replacedOlderCopy };
169
+ }
170
+ //# sourceMappingURL=claude-carry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-carry.js","sourceRoot":"","sources":["../../src/backend/claude-carry.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,39 @@
1
1
  import { type CliBackend, type CliBackendConfig, type ErrorPattern, type InputBox, type RuntimeDialog, type StartupDialog } from "./types.js";
2
2
  /** Mirror Claude Code's ~/.claude/projects key for a working directory. Exported for the detection-signal seam. */
3
3
  export declare function claudeProjectKey(cwd: string): string;
4
+ /**
5
+ * #1754: the directory Claude Code keys a project's auto-memory by — the repository's own root, which for a git
6
+ * worktree is the main checkout's (its memory sits under `projects/<that root's key>/memory/`, shared by every worktree),
7
+ * and for a directory outside any repository the directory itself. Read from the files git leaves (`.git` as a folder,
8
+ * or a worktree's `.git` file → its `commondir`), never by running git: this runs at an instance's launch.
9
+ */
10
+ export declare function claudeMemoryRoot(cwd: string): string;
11
+ /**
12
+ * #1754: a named profile is its own `projects/`, so Claude Code's auto-memory for this project would start empty there
13
+ * and stay apart from every other instance's. Memory is knowledge, not an account — shared like the instructions,
14
+ * agents and skills are (#1699 (a)): `<profile>/projects/<repo key>/memory` becomes a link to the shared one
15
+ * (`<shared config>/projects/<repo key>/memory`). Transcripts beside it stay the profile's own.
16
+ * Never replaces anything real: a non-empty memory folder in the profile is left as it is (and said so); an empty one
17
+ * — what claude makes before it has written anything — gives way to the link. Returns what it found or did.
18
+ */
19
+ export declare function linkSharedClaudeMemory(profileDir: string, workingDirectory: string, sharedConfigDir?: string): "linked" | "already-linked" | "kept-own" | "kept-other-link" | "failed";
20
+ /**
21
+ * #1699: the config dir of a NAMED credential profile — the one string every writer and reader of that profile uses
22
+ * (claude names its macOS Keychain item after it). Null for no profile.
23
+ */
24
+ export declare function claudeProfileConfigDir(backendOptions: Record<string, unknown> | undefined): string | null;
25
+ /**
26
+ * Claude Code's config dir as an instance's CLI sees it: its profile's (#1699), else exactly as before —
27
+ * `CLAUDE_CONFIG_DIR` from the fleet's own environment, or `~/.claude`.
28
+ */
29
+ export declare function claudeConfigDirFor(profileDir: string | null | undefined): string;
30
+ /**
31
+ * #1699: the macOS Keychain item holding the login of claude run with `CLAUDE_CONFIG_DIR=<configDir>` — claude
32
+ * 2.1.296's own naming: the exact string it was given, NFC-normalised, hashed (sha256, first 8 hex digits). So the dir
33
+ * must be spelled exactly as at launch (credentialProfileHome: absolute, no trailing slash). Only a named profile has
34
+ * one: the shared login, with the variable unset, is the unsuffixed "Claude Code-credentials".
35
+ */
36
+ export declare function claudeKeychainService(configDir: string): string;
4
37
  /**
5
38
  * Take `lockPath` via exclusive create, recording {pid, token} so a stale lock
6
39
  * can be attributed. Returns the token to release with, or null when the lock
@@ -71,6 +104,33 @@ export declare function claudeColdResumePromptState(pane: string): {
71
104
  * is why the box is found by its frame and never by the glyph. Anything else — no frame near the bottom, a first row
72
105
  * that is not the prompt, a row that is not indented — is not a box this reader can vouch for: null.
73
106
  */
107
+ /**
108
+ * #1690: claude 2.1.296's cache-warning confirmation for a runtime `/model` (and, by its binary strings, `/effort`):
109
+ *
110
+ * Switch model? (or: Change effort level?)
111
+ * Your next response will be slower and use more tokens
112
+ * This conversation is cached for the current model. Switching to Opus 5.5 means …
113
+ * ❯ 1. Yes, switch to Opus 5.5
114
+ * 2. No, go back
115
+ *
116
+ * Shown only with a warm cache for another model than the last turn's. The default cursor is Yes; Escape is a clean
117
+ * "No" (`Kept model as …`); a digit selects and confirms. `target` is what claude will switch to: a display name for a
118
+ * name it knows (`opus` → "Opus 5.5"), the raw input for one it does not (it validates nothing). Bottom-anchored: the
119
+ * options are the last rows apart from a short footer, so a transcript quoting the dialog is not it.
120
+ */
121
+ export interface ClaudeSwitchDialog {
122
+ kind: "model" | "effort";
123
+ target: string;
124
+ cursor: 1 | 2 | null;
125
+ }
126
+ export declare function claudeSwitchDialog(pane: string): ClaudeSwitchDialog | null;
127
+ /**
128
+ * #1690: does the model claude is about to switch to name the one AgEnD asked for? Display-name equality: case, a
129
+ * `claude-` prefix, separators and the 1M marker do not count (`claude-sonnet-5-5` ≡ "Sonnet 5.5"). A bare family alias
130
+ * matches its family's version (`opus` ≡ "Opus 5.5"); a dated id matches without its date. `default` / `opusplan` resolve to names AgEnD cannot predict:
131
+ * any name claude resolved (not the raw input echoed back) is theirs. Anything else is a mismatch.
132
+ */
133
+ export declare function claudeSwitchTargetMatches(requested: string, shown: string): boolean;
74
134
  export declare function readClaudeInputBox(pane: string): InputBox | null;
75
135
  export interface ClaudeChoiceDialogState {
76
136
  /** The dialog is the live, bottom-most interactive region. */
@@ -157,6 +217,9 @@ export declare class ClaudeCodeBackend implements CliBackend {
157
217
  }): number | undefined;
158
218
  private binaryPath;
159
219
  constructor(instanceDir: string);
220
+ /** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
221
+ private quietLaunchNotes;
222
+ consumeQuietLaunchNotes(): string[];
160
223
  buildCommand(config: CliBackendConfig): string;
161
224
  private hasProjectSession;
162
225
  writeConfig(config: CliBackendConfig): void;
@@ -282,10 +345,10 @@ export declare class ClaudeCodeBackend implements CliBackend {
282
345
  * signal is projects[<cwd>].hasTrustDialogAccepted in claude.json. Also marks
283
346
  * onboarding complete for an already-authenticated account (see below).
284
347
  */
285
- preTrust(workingDirectory: string): void;
348
+ preTrust(workingDirectory: string, config?: CliBackendConfig): void;
286
349
  /** Pre-approve ANTHROPIC_API_KEY in claude.json to skip the interactive prompt */
287
350
  private preApproveApiKey;
288
- /** Check if user has an active OAuth session in claude.json */
351
+ /** Check if user has an active OAuth session in claude.json (#1699: the instance's own config dir). */
289
352
  private hasOAuthSession;
290
353
  private writeStatusLineScript;
291
354
  }
@@ -1,7 +1,11 @@
1
1
  import { EFFORT_CAPABILITIES } from "./effort-metadata.js";
2
- import { dirname, join, resolve } from "node:path";
3
- import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { flagEffort } from "./launch-effort.js";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
4
5
  import { homedir } from "node:os";
6
+ import { createHash } from "node:crypto";
7
+ import { credentialHomeSpec, credentialProfileHome, prepareCredentialProfileHome, resolveCredentialProfile } from "./credential-profile.js";
8
+ import { getAgendHome } from "../paths.js";
5
9
  import { CLI_PROBE_LONGEST_LEAF_MS, resolveBinary, shellQuote, validateModel, warnIfModelMismatch } from "./types.js";
6
10
  /** Mirror Claude Code's ~/.claude/projects key for a working directory. Exported for the detection-signal seam. */
7
11
  export function claudeProjectKey(cwd) {
@@ -24,9 +28,127 @@ export function claudeProjectKey(cwd) {
24
28
  }
25
29
  return `${sanitized.slice(0, 200)}-${Math.abs(hash).toString(36)}`;
26
30
  }
27
- /** Claude Code's top-level config file, honoring CLAUDE_CONFIG_DIR. */
28
- function claudeJsonPath() {
29
- const configDir = process.env.CLAUDE_CONFIG_DIR?.trim();
31
+ /**
32
+ * #1754: the directory Claude Code keys a project's auto-memory by — the repository's own root, which for a git
33
+ * worktree is the main checkout's (its memory sits under `projects/<that root's key>/memory/`, shared by every worktree),
34
+ * and for a directory outside any repository the directory itself. Read from the files git leaves (`.git` as a folder,
35
+ * or a worktree's `.git` file → its `commondir`), never by running git: this runs at an instance's launch.
36
+ */
37
+ export function claudeMemoryRoot(cwd) {
38
+ let dir = resolve(cwd);
39
+ try {
40
+ dir = realpathSync(dir);
41
+ }
42
+ catch { /* as given */ }
43
+ for (let at = dir;; at = dirname(at)) {
44
+ const dotGit = join(at, ".git");
45
+ let st;
46
+ try {
47
+ st = statSync(dotGit);
48
+ }
49
+ catch {
50
+ st = null;
51
+ }
52
+ if (st?.isDirectory())
53
+ return at;
54
+ if (st?.isFile()) {
55
+ try {
56
+ const gitdir = /^gitdir:\s*(.+)$/m.exec(readFileSync(dotGit, "utf-8"))?.[1]?.trim();
57
+ if (gitdir) {
58
+ const worktreeGitDir = resolve(at, gitdir);
59
+ const common = resolve(worktreeGitDir, readFileSync(join(worktreeGitDir, "commondir"), "utf-8").trim());
60
+ if (basename(common) === ".git")
61
+ return dirname(common);
62
+ }
63
+ }
64
+ catch { /* a submodule or an unusual layout: this directory is its own root */ }
65
+ return at;
66
+ }
67
+ if (dirname(at) === at)
68
+ return dir;
69
+ }
70
+ }
71
+ /**
72
+ * #1754: a named profile is its own `projects/`, so Claude Code's auto-memory for this project would start empty there
73
+ * and stay apart from every other instance's. Memory is knowledge, not an account — shared like the instructions,
74
+ * agents and skills are (#1699 (a)): `<profile>/projects/<repo key>/memory` becomes a link to the shared one
75
+ * (`<shared config>/projects/<repo key>/memory`). Transcripts beside it stay the profile's own.
76
+ * Never replaces anything real: a non-empty memory folder in the profile is left as it is (and said so); an empty one
77
+ * — what claude makes before it has written anything — gives way to the link. Returns what it found or did.
78
+ */
79
+ export function linkSharedClaudeMemory(profileDir, workingDirectory, sharedConfigDir = claudeConfigDirFor(null)) {
80
+ const key = claudeProjectKey(claudeMemoryRoot(workingDirectory));
81
+ const shared = join(sharedConfigDir, "projects", key, "memory");
82
+ const target = join(profileDir, "projects", key, "memory");
83
+ try {
84
+ let existing;
85
+ try {
86
+ existing = lstatSync(target);
87
+ }
88
+ catch {
89
+ existing = null;
90
+ }
91
+ if (existing?.isSymbolicLink()) {
92
+ if (resolve(dirname(target), readlinkSync(target)) !== resolve(shared))
93
+ return "kept-other-link";
94
+ // Ours — but it may point at nothing yet (linked before the shared side was made, or that side since removed):
95
+ // the shared folder must be there for a note to land. A file in its place throws (EEXIST): the failure path below.
96
+ mkdirSync(shared, { recursive: true });
97
+ return "already-linked";
98
+ }
99
+ if (existing?.isDirectory()) {
100
+ if (readdirSync(target).length > 0) {
101
+ console.warn(`[agend] a credential profile has its own Claude memory, so it is not linked to the shared one: ${target}`);
102
+ return "kept-own";
103
+ }
104
+ rmdirSync(target);
105
+ }
106
+ else if (existing) {
107
+ return "kept-own";
108
+ }
109
+ mkdirSync(shared, { recursive: true });
110
+ mkdirSync(dirname(target), { recursive: true });
111
+ symlinkSync(shared, target, "dir");
112
+ return "linked";
113
+ }
114
+ catch (err) {
115
+ console.warn(`[agend] could not link a credential profile's Claude memory to the shared one (${err.message}): ${target}`);
116
+ return "failed";
117
+ }
118
+ }
119
+ /**
120
+ * #1699: the config dir of a NAMED credential profile — the one string every writer and reader of that profile uses
121
+ * (claude names its macOS Keychain item after it). Null for no profile.
122
+ */
123
+ export function claudeProfileConfigDir(backendOptions) {
124
+ let profile;
125
+ try {
126
+ profile = resolveCredentialProfile(backendOptions);
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ return profile ? credentialProfileHome(getAgendHome(), "claude-code", profile) : null;
132
+ }
133
+ /**
134
+ * Claude Code's config dir as an instance's CLI sees it: its profile's (#1699), else exactly as before —
135
+ * `CLAUDE_CONFIG_DIR` from the fleet's own environment, or `~/.claude`.
136
+ */
137
+ export function claudeConfigDirFor(profileDir) {
138
+ return profileDir || process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
139
+ }
140
+ /**
141
+ * #1699: the macOS Keychain item holding the login of claude run with `CLAUDE_CONFIG_DIR=<configDir>` — claude
142
+ * 2.1.296's own naming: the exact string it was given, NFC-normalised, hashed (sha256, first 8 hex digits). So the dir
143
+ * must be spelled exactly as at launch (credentialProfileHome: absolute, no trailing slash). Only a named profile has
144
+ * one: the shared login, with the variable unset, is the unsuffixed "Claude Code-credentials".
145
+ */
146
+ export function claudeKeychainService(configDir) {
147
+ return `Claude Code-credentials-${createHash("sha256").update(configDir.normalize("NFC")).digest("hex").slice(0, 8)}`;
148
+ }
149
+ /** Claude Code's top-level config file: `<profile dir>/.claude.json` for a profile, else honoring CLAUDE_CONFIG_DIR. */
150
+ function claudeJsonPath(profileDir) {
151
+ const configDir = profileDir || process.env.CLAUDE_CONFIG_DIR?.trim();
30
152
  return configDir ? join(configDir, ".claude.json") : join(homedir(), ".claude.json");
31
153
  }
32
154
  function sleepSync(ms) {
@@ -99,8 +221,8 @@ export function releaseClaudeJsonLock(lockPath, token) {
99
221
  * unparseable file is likewise left untouched — never clobber the user's whole
100
222
  * config over a parse error. Both give up to the getStartupDialogs() fallback.
101
223
  */
102
- function updateClaudeJson(mutate) {
103
- const path = claudeJsonPath();
224
+ function updateClaudeJson(mutate, profileDir) {
225
+ const path = claudeJsonPath(profileDir);
104
226
  // The parent must exist before both the lock create and the rename — on a
105
227
  // fresh machine (no ~/.claude.json yet) an ENOENT here previously made every
106
228
  // lock attempt fail, silently degrading to unlocked last-write-wins.
@@ -308,23 +430,61 @@ const CLAUDE_STATUS_TIP_ROW = /^ {2}⎿/;
308
430
  const CLAUDE_STATUS_SPINNER_ROW = /^[^\s❯●⎿]/;
309
431
  /** A paste Claude shows collapsed in the box; the pasted text itself is not on screen. */
310
432
  const CLAUDE_COLLAPSED_PASTE = /\[Pasted text #\d+(?: \+\d+ lines?)?\]/g;
433
+ const SWITCH_TITLE = /^[ \t]*(Switch model\?|Change effort level\?)[ \t]*$/;
434
+ const SWITCH_YES = /^[ \t]*(❯)?[ \t]*1\.[ \t]+Yes, switch to (.+?)[ \t]*$/;
435
+ const SWITCH_NO = /^[ \t]*(❯)?[ \t]*2\.[ \t]+No, go back[ \t]*$/;
436
+ export function claudeSwitchDialog(pane) {
437
+ const rows = pane.replace(/\r/g, "").split("\n").map(r => r.trimEnd());
438
+ let title = -1;
439
+ for (let i = rows.length - 1; i >= 0; i--)
440
+ if (SWITCH_TITLE.test(rows[i])) {
441
+ title = i;
442
+ break;
443
+ }
444
+ if (title < 0)
445
+ return null;
446
+ let yes = -1, no = -1;
447
+ for (let i = title + 1; i < rows.length && i <= title + 12; i++) {
448
+ if (yes < 0 && SWITCH_YES.test(rows[i]))
449
+ yes = i;
450
+ else if (yes >= 0 && SWITCH_NO.test(rows[i])) {
451
+ no = i;
452
+ break;
453
+ }
454
+ }
455
+ if (yes < 0 || no !== yes + 1)
456
+ return null;
457
+ if (rows.slice(no + 1).filter(r => r.trim() !== "").length > 3)
458
+ return null;
459
+ const y = SWITCH_YES.exec(rows[yes]), n = SWITCH_NO.exec(rows[no]);
460
+ const cursor = y[1] && !n[1] ? 1 : n[1] && !y[1] ? 2 : null;
461
+ return { kind: SWITCH_TITLE.exec(rows[title])[1].startsWith("Switch") ? "model" : "effort", target: y[2], cursor };
462
+ }
311
463
  /**
312
- * Claude Code's live input box, read as a region (#1200). Captured live from 2.1.293 under the production launch
313
- * (tests/fixtures/claude-2.1.293-*.pane.txt):
314
- *
315
- * ❯ draft one two three ← typed text: `❯` + U+00A0, then the text
316
- * ────────────────────────────────── ← the box's upper rule
317
- * ❯ [Pasted text #1 +11 lines] ← a long paste stays collapsed until it is submitted
318
- * ────────────────────────────────── ← lower rule
319
- * ok ← statusLine
320
- * ⏵⏵ bypass permissions on (shift+tab to cycle)
321
- *
322
- * Bottom-anchored: the lower rule is the last `─` row, with at most CLAUDE_BOX_FOOTER_MAX_ROWS non-blank rows under it;
323
- * the upper rule is the previous `─` row. The first row inside starts with `❯` (an empty box is a bare `❯`), further
324
- * rows are indented two columns. Transcript echoes above the box also start with `❯` (with an ordinary space), which
325
- * is why the box is found by its frame and never by the glyph. Anything else — no frame near the bottom, a first row
326
- * that is not the prompt, a row that is not indented — is not a box this reader can vouch for: null.
464
+ * #1690: does the model claude is about to switch to name the one AgEnD asked for? Display-name equality: case, a
465
+ * `claude-` prefix, separators and the 1M marker do not count (`claude-sonnet-5-5` ≡ "Sonnet 5.5"). A bare family alias
466
+ * matches its family's version (`opus` ≡ "Opus 5.5"); a dated id matches without its date. `default` / `opusplan` resolve to names AgEnD cannot predict:
467
+ * any name claude resolved (not the raw input echoed back) is theirs. Anything else is a mismatch.
327
468
  */
469
+ export function claudeSwitchTargetMatches(requested, shown) {
470
+ // A dated catalog id names the same model as its display name: claude-sonnet-4-5-20250929 ≡ "Sonnet 4.5" (live, 2.1.296).
471
+ const norm = (s) => s.toLowerCase().replace(/\(1m context\)|1m context|\[1m\]/g, " 1m").replace(/^claude[\s-]+/, "")
472
+ .replace(/[-_]\d{8}(?=$|\s)/g, "").replace(/[\s._-]+/g, "");
473
+ const want = norm(requested), got = norm(shown);
474
+ if (!want || !got)
475
+ return false;
476
+ if (want === "default" || want === "opusplan")
477
+ return shown.trim().toLowerCase() !== requested.trim().toLowerCase();
478
+ if (want === got)
479
+ return true;
480
+ const family = /^(opus|sonnet|haiku|fable)(1m)?$/.exec(want);
481
+ if (family) {
482
+ const m = /^(opus|sonnet|haiku|fable)(\d+)(1m)?$/.exec(got);
483
+ return !!m && m[1] === family[1] && (m[3] ?? "") === (family[2] ?? "");
484
+ }
485
+ return false;
486
+ }
487
+ const switchDialogActive = (pane) => claudeSwitchDialog(pane) !== null;
328
488
  export function readClaudeInputBox(pane) {
329
489
  const rows = claudeRows(pane).map(row => row.trimEnd());
330
490
  let bottom = -1;
@@ -804,13 +964,27 @@ export class ClaudeCodeBackend {
804
964
  this.instanceDir = instanceDir;
805
965
  this.binaryPath = resolveBinary("claude");
806
966
  }
967
+ /** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
968
+ quietLaunchNotes = [];
969
+ consumeQuietLaunchNotes() { const notes = this.quietLaunchNotes; this.quietLaunchNotes = []; return notes; }
807
970
  buildCommand(config) {
808
971
  const settingsPath = join(this.instanceDir, "claude-settings.json");
809
972
  const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
810
973
  const envPrefix = ["CMUX_CLAUDE_HOOKS_DISABLED=1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"];
974
+ // #1699: a named credential profile is its own config dir — its login, account, trust and conversations. No profile:
975
+ // nothing added, nothing created (the command is exactly what it was).
976
+ const profileDir = claudeProfileConfigDir(config.backendOptions);
977
+ if (profileDir) {
978
+ prepareCredentialProfileHome(credentialHomeSpec("claude-code"), profileDir);
979
+ // #1754: the project's auto-memory is the shared one, as on the default login.
980
+ linkSharedClaudeMemory(profileDir, config.workingDirectory, credentialHomeSpec("claude-code").sharedRoot());
981
+ envPrefix.push(`CLAUDE_CONFIG_DIR=${shellQuote(profileDir)}`);
982
+ }
811
983
  if (process.env.ANTHROPIC_BASE_URL)
812
984
  envPrefix.push(`ANTHROPIC_BASE_URL=${shellQuote(process.env.ANTHROPIC_BASE_URL)}`);
813
- if (process.env.ANTHROPIC_API_KEY && !this.hasOAuthSession()) {
985
+ // The fleet's API key only where this instance's own config dir has no OAuth login: a key in the environment wins
986
+ // over the login, so reading another dir here bills the wrong account (#1699).
987
+ if (process.env.ANTHROPIC_API_KEY && !this.hasOAuthSession(profileDir)) {
814
988
  envPrefix.push(`ANTHROPIC_API_KEY=${shellQuote(process.env.ANTHROPIC_API_KEY)}`);
815
989
  }
816
990
  let cmd = `${envPrefix.join(" ")} ${this.binaryPath} --settings ${settingsPath} --mcp-config ${mcpConfigPath}`;
@@ -824,7 +998,7 @@ export class ClaudeCodeBackend {
824
998
  // own project transcript as the source of truth instead of the generic
825
999
  // marker alone. This also makes an empty/missing Classic workspace start
826
1000
  // fresh rather than entering a resume crash loop.
827
- if (!config.skipResume && existsSync(sessionIdFile) && this.hasProjectSession(config.workingDirectory)) {
1001
+ if (!config.skipResume && existsSync(sessionIdFile) && this.hasProjectSession(config.workingDirectory, profileDir)) {
828
1002
  cmd += " --continue";
829
1003
  }
830
1004
  if (config.model) {
@@ -832,6 +1006,11 @@ export class ClaudeCodeBackend {
832
1006
  warnIfModelMismatch("claude-code", model);
833
1007
  cmd += ` --model ${shellQuote(model)}`;
834
1008
  }
1009
+ // #1811: the configured effort, where this claude takes it (launch-effort.ts); else a quiet note.
1010
+ const effort = flagEffort("claude-code", config.effort, this.binaryPath);
1011
+ if (effort.arg)
1012
+ cmd += effort.arg;
1013
+ this.quietLaunchNotes = effort.note ? [effort.note] : [];
835
1014
  // Additive system prompt: append fleet instructions without overriding Claude's built-in prompt
836
1015
  const instrFile = join(this.instanceDir, "fleet-instructions.md");
837
1016
  if (existsSync(instrFile)) {
@@ -839,11 +1018,11 @@ export class ClaudeCodeBackend {
839
1018
  }
840
1019
  return cmd;
841
1020
  }
842
- hasProjectSession(workingDirectory) {
1021
+ hasProjectSession(workingDirectory, profileDir) {
843
1022
  const cwd = workingDirectory.trim();
844
1023
  if (!cwd)
845
1024
  return false;
846
- const configDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
1025
+ const configDir = claudeConfigDirFor(profileDir);
847
1026
  const encodedCwd = claudeProjectKey(cwd);
848
1027
  try {
849
1028
  return readdirSync(join(configDir, "projects", encodedCwd), { withFileTypes: true })
@@ -1276,6 +1455,17 @@ export class ClaudeCodeBackend {
1276
1455
  { pattern: /Choose the text style that looks best with your terminal/, isActive: claudeThemePickerActive, keys: ["Enter"], description: "Claude onboarding theme picker — accept the highlighted theme", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-theme" },
1277
1456
  { pattern: /Press Enter to continue/, isActive: claudeSecurityNotesActive, keys: ["Enter"], description: "Claude onboarding security notes — continue", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-security" },
1278
1457
  { pattern: /Use Claude Code's terminal setup\?/, isActive: claudeTerminalSetupActive, keys: ["Escape"], description: "Claude onboarding terminal-setup offer — skip", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-terminal-setup" },
1458
+ // #1690: the model/effort switch confirmation. Held for every delivery while it is up (its default cursor is Yes:
1459
+ // a stray Enter would confirm it) and never answered by the scanner — AgEnD's own /model answers it on a verified
1460
+ // cursor (Daemon.answerOwnSwitchDialog); one AgEnD did not ask for is parked for a human.
1461
+ {
1462
+ pattern: /Switch model\?|Change effort level\?/,
1463
+ isActive: switchDialogActive,
1464
+ keys: [],
1465
+ holdOnly: true,
1466
+ blocksDelivery: true,
1467
+ description: "Claude model/effort switch confirmation (\u201cSwitch model?\u201d) — not asked for by AgEnD, or its answer did not take",
1468
+ },
1279
1469
  ];
1280
1470
  }
1281
1471
  getQuitCommand() { return "/exit"; }
@@ -1369,10 +1559,12 @@ export class ClaudeCodeBackend {
1369
1559
  * signal is projects[<cwd>].hasTrustDialogAccepted in claude.json. Also marks
1370
1560
  * onboarding complete for an already-authenticated account (see below).
1371
1561
  */
1372
- preTrust(workingDirectory) {
1562
+ preTrust(workingDirectory, config) {
1373
1563
  const cwd = workingDirectory?.trim();
1374
1564
  if (!cwd)
1375
1565
  return;
1566
+ // #1699: trust and onboarding are per config dir — a profile's own `.claude.json`, never the shared one.
1567
+ const profileDir = claudeProfileConfigDir(config?.backendOptions);
1376
1568
  const literal = resolve(cwd);
1377
1569
  let canonical = literal;
1378
1570
  try {
@@ -1416,15 +1608,16 @@ export class ClaudeCodeBackend {
1416
1608
  changed = true;
1417
1609
  }
1418
1610
  return changed;
1419
- });
1611
+ }, profileDir);
1420
1612
  }
1421
1613
  catch { /* best effort — startup dialog auto-dismiss remains the fallback */ }
1422
1614
  }
1423
1615
  /** Pre-approve ANTHROPIC_API_KEY in claude.json to skip the interactive prompt */
1424
- preApproveApiKey(_config) {
1616
+ preApproveApiKey(config) {
1425
1617
  const apiKey = process.env.ANTHROPIC_API_KEY;
1426
1618
  if (!apiKey)
1427
1619
  return;
1620
+ const profileDir = claudeProfileConfigDir(config.backendOptions);
1428
1621
  const fingerprint = apiKey.length > 20 ? apiKey.slice(-20) : apiKey;
1429
1622
  try {
1430
1623
  updateClaudeJson(cfg => {
@@ -1437,14 +1630,14 @@ export class ClaudeCodeBackend {
1437
1630
  rejected: existing?.rejected ?? [],
1438
1631
  };
1439
1632
  return true;
1440
- });
1633
+ }, profileDir);
1441
1634
  }
1442
1635
  catch { /* best effort — the startup prompt remains the fallback */ }
1443
1636
  }
1444
- /** Check if user has an active OAuth session in claude.json */
1445
- hasOAuthSession() {
1637
+ /** Check if user has an active OAuth session in claude.json (#1699: the instance's own config dir). */
1638
+ hasOAuthSession(profileDir) {
1446
1639
  try {
1447
- const cfg = JSON.parse(readFileSync(claudeJsonPath(), "utf-8"));
1640
+ const cfg = JSON.parse(readFileSync(claudeJsonPath(profileDir), "utf-8"));
1448
1641
  return !!cfg.oauthAccount?.accountUuid;
1449
1642
  }
1450
1643
  catch {