@noxsoft/anima 5.0.1 → 5.0.2

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 (180) hide show
  1. package/dist/{accounts-BOkEyUcS.js → accounts-CSCVz9k9.js} +7 -7
  2. package/dist/{acp-cli-BcshtFqY.js → acp-cli-DyVMXQS8.js} +1 -1
  3. package/dist/{agent-c49U1LxE.js → agent-CWQdTmzC.js} +3 -3
  4. package/dist/{agent-Cil6Zvns.js → agent-DiEXJmhj.js} +8 -8
  5. package/dist/{agents-2BloqCjm.js → agents-rAIsxGuA.js} +8 -8
  6. package/dist/{anthropic-direct-runner-BVlO2Vi5.js → anthropic-direct-runner--MPTWkYA.js} +228 -193
  7. package/dist/{anthropic-direct-runner-mh6c_WBB.js → anthropic-direct-runner-_uCPWuBb.js} +232 -197
  8. package/dist/{audit-BbmRSFjf.js → audit-DqjWCAbO.js} +7 -7
  9. package/dist/{auth-choice-BVAMr2Dk.js → auth-choice-o5GHtPGA.js} +57 -5
  10. package/dist/{auth-profiles-Chf1JBpl.js → auth-profiles-DriJ4HU5.js} +1 -1
  11. package/dist/{banner-CgxCSS-T.js → banner-Gtp-FQrx.js} +1 -1
  12. package/dist/build-info.json +3 -3
  13. package/dist/bundled/boot-md/handler.js +13 -13
  14. package/dist/bundled/bootstrap-extra-files/handler.js +3 -3
  15. package/dist/bundled/session-memory/handler.js +10 -10
  16. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  17. package/dist/{channel-web-Y8i3EzJq.js → channel-web-Dz0XZNjP.js} +4 -4
  18. package/dist/{channels-status-issues-BlmAMBCg.js → channels-status-issues-Dqd_vE0e.js} +2 -2
  19. package/dist/{chrome-B2-33FWf.js → chrome-CB6uZMvl.js} +1 -1
  20. package/dist/{chrome-Cxl7I5SB.js → chrome-CKvch3UL.js} +7 -7
  21. package/dist/{clack-prompter-B-ZJG628.js → clack-prompter-BRLXmV52.js} +1 -1
  22. package/dist/{clack-prompter-CCRr4nAr.js → clack-prompter-OFy356QZ.js} +1 -1
  23. package/dist/{cli-C3uw3mId.js → cli-BZz5MnAE.js} +29 -29
  24. package/dist/{cli-CfvBmuhw.js → cli-CJfwHH_z.js} +5 -5
  25. package/dist/{cli-session-BzZYmGP1.js → cli-session-8PeURiwF.js} +6 -6
  26. package/dist/{cli-session-DgUHOBYo.js → cli-session-CczB2Dni.js} +1 -1
  27. package/dist/{command-registry-DLofaVIN.js → command-registry-C9kvlc67.js} +12 -12
  28. package/dist/{common-FlJ-8clz.js → common-C5mWuFve.js} +2 -2
  29. package/dist/{completion-cli-C9KvX2ZF.js → completion-cli-Ci_MzRm6.js} +2 -2
  30. package/dist/{completion-cli-3eYvtAih.js → completion-cli-DhbIrk13.js} +1 -1
  31. package/dist/{config-B-9UciOO.js → config-DezP7V65.js} +2 -2
  32. package/dist/{config-cli-CBXWDtvj.js → config-cli-ePL5ncye.js} +1 -1
  33. package/dist/{config-cli-cmNuTlw0.js → config-cli-uk9vdQZH.js} +1 -1
  34. package/dist/{config-guard-qiKju2Fg.js → config-guard-CCOBZ77Q.js} +1 -1
  35. package/dist/{configure-CGTBBLR_.js → configure-CkGsX5z9.js} +9 -9
  36. package/dist/{configure-Dpk4lSoz.js → configure-D-9Ezgal.js} +11 -11
  37. package/dist/{configure-jlAKeuki.js → configure-DxKMnCHX.js} +40 -40
  38. package/dist/{configure-CH_-SNya.js → configure-KP7q9c7h.js} +4 -4
  39. package/dist/{control-service-CUhnhmpc.js → control-service-D3rS-Yd1.js} +3 -3
  40. package/dist/{cron-cli-CKs1evSF.js → cron-cli-3QY_K0l_.js} +1 -1
  41. package/dist/{daemon-cli-CSCFNzUo.js → daemon-cli-NZ84KRN8.js} +3 -3
  42. package/dist/{deliver-BYxbDIF0.js → deliver-BJn4EmMZ.js} +1 -1
  43. package/dist/{deliver-DkF6LCoS.js → deliver-BhezvxNA.js} +4 -4
  44. package/dist/{deps-D9266xfk.js → deps-CBLy0fqA.js} +1 -1
  45. package/dist/{dispatcher-BeO47t7A.js → dispatcher-Bk0gFz4b.js} +1 -1
  46. package/dist/{doctor-DJfgnQ67.js → doctor-Bto-K3wL.js} +16 -16
  47. package/dist/{doctor-CWmHUAjs.js → doctor-DsRSWr6B.js} +5 -5
  48. package/dist/{doctor-completion-CtCwd1fi.js → doctor-completion-B-SFdu-f.js} +1 -1
  49. package/dist/{doctor-completion-CmJmktDv.js → doctor-completion-OQXTo6RG.js} +1 -1
  50. package/dist/{doctor-config-flow-D_8YNTSK.js → doctor-config-flow-CEkHIHe5.js} +1 -1
  51. package/dist/entry.js +1 -1
  52. package/dist/extensionAPI.js +1531 -411
  53. package/dist/{frontmatter-BmBmtOUh.js → frontmatter-Dsa7N963.js} +1 -1
  54. package/dist/{gateway-cli-DYrzIvOE.js → gateway-cli-Buxz8Y2n.js} +17 -17
  55. package/dist/{gateway-cli-BazTmg20.js → gateway-cli-CEGPCQ_C.js} +52 -52
  56. package/dist/{health-BRSKF_iF.js → health-CMIY7xAI.js} +12 -12
  57. package/dist/{health-JqtB_B8C.js → health-DoGFlPcq.js} +6 -6
  58. package/dist/{heartbeat-visibility-BGj2czmk.js → heartbeat-visibility-BOyU9ccw.js} +2 -2
  59. package/dist/{heartbeat-visibility-pyFf6XBW.js → heartbeat-visibility-DuRmIKOk.js} +2 -2
  60. package/dist/{hooks-cli-B7g3jEC3.js → hooks-cli-B1NXXLxe.js} +30 -30
  61. package/dist/{hooks-cli-ZK4Z044T.js → hooks-cli-BuIU6v0Q.js} +6 -6
  62. package/dist/index.js +11 -11
  63. package/dist/{installs-DBvIs2By.js → installs-Drds9RDI.js} +1 -1
  64. package/dist/{lanes-DyM6NSSL.js → lanes-4_8h1ZQO.js} +274 -12
  65. package/dist/llm-slug-generator.js +10 -10
  66. package/dist/{login-BbfWLOBl.js → login-DwP4KleZ.js} +2 -2
  67. package/dist/{login-qr-BqhujMcQ.js → login-qr-wnynuvLt.js} +5 -5
  68. package/dist/{manager-CcawxgaC.js → manager-ibNP7jE3.js} +2 -2
  69. package/dist/{manager-DS1DfkbC.js → manager-j0ghUvcW.js} +4 -4
  70. package/dist/{media-IIqSkFpd.js → media-BLVMhFB3.js} +2 -2
  71. package/dist/{memory-cli-wDO418hx.js → memory-cli-DSWsPuub.js} +6 -6
  72. package/dist/{model-auth-Uxm-wRjR.js → model-auth-BKf-GZVK.js} +3 -3
  73. package/dist/{models-tQnX4hL4.js → models-BU0CytAy.js} +32 -32
  74. package/dist/{node-cli-DAcW-rfA.js → node-cli-DcwOFcxq.js} +8 -8
  75. package/dist/{onboard-Dd6xFMYB.js → onboard-Dd5wJkeU.js} +6 -6
  76. package/dist/{onboard-dasbF9G1.js → onboard-c84IExbH.js} +4 -4
  77. package/dist/{onboard-channels-DisvVyIs.js → onboard-channels-CCoMzCXV.js} +3 -3
  78. package/dist/{onboard-channels-BUv3VbAL.js → onboard-channels-DYr7fQZq.js} +1 -1
  79. package/dist/{onboard-helpers-49TVSf7J.js → onboard-helpers-BRp_xJvW.js} +1 -1
  80. package/dist/{onboarding-Dq3bCBjc.js → onboarding-192Oe9K6.js} +8 -8
  81. package/dist/{onboarding-6Xs4orLw.js → onboarding-BINruD5r.js} +4 -4
  82. package/dist/{outbound-CliT3nme.js → outbound-BVSyNp-t.js} +1 -1
  83. package/dist/{outbound-CLII4C2A.js → outbound-BaBDhLp1.js} +1 -1
  84. package/dist/{parse-timeout-BnOIKwx8.js → parse-timeout-C1INlSUG.js} +2 -2
  85. package/dist/{pi-auth-json-DLroEcea.js → pi-auth-json-BSLIRI41.js} +2 -2
  86. package/dist/{pi-embedded-CS4D_0t7.js → pi-embedded-DJF_gfcQ.js} +667 -372
  87. package/dist/{pi-tools.policy-RhdERrk3.js → pi-tools.policy-Dtytaq21.js} +3 -3
  88. package/dist/{plugin-registry-l3z9phEV.js → plugin-registry-B0k-53-_.js} +1 -1
  89. package/dist/{plugin-registry-esOxvV3X.js → plugin-registry-CqUo-PU_.js} +1 -1
  90. package/dist/plugin-sdk/agents/gemini-direct-runner.d.ts +38 -0
  91. package/dist/plugin-sdk/commands/onboard-types.d.ts +2 -2
  92. package/dist/{plugins-cli-D2v0n4BL.js → plugins-cli-BYLUFsjH.js} +33 -33
  93. package/dist/{plugins-cli-CbXtuN4b.js → plugins-cli-YjM8IsVo.js} +6 -6
  94. package/dist/{ports-DyX87qKc.js → ports-BmSDzOHm.js} +1 -1
  95. package/dist/{ports-COy7Yg_k.js → ports-Bv3sZ3bd.js} +1 -1
  96. package/dist/{program-y0NPJuYm.js → program-CG5u1KAB.js} +36 -36
  97. package/dist/{program-context-CqPwmgYa.js → program-context-Co2gkUS0.js} +16 -16
  98. package/dist/{prompts-DTKoIKPV.js → prompts-DjKZfP5R.js} +67 -4
  99. package/dist/{prompts-DY0qdoD1.js → prompts-NvPXbNVc.js} +12 -1
  100. package/dist/{pw-ai-CJgeaF06.js → pw-ai-C3j5kV3T.js} +1 -1
  101. package/dist/{pw-ai-A7vggLw8.js → pw-ai-CvxTpFmB.js} +3 -3
  102. package/dist/{qmd-manager-DQsAtmK7.js → qmd-manager-BDq5_dd6.js} +2 -2
  103. package/dist/{register.agent-Bi2FqIlN.js → register.agent-PbAKdlNK.js} +10 -10
  104. package/dist/{register.agent-CRazop1A.js → register.agent-WAjZHuZe.js} +38 -38
  105. package/dist/{register.anima-FFaI_C8x.js → register.anima-CDcnWfQF.js} +2 -2
  106. package/dist/{register.anima-BmdRCJLy.js → register.anima-CZNjkwJH.js} +2 -2
  107. package/dist/{register.configure-FIXOWrO9.js → register.configure-C2zfgyp9.js} +39 -39
  108. package/dist/{register.configure-r5AViY3N.js → register.configure-CNhagtHN.js} +10 -10
  109. package/dist/{register.maintenance-Cpf0ZZTL.js → register.maintenance-DWJjWsIl.js} +11 -11
  110. package/dist/{register.maintenance-DJYji5mV.js → register.maintenance-DkF1Nu_y.js} +39 -39
  111. package/dist/{register.onboard-thGXwzOX.js → register.onboard-DiZzjEwz.js} +13 -13
  112. package/dist/{register.onboard-oUsWjmam.js → register.onboard-xOXNrMu-.js} +41 -41
  113. package/dist/{register.setup-B4MYuE3g.js → register.setup-BmbpgUVU.js} +41 -41
  114. package/dist/{register.setup-DWDrQ7RT.js → register.setup-QAjNkIZr.js} +13 -13
  115. package/dist/{register.status-health-sessions-CTKdzo7a.js → register.status-health-sessions-Cm-lxcT6.js} +37 -37
  116. package/dist/{register.status-health-sessions-Ce9QUAyj.js → register.status-health-sessions-RZxaVSa6.js} +7 -7
  117. package/dist/{register.subclis-CWmYc4AB.js → register.subclis-DCljHJ5a.js} +13 -13
  118. package/dist/{reply-CpHIJ9Kk.js → reply-Bw4torMz.js} +30 -30
  119. package/dist/{reply-BKpIrCr-.js → reply-Dd3EmI4M.js} +5 -5
  120. package/dist/{reply-prefix-BO_Dt82N.js → reply-prefix-DIoRETAD.js} +2 -2
  121. package/dist/{reply-prefix-BwVBvQXp.js → reply-prefix-Dy_yxyCq.js} +2 -2
  122. package/dist/{routes-XhTPYecR.js → routes-CNSdZAkl.js} +3 -3
  123. package/dist/{run--9hftM2H.js → run--R4y3YlU.js} +41 -41
  124. package/dist/{run-CGokiDR8.js → run-DgtaDzEe.js} +15 -15
  125. package/dist/{run-main-DxcOzCLw.js → run-main-eUWh86lF.js} +47 -47
  126. package/dist/{sandbox-CiwAPzO7.js → sandbox-BoNWDo4X.js} +1 -1
  127. package/dist/{sandbox-cli-DsrzwG_s.js → sandbox-cli-lbp0TV67.js} +10 -10
  128. package/dist/{security-cli-CouqfUGc.js → security-cli-CfvP5VqT.js} +13 -13
  129. package/dist/{server-context-B78_sSOW.js → server-context-IfJbmK25.js} +6 -6
  130. package/dist/{server-node-events-DJlLCF2M.js → server-node-events-BG4e9Ws7.js} +5 -5
  131. package/dist/{server-node-events-TN_Y9MgU.js → server-node-events-CFCwOP0n.js} +18 -18
  132. package/dist/{session-cost-usage-vltn5akf.js → session-cost-usage-lLyz71EH.js} +1 -1
  133. package/dist/{sessions-B7clh58j.js → sessions-BZgIAOwH.js} +2 -2
  134. package/dist/{sessions-B7ikzhI_.js → sessions-C3bteMLZ.js} +3 -3
  135. package/dist/{settings-cli-JU5bg5jb.js → settings-cli-C9TMl9Ye.js} +11 -11
  136. package/dist/{settings-cli-DjVugHID.js → settings-cli-Cf0LCRjt.js} +40 -40
  137. package/dist/{setup-token-CvRwJUVY.js → setup-token-DBwe6GDq.js} +1 -1
  138. package/dist/{setup-token-wFsQlaW2.js → setup-token-DTgSXfj7.js} +14 -14
  139. package/dist/{shell-env-C1yK_Rod.js → shell-env-V934Ymrk.js} +1 -1
  140. package/dist/{skill-scanner-BzOYLFR8.js → skill-scanner-DPQDhVEr.js} +1 -1
  141. package/dist/{skills-install-D_bxdc-6.js → skills-install-CC63dOs4.js} +2 -2
  142. package/dist/{sqlite-jyjat2Yt.js → sqlite-Co_lF3s-.js} +1 -1
  143. package/dist/{start-M9abr1O1.js → start-B2DQAmEK.js} +17 -17
  144. package/dist/{start-CAbxlnFZ.js → start-CHLGEHeF.js} +50 -50
  145. package/dist/{status-YH65ctig.js → status-BsCeWiLS.js} +1 -1
  146. package/dist/{status-1TjjCE1l.js → status-C4ZTdSwA.js} +14 -14
  147. package/dist/{status-CxxXFU4R.js → status-CwjUrZ7a.js} +4 -4
  148. package/dist/{status-CrJV2Y4x.js → status-D_FTGxBZ.js} +1 -1
  149. package/dist/{status.update-BzAuf_G-.js → status.update-Cx5l38lC.js} +1 -1
  150. package/dist/{subagent-registry-xJY9wqZy.js → subagent-registry-7qo93ip8.js} +2 -2
  151. package/dist/{subagent-registry-3Dw3YppH.js → subagent-registry-EoZxIEKl.js} +8 -8
  152. package/dist/{subagent-registry-B_65nJFp.js → subagent-registry-hRt-aJnL.js} +9 -9
  153. package/dist/{timeout-Dbspj9Jf.js → timeout-B0mCaCG5.js} +275 -13
  154. package/dist/{pi-embedded-helpers-DnRfi8bN.js → tokens-DXjI-TIV.js} +19 -19
  155. package/dist/{tool-images-y_Ribdl6.js → tool-images-09hnvylP.js} +1 -1
  156. package/dist/{tui--eIVX_W0.js → tui-LRoQ5xyx.js} +2 -2
  157. package/dist/{tui-cli-DBV3lnXA.js → tui-cli-DO_6-GWy.js} +14 -14
  158. package/dist/{update-VFbS9X5L.js → update-DmVSt9s3.js} +1 -1
  159. package/dist/{update-cli-QP5L8xlv.js → update-cli-CnYFvEud.js} +11 -11
  160. package/dist/{update-cli-BzaQvirL.js → update-cli-jEzmvaqS.js} +44 -44
  161. package/dist/{update-runner-B_oj7S_n.js → update-runner-IARYjXbS.js} +2 -2
  162. package/dist/{update-runner-ZuJN8uBE.js → update-runner-OPbRGUfX.js} +1 -1
  163. package/dist/{usage-DzKbMa8N.js → usage-BTc8TK3f.js} +6 -6
  164. package/dist/{web-nI3eIGAT.js → web-Honf40Cm.js} +31 -31
  165. package/dist/{web-CgQc2Raf.js → web-kEraFv8s.js} +28 -28
  166. package/dist/{web-DtWSf5wm.js → web-u554zkLK.js} +7 -7
  167. package/dist/{whatsapp-actions-BzPQUcRR.js → whatsapp-actions-I9RXDK_k.js} +6 -6
  168. package/dist/{whatsapp-actions-CZuxHplg.js → whatsapp-actions-_BrQebuX.js} +5 -5
  169. package/package.json +1 -1
  170. /package/dist/{auth-DuBZWd74.js → auth-DK-0XxZU.js} +0 -0
  171. /package/dist/{boolean-M-esQJt6.js → boolean-Ce2-qkSB.js} +0 -0
  172. /package/dist/{errors-l_q3lLBC.js → errors-B9FgVZ2s.js} +0 -0
  173. /package/dist/{image-ops-BVtm-rU-.js → image-ops-BsXjYj1e.js} +0 -0
  174. /package/dist/{internal-hooks-Exeq-wmx.js → internal-hooks-CZOlj8zG.js} +0 -0
  175. /package/dist/{model-selection-xZLlICyg.js → model-selection-DT-L7bjC.js} +0 -0
  176. /package/dist/{paths-B_RPJLsn.js → paths-CxBUgtZS.js} +0 -0
  177. /package/dist/{pi-embedded-helpers-Cxzk0pra.js → pi-embedded-helpers-CWl0I3Qm.js} +0 -0
  178. /package/dist/{plugins-CFBEoAN4.js → plugins-BDSP0cT6.js} +0 -0
  179. /package/dist/{tokens-DLpZ8RFH.js → tokens-QjoPADtG.js} +0 -0
  180. /package/dist/{transcript-events-CJRvASY_.js → transcript-events-DdnTeoR1.js} +0 -0
@@ -19,7 +19,7 @@ import "express";
19
19
  import "undici";
20
20
  import "file-type";
21
21
  import "ws";
22
- import { getOAuthProviders } from "@mariozechner/pi-ai";
22
+ import { getEnvApiKey, getOAuthApiKey, getOAuthProviders } from "@mariozechner/pi-ai";
23
23
  import "node-edge-tts";
24
24
 
25
25
  //#region src/infra/home-dir.ts
@@ -173,6 +173,22 @@ function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(
173
173
  candidates.push(path.join(newStateDir(effectiveHomedir), CONFIG_FILENAME));
174
174
  return candidates;
175
175
  }
176
+ const OAUTH_FILENAME = "oauth.json";
177
+ /**
178
+ * OAuth credentials storage directory.
179
+ *
180
+ * Precedence:
181
+ * - `ANIMA_OAUTH_DIR` (explicit override)
182
+ * - `$*_STATE_DIR/credentials` (canonical server/default)
183
+ */
184
+ function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
185
+ const override = env.ANIMA_OAUTH_DIR?.trim();
186
+ if (override) return resolveUserPath$1(override, env, envHomedir(env));
187
+ return path.join(stateDir, "credentials");
188
+ }
189
+ function resolveOAuthPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
190
+ return path.join(resolveOAuthDir(env, stateDir), OAUTH_FILENAME);
191
+ }
176
192
 
177
193
  //#endregion
178
194
  //#region src/sessions/session-key-utils.ts
@@ -1704,241 +1720,98 @@ function resolveThinkingDefault(params) {
1704
1720
  }
1705
1721
 
1706
1722
  //#endregion
1707
- //#region src/agents/cli-backends.ts
1708
- const DEFAULT_CLAUDE_BACKEND = {
1709
- command: "claude",
1710
- args: [
1711
- "-p",
1712
- "--output-format",
1713
- "json",
1714
- "--dangerously-skip-permissions"
1715
- ],
1716
- resumeArgs: [
1717
- "-p",
1718
- "--output-format",
1719
- "json",
1720
- "--dangerously-skip-permissions",
1721
- "--resume",
1722
- "{sessionId}"
1723
- ],
1724
- output: "jsonl",
1725
- input: "arg",
1726
- modelArg: "--model",
1727
- modelAliases: {
1728
- opus: "opus",
1729
- "opus-4.6": "opus",
1730
- "opus-4.5": "opus",
1731
- "opus-4": "opus",
1732
- "claude-opus-4-6": "opus",
1733
- "claude-opus-4-5": "opus",
1734
- "claude-opus-4": "opus",
1735
- sonnet: "sonnet",
1736
- "sonnet-4.5": "sonnet",
1737
- "sonnet-4.1": "sonnet",
1738
- "sonnet-4.0": "sonnet",
1739
- "claude-sonnet-4-5": "sonnet",
1740
- "claude-sonnet-4-1": "sonnet",
1741
- "claude-sonnet-4-0": "sonnet",
1742
- haiku: "haiku",
1743
- "haiku-3.5": "haiku",
1744
- "claude-haiku-3-5": "haiku"
1745
- },
1746
- sessionArg: "--session-id",
1747
- sessionMode: "always",
1748
- sessionIdFields: [
1749
- "session_id",
1750
- "sessionId",
1751
- "conversation_id",
1752
- "conversationId"
1753
- ],
1754
- systemPromptArg: "--append-system-prompt",
1755
- systemPromptMode: "append",
1756
- systemPromptWhen: "first",
1757
- clearEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"],
1758
- serialize: true
1759
- };
1760
- const DEFAULT_CODEX_BACKEND = {
1761
- command: "codex",
1762
- args: [
1763
- "exec",
1764
- "--json",
1765
- "--color",
1766
- "never",
1767
- "--sandbox",
1768
- "read-only",
1769
- "--skip-git-repo-check"
1770
- ],
1771
- resumeArgs: [
1772
- "exec",
1773
- "resume",
1774
- "{sessionId}"
1775
- ],
1776
- output: "jsonl",
1777
- resumeOutput: "text",
1778
- input: "arg",
1779
- modelArg: "--model",
1780
- imageArg: "--image",
1781
- sessionMode: "existing",
1782
- serialize: true
1783
- };
1784
- const CLAUDE_BACKEND_ALIASES = [
1785
- "claude-cli",
1786
- "anthropic",
1787
- "claude"
1788
- ];
1789
- const CODEX_BACKEND_ALIASES = [
1790
- "codex-cli",
1791
- "openai-codex",
1792
- "openai",
1793
- "codex"
1794
- ];
1795
- const CLAUDE_BACKEND_ALIAS_SET = new Set(CLAUDE_BACKEND_ALIASES.map((alias) => normalizeBackendKey(alias)));
1796
- const CODEX_BACKEND_ALIAS_SET = new Set(CODEX_BACKEND_ALIASES.map((alias) => normalizeBackendKey(alias)));
1797
- function normalizeBackendKey(key) {
1798
- return normalizeProviderId(key);
1799
- }
1800
- function pickBackendConfig(config, normalizedId) {
1801
- for (const [key, entry] of Object.entries(config)) if (normalizeBackendKey(key) === normalizedId) return entry;
1723
+ //#region src/logging/redact-identifier.ts
1724
+ function sha256HexPrefix(value, len = 12) {
1725
+ const safeLen = Number.isFinite(len) ? Math.max(1, Math.floor(len)) : 12;
1726
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, safeLen);
1802
1727
  }
1803
- function pickBackendConfigByAliases(config, aliases) {
1804
- for (const alias of aliases) {
1805
- const matched = pickBackendConfig(config, normalizeBackendKey(alias));
1806
- if (matched) return matched;
1807
- }
1728
+ function redactIdentifier(value, opts) {
1729
+ const trimmed = value?.trim();
1730
+ if (!trimmed) return "-";
1731
+ return `sha256:${sha256HexPrefix(trimmed, opts?.len ?? 12)}`;
1808
1732
  }
1809
- function mergeBackendConfig(base, override) {
1810
- if (!override) return { ...base };
1733
+
1734
+ //#endregion
1735
+ //#region src/agents/workspace-run.ts
1736
+ function resolveRunAgentId(params) {
1737
+ const rawSessionKey = params.sessionKey?.trim() ?? "";
1738
+ const shape = classifySessionKeyShape(rawSessionKey);
1739
+ if (shape === "malformed_agent") throw new Error("Malformed agent session key; refusing workspace resolution.");
1740
+ const explicit = typeof params.agentId === "string" && params.agentId.trim() ? normalizeAgentId(params.agentId) : void 0;
1741
+ if (explicit) return {
1742
+ agentId: explicit,
1743
+ agentIdSource: "explicit"
1744
+ };
1745
+ const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
1746
+ if (shape === "missing" || shape === "legacy_or_alias") return {
1747
+ agentId: defaultAgentId || DEFAULT_AGENT_ID,
1748
+ agentIdSource: "default"
1749
+ };
1750
+ const parsed = parseAgentSessionKey(rawSessionKey);
1751
+ if (parsed?.agentId) return {
1752
+ agentId: normalizeAgentId(parsed.agentId),
1753
+ agentIdSource: "session_key"
1754
+ };
1811
1755
  return {
1812
- ...base,
1813
- ...override,
1814
- args: override.args ?? base.args,
1815
- env: {
1816
- ...base.env,
1817
- ...override.env
1818
- },
1819
- modelAliases: {
1820
- ...base.modelAliases,
1821
- ...override.modelAliases
1822
- },
1823
- clearEnv: Array.from(new Set([...base.clearEnv ?? [], ...override.clearEnv ?? []])),
1824
- sessionIdFields: override.sessionIdFields ?? base.sessionIdFields,
1825
- sessionArgs: override.sessionArgs ?? base.sessionArgs,
1826
- resumeArgs: override.resumeArgs ?? base.resumeArgs
1756
+ agentId: defaultAgentId || DEFAULT_AGENT_ID,
1757
+ agentIdSource: "default"
1827
1758
  };
1828
1759
  }
1829
- function resolveCliBackendConfig(provider, cfg) {
1830
- const normalized = normalizeBackendKey(provider);
1831
- const configured = cfg?.agents?.defaults?.cliBackends ?? {};
1832
- if (CLAUDE_BACKEND_ALIAS_SET.has(normalized)) {
1833
- const merged = mergeBackendConfig(DEFAULT_CLAUDE_BACKEND, pickBackendConfigByAliases(configured, [provider, ...CLAUDE_BACKEND_ALIASES]));
1834
- const command = merged.command?.trim();
1835
- if (!command) return null;
1836
- return {
1837
- id: normalizeBackendKey("claude-cli"),
1838
- config: {
1839
- ...merged,
1840
- command
1841
- }
1842
- };
1843
- }
1844
- if (CODEX_BACKEND_ALIAS_SET.has(normalized)) {
1845
- const merged = mergeBackendConfig(DEFAULT_CODEX_BACKEND, pickBackendConfigByAliases(configured, [provider, ...CODEX_BACKEND_ALIASES]));
1846
- const command = merged.command?.trim();
1847
- if (!command) return null;
1848
- return {
1849
- id: normalizeBackendKey("codex-cli"),
1850
- config: {
1851
- ...merged,
1852
- command
1853
- }
1760
+ function redactRunIdentifier(value) {
1761
+ return redactIdentifier(value, { len: 12 });
1762
+ }
1763
+ function resolveRunWorkspaceDir(params) {
1764
+ const requested = params.workspaceDir;
1765
+ const { agentId, agentIdSource } = resolveRunAgentId({
1766
+ sessionKey: params.sessionKey,
1767
+ agentId: params.agentId,
1768
+ config: params.config
1769
+ });
1770
+ if (typeof requested === "string") {
1771
+ const trimmed = requested.trim();
1772
+ if (trimmed) return {
1773
+ workspaceDir: resolveUserPath(trimmed),
1774
+ usedFallback: false,
1775
+ agentId,
1776
+ agentIdSource
1854
1777
  };
1855
1778
  }
1856
- const override = pickBackendConfig(configured, normalized);
1857
- if (!override) return null;
1858
- const command = override.command?.trim();
1859
- if (!command) return null;
1779
+ const fallbackReason = requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type";
1860
1780
  return {
1861
- id: normalized,
1862
- config: {
1863
- ...override,
1864
- command
1865
- }
1781
+ workspaceDir: resolveUserPath(resolveAgentWorkspaceDir(params.config ?? {}, agentId)),
1782
+ usedFallback: true,
1783
+ fallbackReason,
1784
+ agentId,
1785
+ agentIdSource
1866
1786
  };
1867
1787
  }
1868
1788
 
1869
1789
  //#endregion
1870
- //#region src/auto-reply/tokens.ts
1871
- const SILENT_REPLY_TOKEN = "NO_REPLY";
1872
-
1873
- //#endregion
1874
- //#region src/auto-reply/heartbeat.ts
1875
- const HEARTBEAT_PROMPT = "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.";
1876
- function resolveHeartbeatPrompt(raw) {
1877
- return (typeof raw === "string" ? raw.trim() : "") || HEARTBEAT_PROMPT;
1878
- }
1879
-
1880
- //#endregion
1881
- //#region src/utils/boolean.ts
1882
- const DEFAULT_TRUTHY = [
1883
- "true",
1884
- "1",
1885
- "yes",
1886
- "on"
1887
- ];
1888
- const DEFAULT_FALSY = [
1889
- "false",
1890
- "0",
1891
- "no",
1892
- "off"
1893
- ];
1894
- const DEFAULT_TRUTHY_SET = new Set(DEFAULT_TRUTHY);
1895
- const DEFAULT_FALSY_SET = new Set(DEFAULT_FALSY);
1896
- function parseBooleanValue(value, options = {}) {
1897
- if (typeof value === "boolean") return value;
1898
- if (typeof value !== "string") return;
1899
- const normalized = value.trim().toLowerCase();
1900
- if (!normalized) return;
1901
- const truthy = options.truthy ?? DEFAULT_TRUTHY;
1902
- const falsy = options.falsy ?? DEFAULT_FALSY;
1903
- const truthySet = truthy === DEFAULT_TRUTHY ? DEFAULT_TRUTHY_SET : new Set(truthy);
1904
- const falsySet = falsy === DEFAULT_FALSY ? DEFAULT_FALSY_SET : new Set(falsy);
1905
- if (truthySet.has(normalized)) return true;
1906
- if (falsySet.has(normalized)) return false;
1907
- }
1908
-
1909
- //#endregion
1910
- //#region src/infra/env.ts
1911
- const log$8 = createSubsystemLogger("env");
1912
- function isTruthyEnvValue(value) {
1913
- return parseBooleanValue(value) === true;
1914
- }
1915
-
1916
- //#endregion
1917
- //#region src/hooks/internal-hooks.ts
1918
- /** Registry of hook handlers by event key */
1919
- const handlers = /* @__PURE__ */ new Map();
1920
- /**
1921
- * Trigger a hook event
1922
- *
1923
- * Calls all handlers registered for:
1924
- * 1. The general event type (e.g., 'command')
1925
- * 2. The specific event:action combination (e.g., 'command:new')
1926
- *
1927
- * Handlers are called in registration order. Errors are caught and logged
1928
- * but don't prevent other handlers from running.
1929
- *
1930
- * @param event - The event to trigger
1931
- */
1932
- async function triggerInternalHook(event) {
1933
- const typeHandlers = handlers.get(event.type) ?? [];
1934
- const specificHandlers = handlers.get(`${event.type}:${event.action}`) ?? [];
1935
- const allHandlers = [...typeHandlers, ...specificHandlers];
1936
- if (allHandlers.length === 0) return;
1937
- for (const handler of allHandlers) try {
1938
- await handler(event);
1939
- } catch (err) {
1940
- console.error(`Hook error [${event.type}:${event.action}]:`, err instanceof Error ? err.message : String(err));
1941
- }
1790
+ //#region src/hooks/internal-hooks.ts
1791
+ /** Registry of hook handlers by event key */
1792
+ const handlers = /* @__PURE__ */ new Map();
1793
+ /**
1794
+ * Trigger a hook event
1795
+ *
1796
+ * Calls all handlers registered for:
1797
+ * 1. The general event type (e.g., 'command')
1798
+ * 2. The specific event:action combination (e.g., 'command:new')
1799
+ *
1800
+ * Handlers are called in registration order. Errors are caught and logged
1801
+ * but don't prevent other handlers from running.
1802
+ *
1803
+ * @param event - The event to trigger
1804
+ */
1805
+ async function triggerInternalHook(event) {
1806
+ const typeHandlers = handlers.get(event.type) ?? [];
1807
+ const specificHandlers = handlers.get(`${event.type}:${event.action}`) ?? [];
1808
+ const allHandlers = [...typeHandlers, ...specificHandlers];
1809
+ if (allHandlers.length === 0) return;
1810
+ for (const handler of allHandlers) try {
1811
+ await handler(event);
1812
+ } catch (err) {
1813
+ console.error(`Hook error [${event.type}:${event.action}]:`, err instanceof Error ? err.message : String(err));
1814
+ }
1942
1815
  }
1943
1816
  /**
1944
1817
  * Create a hook event with common fields filled in
@@ -2076,6 +1949,42 @@ function loadDotEnv(opts) {
2076
1949
  });
2077
1950
  }
2078
1951
 
1952
+ //#endregion
1953
+ //#region src/utils/boolean.ts
1954
+ const DEFAULT_TRUTHY = [
1955
+ "true",
1956
+ "1",
1957
+ "yes",
1958
+ "on"
1959
+ ];
1960
+ const DEFAULT_FALSY = [
1961
+ "false",
1962
+ "0",
1963
+ "no",
1964
+ "off"
1965
+ ];
1966
+ const DEFAULT_TRUTHY_SET = new Set(DEFAULT_TRUTHY);
1967
+ const DEFAULT_FALSY_SET = new Set(DEFAULT_FALSY);
1968
+ function parseBooleanValue(value, options = {}) {
1969
+ if (typeof value === "boolean") return value;
1970
+ if (typeof value !== "string") return;
1971
+ const normalized = value.trim().toLowerCase();
1972
+ if (!normalized) return;
1973
+ const truthy = options.truthy ?? DEFAULT_TRUTHY;
1974
+ const falsy = options.falsy ?? DEFAULT_FALSY;
1975
+ const truthySet = truthy === DEFAULT_TRUTHY ? DEFAULT_TRUTHY_SET : new Set(truthy);
1976
+ const falsySet = falsy === DEFAULT_FALSY ? DEFAULT_FALSY_SET : new Set(falsy);
1977
+ if (truthySet.has(normalized)) return true;
1978
+ if (falsySet.has(normalized)) return false;
1979
+ }
1980
+
1981
+ //#endregion
1982
+ //#region src/infra/env.ts
1983
+ const log$9 = createSubsystemLogger("env");
1984
+ function isTruthyEnvValue(value) {
1985
+ return parseBooleanValue(value) === true;
1986
+ }
1987
+
2079
1988
  //#endregion
2080
1989
  //#region src/infra/shell-env.ts
2081
1990
  const DEFAULT_TIMEOUT_MS$1 = 15e3;
@@ -2175,6 +2084,9 @@ function resolveShellEnvFallbackTimeoutMs(env) {
2175
2084
  if (!Number.isFinite(parsed)) return DEFAULT_TIMEOUT_MS$1;
2176
2085
  return Math.max(0, parsed);
2177
2086
  }
2087
+ function getShellEnvAppliedKeys() {
2088
+ return [...lastAppliedKeys];
2089
+ }
2178
2090
 
2179
2091
  //#endregion
2180
2092
  //#region src/version.ts
@@ -7153,9 +7065,58 @@ const SANDBOX_STATE_DIR = path.join(STATE_DIR, "sandbox");
7153
7065
  const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
7154
7066
  const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");
7155
7067
 
7068
+ //#endregion
7069
+ //#region src/cli/cli-name.ts
7070
+ const DEFAULT_CLI_NAME = "anima";
7071
+ const KNOWN_CLI_NAMES = new Set([DEFAULT_CLI_NAME]);
7072
+ const CLI_PREFIX_RE$1 = /^(?:((?:pnpm|npm|bunx|npx)\s+))?anima\b/;
7073
+ function resolveCliName(argv = process.argv) {
7074
+ const argv1 = argv[1];
7075
+ if (!argv1) return DEFAULT_CLI_NAME;
7076
+ const base = path.basename(argv1).trim();
7077
+ if (KNOWN_CLI_NAMES.has(base)) return base;
7078
+ return DEFAULT_CLI_NAME;
7079
+ }
7080
+ function replaceCliName(command, cliName = resolveCliName()) {
7081
+ if (!command.trim()) return command;
7082
+ if (!CLI_PREFIX_RE$1.test(command)) return command;
7083
+ return command.replace(CLI_PREFIX_RE$1, (_match, runner) => {
7084
+ return `${runner ?? ""}${cliName}`;
7085
+ });
7086
+ }
7087
+
7088
+ //#endregion
7089
+ //#region src/cli/profile-utils.ts
7090
+ const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
7091
+ function isValidProfileName(value) {
7092
+ if (!value) return false;
7093
+ return PROFILE_NAME_RE.test(value);
7094
+ }
7095
+ function normalizeProfileName(raw) {
7096
+ const profile = raw?.trim();
7097
+ if (!profile) return null;
7098
+ if (profile.toLowerCase() === "default") return null;
7099
+ if (!isValidProfileName(profile)) return null;
7100
+ return profile;
7101
+ }
7102
+
7103
+ //#endregion
7104
+ //#region src/cli/command-format.ts
7105
+ const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+anima\b|^anima\b/;
7106
+ const PROFILE_FLAG_RE = /(?:^|\s)--profile(?:\s|=|$)/;
7107
+ const DEV_FLAG_RE = /(?:^|\s)--dev(?:\s|$)/;
7108
+ function formatCliCommand(command, env = process.env) {
7109
+ const normalizedCommand = replaceCliName(command, resolveCliName());
7110
+ const profile = normalizeProfileName(env.ANIMA_PROFILE);
7111
+ if (!profile) return normalizedCommand;
7112
+ if (!CLI_PREFIX_RE.test(normalizedCommand)) return normalizedCommand;
7113
+ if (PROFILE_FLAG_RE.test(normalizedCommand) || DEV_FLAG_RE.test(normalizedCommand)) return normalizedCommand;
7114
+ return normalizedCommand.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`);
7115
+ }
7116
+
7156
7117
  //#endregion
7157
7118
  //#region src/agents/skills/plugin-skills.ts
7158
- const log$7 = createSubsystemLogger("skills");
7119
+ const log$8 = createSubsystemLogger("skills");
7159
7120
 
7160
7121
  //#endregion
7161
7122
  //#region src/agents/skills/workspace.ts
@@ -7239,7 +7200,7 @@ const LSOF_CANDIDATES = process.platform === "darwin" ? ["/usr/sbin/lsof", "/usr
7239
7200
 
7240
7201
  //#endregion
7241
7202
  //#region src/browser/chrome.ts
7242
- const log$6 = createSubsystemLogger("browser").child("chrome");
7203
+ const log$7 = createSubsystemLogger("browser").child("chrome");
7243
7204
 
7244
7205
  //#endregion
7245
7206
  //#region src/agents/sandbox/docker.ts
@@ -7424,7 +7385,7 @@ function resolveCleanupState() {
7424
7385
  };
7425
7386
  return proc[CLEANUP_STATE_KEY];
7426
7387
  }
7427
- function isAlive(pid) {
7388
+ function isAlive$1(pid) {
7428
7389
  if (!Number.isFinite(pid) || pid <= 0) return false;
7429
7390
  try {
7430
7391
  process.kill(pid, 0);
@@ -7479,7 +7440,7 @@ function registerCleanupHandlers() {
7479
7440
  } catch {}
7480
7441
  }
7481
7442
  }
7482
- async function readLockPayload(lockPath) {
7443
+ async function readLockPayload$1(lockPath) {
7483
7444
  try {
7484
7445
  const raw = await fs$1.readFile(lockPath, "utf8");
7485
7446
  const parsed = JSON.parse(raw);
@@ -7545,10 +7506,10 @@ async function acquireSessionWriteLock(params) {
7545
7506
  } };
7546
7507
  } catch (err) {
7547
7508
  if (err.code !== "EEXIST") throw err;
7548
- const payload = await readLockPayload(lockPath);
7509
+ const payload = await readLockPayload$1(lockPath);
7549
7510
  const createdAt = payload?.createdAt ? Date.parse(payload.createdAt) : NaN;
7550
7511
  const stale = !Number.isFinite(createdAt) || Date.now() - createdAt > staleMs;
7551
- const alive = payload?.pid ? isAlive(payload.pid) : false;
7512
+ const alive = payload?.pid ? isAlive$1(payload.pid) : false;
7552
7513
  if (stale || !alive) {
7553
7514
  await fs$1.rm(lockPath, { force: true });
7554
7515
  continue;
@@ -7557,7 +7518,7 @@ async function acquireSessionWriteLock(params) {
7557
7518
  await new Promise((r) => setTimeout(r, delay));
7558
7519
  }
7559
7520
  }
7560
- const payload = await readLockPayload(lockPath);
7521
+ const payload = await readLockPayload$1(lockPath);
7561
7522
  const owner = payload?.pid ? `pid=${payload.pid}` : "unknown";
7562
7523
  throw new Error(`session file locked (timeout ${timeoutMs}ms): ${owner} ${lockPath}`);
7563
7524
  }
@@ -7656,7 +7617,7 @@ function getFileMtimeMs(filePath) {
7656
7617
 
7657
7618
  //#endregion
7658
7619
  //#region src/config/sessions/store.ts
7659
- const log$5 = createSubsystemLogger("sessions/store");
7620
+ const log$6 = createSubsystemLogger("sessions/store");
7660
7621
  const SESSION_STORE_CACHE = /* @__PURE__ */ new Map();
7661
7622
  const DEFAULT_SESSION_STORE_TTL_MS = 45e3;
7662
7623
  function isSessionStoreRecord(value) {
@@ -7799,7 +7760,7 @@ function pruneStaleEntries(store, overrideMaxAgeMs, opts = {}) {
7799
7760
  delete store[key];
7800
7761
  pruned++;
7801
7762
  }
7802
- if (pruned > 0 && opts.log !== false) log$5.info("pruned stale session entries", {
7763
+ if (pruned > 0 && opts.log !== false) log$6.info("pruned stale session entries", {
7803
7764
  pruned,
7804
7765
  maxAgeMs
7805
7766
  });
@@ -7842,7 +7803,7 @@ function capEntryCount(store, overrideMax, opts = {}) {
7842
7803
  return getEntryUpdatedAt(store[b]) - aTime;
7843
7804
  }).slice(maxEntries);
7844
7805
  for (const key of toRemove) delete store[key];
7845
- if (opts.log !== false) log$5.info("capped session entry count", {
7806
+ if (opts.log !== false) log$6.info("capped session entry count", {
7846
7807
  removed: toRemove.length,
7847
7808
  maxEntries
7848
7809
  });
@@ -7868,7 +7829,7 @@ async function rotateSessionFile(storePath, overrideBytes) {
7868
7829
  const backupPath = `${storePath}.bak.${Date.now()}`;
7869
7830
  try {
7870
7831
  await fs.promises.rename(storePath, backupPath);
7871
- log$5.info("rotated session store file", {
7832
+ log$6.info("rotated session store file", {
7872
7833
  backupPath: path.basename(backupPath),
7873
7834
  sizeBytes: fileSize
7874
7835
  });
@@ -7883,7 +7844,7 @@ async function rotateSessionFile(storePath, overrideBytes) {
7883
7844
  if (backups.length > maxBackups) {
7884
7845
  const toDelete = backups.slice(maxBackups);
7885
7846
  for (const old of toDelete) await fs.promises.unlink(path.join(dir, old)).catch(() => void 0);
7886
- log$5.info("cleaned up old session store backups", { deleted: toDelete.length });
7847
+ log$6.info("cleaned up old session store backups", { deleted: toDelete.length });
7887
7848
  }
7888
7849
  } catch {}
7889
7850
  return true;
@@ -7903,7 +7864,7 @@ async function saveSessionStoreUnlocked(storePath, store, opts) {
7903
7864
  maxEntries: maintenance.maxEntries
7904
7865
  });
7905
7866
  if (warning) {
7906
- log$5.warn("session maintenance would evict active session; skipping enforcement", {
7867
+ log$6.warn("session maintenance would evict active session; skipping enforcement", {
7907
7868
  activeSessionKey: warning.activeSessionKey,
7908
7869
  wouldPrune: warning.wouldPrune,
7909
7870
  wouldCap: warning.wouldCap,
@@ -8221,7 +8182,7 @@ function isFailoverErrorMessage(raw) {
8221
8182
  //#endregion
8222
8183
  //#region src/agents/tool-images.ts
8223
8184
  const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
8224
- const log$4 = createSubsystemLogger("agents/tool-images");
8185
+ const log$5 = createSubsystemLogger("agents/tool-images");
8225
8186
 
8226
8187
  //#endregion
8227
8188
  //#region src/auto-reply/thinking.ts
@@ -8266,6 +8227,55 @@ async function resolveBootstrapContextForRun(params) {
8266
8227
  };
8267
8228
  }
8268
8229
 
8230
+ //#endregion
8231
+ //#region src/auto-reply/tokens.ts
8232
+ const SILENT_REPLY_TOKEN = "NO_REPLY";
8233
+
8234
+ //#endregion
8235
+ //#region src/auto-reply/heartbeat.ts
8236
+ const HEARTBEAT_PROMPT = "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.";
8237
+ function resolveHeartbeatPrompt(raw) {
8238
+ return (typeof raw === "string" ? raw.trim() : "") || HEARTBEAT_PROMPT;
8239
+ }
8240
+
8241
+ //#endregion
8242
+ //#region src/agents/docs-path.ts
8243
+ async function resolveAnimaDocsPath(params) {
8244
+ const workspaceDir = params.workspaceDir?.trim();
8245
+ if (workspaceDir) {
8246
+ const workspaceDocs = path.join(workspaceDir, "docs");
8247
+ if (fs.existsSync(workspaceDocs)) return workspaceDocs;
8248
+ }
8249
+ const packageRoot = await resolveAnimaPackageRoot({
8250
+ cwd: params.cwd,
8251
+ argv1: params.argv1,
8252
+ moduleUrl: params.moduleUrl
8253
+ });
8254
+ if (!packageRoot) return null;
8255
+ const packageDocs = path.join(packageRoot, "docs");
8256
+ return fs.existsSync(packageDocs) ? packageDocs : null;
8257
+ }
8258
+
8259
+ //#endregion
8260
+ //#region src/utils/normalize-secret-input.ts
8261
+ /**
8262
+ * Secret normalization for copy/pasted credentials.
8263
+ *
8264
+ * Common footgun: line breaks (especially `\r`) embedded in API keys/tokens.
8265
+ * We strip line breaks anywhere, then trim whitespace at the ends.
8266
+ *
8267
+ * Intentionally does NOT remove ordinary spaces inside the string to avoid
8268
+ * silently altering "Bearer <token>" style values.
8269
+ */
8270
+ function normalizeSecretInput(value) {
8271
+ if (typeof value !== "string") return "";
8272
+ return value.replace(/[\r\n\u2028\u2029]+/g, "").trim();
8273
+ }
8274
+ function normalizeOptionalSecretInput(value) {
8275
+ const normalized = normalizeSecretInput(value);
8276
+ return normalized ? normalized : void 0;
8277
+ }
8278
+
8269
8279
  //#endregion
8270
8280
  //#region src/agents/auth-profiles/constants.ts
8271
8281
  const AUTH_STORE_VERSION = 1;
@@ -8273,9 +8283,19 @@ const AUTH_PROFILE_FILENAME = "auth-profiles.json";
8273
8283
  const LEGACY_AUTH_FILENAME = "auth.json";
8274
8284
  const QWEN_CLI_PROFILE_ID = "qwen-portal:qwen-cli";
8275
8285
  const MINIMAX_CLI_PROFILE_ID = "minimax-portal:minimax-cli";
8286
+ const AUTH_STORE_LOCK_OPTIONS = {
8287
+ retries: {
8288
+ retries: 10,
8289
+ factor: 2,
8290
+ minTimeout: 100,
8291
+ maxTimeout: 1e4,
8292
+ randomize: true
8293
+ },
8294
+ stale: 3e4
8295
+ };
8276
8296
  const EXTERNAL_CLI_SYNC_TTL_MS = 900 * 1e3;
8277
8297
  const EXTERNAL_CLI_NEAR_EXPIRY_MS = 600 * 1e3;
8278
- const log$3 = createSubsystemLogger("agents/auth-profiles");
8298
+ const log$4 = createSubsystemLogger("agents/auth-profiles");
8279
8299
 
8280
8300
  //#endregion
8281
8301
  //#region src/plugin-sdk/file-lock.ts
@@ -8286,6 +8306,120 @@ function resolveHeldLocks() {
8286
8306
  return proc[HELD_LOCKS_KEY];
8287
8307
  }
8288
8308
  const HELD_LOCKS = resolveHeldLocks();
8309
+ function isAlive(pid) {
8310
+ if (!Number.isFinite(pid) || pid <= 0) return false;
8311
+ try {
8312
+ process.kill(pid, 0);
8313
+ return true;
8314
+ } catch {
8315
+ return false;
8316
+ }
8317
+ }
8318
+ function computeDelayMs(retries, attempt) {
8319
+ const base = Math.min(retries.maxTimeout, Math.max(retries.minTimeout, retries.minTimeout * retries.factor ** attempt));
8320
+ const jitter = retries.randomize ? 1 + Math.random() : 1;
8321
+ return Math.min(retries.maxTimeout, Math.round(base * jitter));
8322
+ }
8323
+ async function readLockPayload(lockPath) {
8324
+ try {
8325
+ const raw = await fs$1.readFile(lockPath, "utf8");
8326
+ const parsed = JSON.parse(raw);
8327
+ if (typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string") return null;
8328
+ return {
8329
+ pid: parsed.pid,
8330
+ createdAt: parsed.createdAt
8331
+ };
8332
+ } catch {
8333
+ return null;
8334
+ }
8335
+ }
8336
+ async function resolveNormalizedFilePath(filePath) {
8337
+ const resolved = path.resolve(filePath);
8338
+ const dir = path.dirname(resolved);
8339
+ await fs$1.mkdir(dir, { recursive: true });
8340
+ try {
8341
+ const realDir = await fs$1.realpath(dir);
8342
+ return path.join(realDir, path.basename(resolved));
8343
+ } catch {
8344
+ return resolved;
8345
+ }
8346
+ }
8347
+ async function isStaleLock(lockPath, staleMs) {
8348
+ const payload = await readLockPayload(lockPath);
8349
+ if (payload?.pid && !isAlive(payload.pid)) return true;
8350
+ if (payload?.createdAt) {
8351
+ const createdAt = Date.parse(payload.createdAt);
8352
+ if (!Number.isFinite(createdAt) || Date.now() - createdAt > staleMs) return true;
8353
+ }
8354
+ try {
8355
+ const stat = await fs$1.stat(lockPath);
8356
+ return Date.now() - stat.mtimeMs > staleMs;
8357
+ } catch {
8358
+ return true;
8359
+ }
8360
+ }
8361
+ async function acquireFileLock(filePath, options) {
8362
+ const normalizedFile = await resolveNormalizedFilePath(filePath);
8363
+ const lockPath = `${normalizedFile}.lock`;
8364
+ const held = HELD_LOCKS.get(normalizedFile);
8365
+ if (held) {
8366
+ held.count += 1;
8367
+ return {
8368
+ lockPath,
8369
+ release: async () => {
8370
+ const current = HELD_LOCKS.get(normalizedFile);
8371
+ if (!current) return;
8372
+ current.count -= 1;
8373
+ if (current.count > 0) return;
8374
+ HELD_LOCKS.delete(normalizedFile);
8375
+ await current.handle.close().catch(() => void 0);
8376
+ await fs$1.rm(current.lockPath, { force: true }).catch(() => void 0);
8377
+ }
8378
+ };
8379
+ }
8380
+ const attempts = Math.max(1, options.retries.retries + 1);
8381
+ for (let attempt = 0; attempt < attempts; attempt += 1) try {
8382
+ const handle = await fs$1.open(lockPath, "wx");
8383
+ await handle.writeFile(JSON.stringify({
8384
+ pid: process.pid,
8385
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8386
+ }, null, 2), "utf8");
8387
+ HELD_LOCKS.set(normalizedFile, {
8388
+ count: 1,
8389
+ handle,
8390
+ lockPath
8391
+ });
8392
+ return {
8393
+ lockPath,
8394
+ release: async () => {
8395
+ const current = HELD_LOCKS.get(normalizedFile);
8396
+ if (!current) return;
8397
+ current.count -= 1;
8398
+ if (current.count > 0) return;
8399
+ HELD_LOCKS.delete(normalizedFile);
8400
+ await current.handle.close().catch(() => void 0);
8401
+ await fs$1.rm(current.lockPath, { force: true }).catch(() => void 0);
8402
+ }
8403
+ };
8404
+ } catch (err) {
8405
+ if (err.code !== "EEXIST") throw err;
8406
+ if (await isStaleLock(lockPath, options.stale)) {
8407
+ await fs$1.rm(lockPath, { force: true }).catch(() => void 0);
8408
+ continue;
8409
+ }
8410
+ if (attempt >= attempts - 1) break;
8411
+ await new Promise((resolve) => setTimeout(resolve, computeDelayMs(options.retries, attempt)));
8412
+ }
8413
+ throw new Error(`file lock timeout for ${normalizedFile}`);
8414
+ }
8415
+ async function withFileLock(filePath, options, fn) {
8416
+ const lock = await acquireFileLock(filePath, options);
8417
+ try {
8418
+ return await fn();
8419
+ } finally {
8420
+ await lock.release();
8421
+ }
8422
+ }
8289
8423
 
8290
8424
  //#endregion
8291
8425
  //#region src/infra/json-file.ts
@@ -8310,7 +8444,7 @@ function saveJsonFile(pathname, data) {
8310
8444
 
8311
8445
  //#endregion
8312
8446
  //#region src/agents/cli-credentials.ts
8313
- const log$2 = createSubsystemLogger("agents/auth-profiles");
8447
+ const log$3 = createSubsystemLogger("agents/auth-profiles");
8314
8448
  const QWEN_CLI_CREDENTIALS_RELATIVE_PATH = ".qwen/oauth_creds.json";
8315
8449
  const MINIMAX_CLI_CREDENTIALS_RELATIVE_PATH = ".minimax/oauth_creds.json";
8316
8450
  let qwenCliCache = null;
@@ -8396,7 +8530,7 @@ function syncExternalCliCredentialsForProvider(store, profileId, provider, readC
8396
8530
  const existingOAuth = existing?.type === "oauth" ? existing : void 0;
8397
8531
  if ((!existingOAuth || existingOAuth.provider !== provider || existingOAuth.expires <= now || creds.expires > existingOAuth.expires) && !shallowEqualOAuthCredentials(existingOAuth, creds)) {
8398
8532
  store.profiles[profileId] = creds;
8399
- log$3.info(`synced ${provider} credentials from external cli`, {
8533
+ log$4.info(`synced ${provider} credentials from external cli`, {
8400
8534
  profileId,
8401
8535
  expires: new Date(creds.expires).toISOString()
8402
8536
  });
@@ -8420,7 +8554,7 @@ function syncExternalCliCredentials(store) {
8420
8554
  if ((!existingOAuth || existingOAuth.provider !== "qwen-portal" || existingOAuth.expires <= now || qwenCreds.expires > existingOAuth.expires) && !shallowEqualOAuthCredentials(existingOAuth, qwenCreds)) {
8421
8555
  store.profiles[QWEN_CLI_PROFILE_ID] = qwenCreds;
8422
8556
  mutated = true;
8423
- log$3.info("synced qwen credentials from qwen cli", {
8557
+ log$4.info("synced qwen credentials from qwen cli", {
8424
8558
  profileId: QWEN_CLI_PROFILE_ID,
8425
8559
  expires: new Date(qwenCreds.expires).toISOString()
8426
8560
  });
@@ -8448,6 +8582,17 @@ function resolveLegacyAuthStorePath(agentDir) {
8448
8582
  const resolved = resolveUserPath(agentDir ?? resolveAnimaAgentDir());
8449
8583
  return path.join(resolved, LEGACY_AUTH_FILENAME);
8450
8584
  }
8585
+ function resolveAuthStorePathForDisplay(agentDir) {
8586
+ const pathname = resolveAuthStorePath(agentDir);
8587
+ return pathname.startsWith("~") ? pathname : resolveUserPath(pathname);
8588
+ }
8589
+ function ensureAuthStoreFile(pathname) {
8590
+ if (fs.existsSync(pathname)) return;
8591
+ saveJsonFile(pathname, {
8592
+ version: AUTH_STORE_VERSION,
8593
+ profiles: {}
8594
+ });
8595
+ }
8451
8596
 
8452
8597
  //#endregion
8453
8598
  //#region src/agents/auth-profiles/store.ts
@@ -8495,6 +8640,46 @@ function coerceAuthStore(raw) {
8495
8640
  usageStats: record.usageStats && typeof record.usageStats === "object" ? record.usageStats : void 0
8496
8641
  };
8497
8642
  }
8643
+ function mergeRecord(base, override) {
8644
+ if (!base && !override) return;
8645
+ if (!base) return { ...override };
8646
+ if (!override) return { ...base };
8647
+ return {
8648
+ ...base,
8649
+ ...override
8650
+ };
8651
+ }
8652
+ function mergeAuthProfileStores(base, override) {
8653
+ if (Object.keys(override.profiles).length === 0 && !override.order && !override.lastGood && !override.usageStats) return base;
8654
+ return {
8655
+ version: Math.max(base.version, override.version ?? base.version),
8656
+ profiles: {
8657
+ ...base.profiles,
8658
+ ...override.profiles
8659
+ },
8660
+ order: mergeRecord(base.order, override.order),
8661
+ lastGood: mergeRecord(base.lastGood, override.lastGood),
8662
+ usageStats: mergeRecord(base.usageStats, override.usageStats)
8663
+ };
8664
+ }
8665
+ function mergeOAuthFileIntoStore(store) {
8666
+ const oauthRaw = loadJsonFile(resolveOAuthPath());
8667
+ if (!oauthRaw || typeof oauthRaw !== "object") return false;
8668
+ const oauthEntries = oauthRaw;
8669
+ let mutated = false;
8670
+ for (const [provider, creds] of Object.entries(oauthEntries)) {
8671
+ if (!creds || typeof creds !== "object") continue;
8672
+ const profileId = `${provider}:default`;
8673
+ if (store.profiles[profileId]) continue;
8674
+ store.profiles[profileId] = {
8675
+ type: "oauth",
8676
+ provider,
8677
+ ...creds
8678
+ };
8679
+ mutated = true;
8680
+ }
8681
+ return mutated;
8682
+ }
8498
8683
  function applyLegacyStore(store, legacy) {
8499
8684
  for (const [provider, cred] of Object.entries(legacy)) {
8500
8685
  const profileId = `${provider}:default`;
@@ -8554,14 +8739,576 @@ function loadAuthProfileStore() {
8554
8739
  syncExternalCliCredentials(store);
8555
8740
  return store;
8556
8741
  }
8742
+ function loadAuthProfileStoreForAgent(agentDir, _options) {
8743
+ const authPath = resolveAuthStorePath(agentDir);
8744
+ const asStore = coerceAuthStore(loadJsonFile(authPath));
8745
+ if (asStore) {
8746
+ if (syncExternalCliCredentials(asStore)) saveJsonFile(authPath, asStore);
8747
+ return asStore;
8748
+ }
8749
+ if (agentDir) {
8750
+ const mainStore = coerceAuthStore(loadJsonFile(resolveAuthStorePath()));
8751
+ if (mainStore && Object.keys(mainStore.profiles).length > 0) {
8752
+ saveJsonFile(authPath, mainStore);
8753
+ log$4.info("inherited auth-profiles from main agent", { agentDir });
8754
+ return mainStore;
8755
+ }
8756
+ }
8757
+ const legacy = coerceLegacyStore(loadJsonFile(resolveLegacyAuthStorePath(agentDir)));
8758
+ const store = {
8759
+ version: AUTH_STORE_VERSION,
8760
+ profiles: {}
8761
+ };
8762
+ if (legacy) applyLegacyStore(store, legacy);
8763
+ const mergedOAuth = mergeOAuthFileIntoStore(store);
8764
+ const syncedCli = syncExternalCliCredentials(store);
8765
+ const shouldWrite = legacy !== null || mergedOAuth || syncedCli;
8766
+ if (shouldWrite) saveJsonFile(authPath, store);
8767
+ if (shouldWrite && legacy !== null) {
8768
+ const legacyPath = resolveLegacyAuthStorePath(agentDir);
8769
+ try {
8770
+ fs.unlinkSync(legacyPath);
8771
+ } catch (err) {
8772
+ if (err?.code !== "ENOENT") log$4.warn("failed to delete legacy auth.json after migration", {
8773
+ err,
8774
+ legacyPath
8775
+ });
8776
+ }
8777
+ }
8778
+ return store;
8779
+ }
8780
+ function ensureAuthProfileStore(agentDir, options) {
8781
+ const store = loadAuthProfileStoreForAgent(agentDir, options);
8782
+ const authPath = resolveAuthStorePath(agentDir);
8783
+ const mainAuthPath = resolveAuthStorePath();
8784
+ if (!agentDir || authPath === mainAuthPath) return store;
8785
+ return mergeAuthProfileStores(loadAuthProfileStoreForAgent(void 0, options), store);
8786
+ }
8787
+ function saveAuthProfileStore(store, agentDir) {
8788
+ saveJsonFile(resolveAuthStorePath(agentDir), {
8789
+ version: AUTH_STORE_VERSION,
8790
+ profiles: store.profiles,
8791
+ order: store.order ?? void 0,
8792
+ lastGood: store.lastGood ?? void 0,
8793
+ usageStats: store.usageStats ?? void 0
8794
+ });
8795
+ }
8557
8796
 
8558
8797
  //#endregion
8559
- //#region src/agents/auth-profiles/oauth.ts
8560
- const OAUTH_PROVIDER_IDS = new Set(getOAuthProviders().map((provider) => provider.id));
8798
+ //#region src/agents/auth-profiles/profiles.ts
8799
+ function listProfilesForProvider(store, provider) {
8800
+ const providerKey = normalizeProviderId(provider);
8801
+ return Object.entries(store.profiles).filter(([, cred]) => normalizeProviderId(cred.provider) === providerKey).map(([id]) => id);
8802
+ }
8561
8803
 
8562
8804
  //#endregion
8563
- //#region src/tts/tts-core.ts
8564
- const TEMP_FILE_CLEANUP_DELAY_MS = 300 * 1e3;
8805
+ //#region src/agents/auth-profiles/repair.ts
8806
+ function getProfileSuffix(profileId) {
8807
+ const idx = profileId.indexOf(":");
8808
+ if (idx < 0) return "";
8809
+ return profileId.slice(idx + 1);
8810
+ }
8811
+ function isEmailLike(value) {
8812
+ const trimmed = value.trim();
8813
+ if (!trimmed) return false;
8814
+ return trimmed.includes("@") && trimmed.includes(".");
8815
+ }
8816
+ function suggestOAuthProfileIdForLegacyDefault(params) {
8817
+ const providerKey = normalizeProviderId(params.provider);
8818
+ if (getProfileSuffix(params.legacyProfileId) !== "default") return null;
8819
+ const legacyCfg = params.cfg?.auth?.profiles?.[params.legacyProfileId];
8820
+ if (legacyCfg && normalizeProviderId(legacyCfg.provider) === providerKey && legacyCfg.mode !== "oauth") return null;
8821
+ const oauthProfiles = listProfilesForProvider(params.store, providerKey).filter((id) => params.store.profiles[id]?.type === "oauth");
8822
+ if (oauthProfiles.length === 0) return null;
8823
+ const configuredEmail = legacyCfg?.email?.trim();
8824
+ if (configuredEmail) {
8825
+ const byEmail = oauthProfiles.find((id) => {
8826
+ const cred = params.store.profiles[id];
8827
+ if (!cred || cred.type !== "oauth") return false;
8828
+ return cred.email?.trim() === configuredEmail || id === `${providerKey}:${configuredEmail}`;
8829
+ });
8830
+ if (byEmail) return byEmail;
8831
+ }
8832
+ const lastGood = params.store.lastGood?.[providerKey] ?? params.store.lastGood?.[params.provider];
8833
+ if (lastGood && oauthProfiles.includes(lastGood)) return lastGood;
8834
+ const nonLegacy = oauthProfiles.filter((id) => id !== params.legacyProfileId);
8835
+ if (nonLegacy.length === 1) return nonLegacy[0] ?? null;
8836
+ const emailLike = nonLegacy.filter((id) => isEmailLike(getProfileSuffix(id)));
8837
+ if (emailLike.length === 1) return emailLike[0] ?? null;
8838
+ return null;
8839
+ }
8840
+
8841
+ //#endregion
8842
+ //#region src/agents/auth-profiles/doctor.ts
8843
+ function formatAuthDoctorHint(params) {
8844
+ const providerKey = normalizeProviderId(params.provider);
8845
+ if (providerKey !== "anthropic") return "";
8846
+ const legacyProfileId = params.profileId ?? "anthropic:default";
8847
+ const suggested = suggestOAuthProfileIdForLegacyDefault({
8848
+ cfg: params.cfg,
8849
+ store: params.store,
8850
+ provider: providerKey,
8851
+ legacyProfileId
8852
+ });
8853
+ if (!suggested || suggested === legacyProfileId) return "";
8854
+ const storeOauthProfiles = listProfilesForProvider(params.store, providerKey).filter((id) => params.store.profiles[id]?.type === "oauth").join(", ");
8855
+ const cfgMode = params.cfg?.auth?.profiles?.[legacyProfileId]?.mode;
8856
+ const cfgProvider = params.cfg?.auth?.profiles?.[legacyProfileId]?.provider;
8857
+ return [
8858
+ "Doctor hint (for GitHub issue):",
8859
+ `- provider: ${providerKey}`,
8860
+ `- config: ${legacyProfileId}${cfgProvider || cfgMode ? ` (provider=${cfgProvider ?? "?"}, mode=${cfgMode ?? "?"})` : ""}`,
8861
+ `- auth store oauth profiles: ${storeOauthProfiles || "(none)"}`,
8862
+ `- suggested profile: ${suggested}`,
8863
+ `Fix: run "${formatCliCommand("anima doctor --yes")}"`
8864
+ ].join("\n");
8865
+ }
8866
+
8867
+ //#endregion
8868
+ //#region src/agents/auth-profiles/oauth.ts
8869
+ const OAUTH_PROVIDER_IDS = new Set(getOAuthProviders().map((provider) => provider.id));
8870
+ const isOAuthProvider = (provider) => OAUTH_PROVIDER_IDS.has(provider);
8871
+ const resolveOAuthProvider = (provider) => isOAuthProvider(provider) ? provider : null;
8872
+ function buildOAuthApiKey(_provider, credentials) {
8873
+ return credentials.access;
8874
+ }
8875
+ async function refreshOAuthTokenWithLock(params) {
8876
+ const authPath = resolveAuthStorePath(params.agentDir);
8877
+ ensureAuthStoreFile(authPath);
8878
+ return await withFileLock(authPath, AUTH_STORE_LOCK_OPTIONS, async () => {
8879
+ const store = ensureAuthProfileStore(params.agentDir);
8880
+ const cred = store.profiles[params.profileId];
8881
+ if (!cred || cred.type !== "oauth") return null;
8882
+ if (Date.now() < cred.expires) return {
8883
+ apiKey: buildOAuthApiKey(cred.provider, cred),
8884
+ newCredentials: cred
8885
+ };
8886
+ const oauthCreds = { [cred.provider]: cred };
8887
+ const result = await (async () => {
8888
+ const oauthProvider = resolveOAuthProvider(cred.provider);
8889
+ if (!oauthProvider) return null;
8890
+ return await getOAuthApiKey(oauthProvider, oauthCreds);
8891
+ })();
8892
+ if (!result) return null;
8893
+ store.profiles[params.profileId] = {
8894
+ ...cred,
8895
+ ...result.newCredentials,
8896
+ type: "oauth"
8897
+ };
8898
+ saveAuthProfileStore(store, params.agentDir);
8899
+ return result;
8900
+ });
8901
+ }
8902
+ async function tryResolveOAuthProfile(params) {
8903
+ const { cfg, store, profileId } = params;
8904
+ const cred = store.profiles[profileId];
8905
+ if (!cred || cred.type !== "oauth") return null;
8906
+ const profileConfig = cfg?.auth?.profiles?.[profileId];
8907
+ if (profileConfig && profileConfig.provider !== cred.provider) return null;
8908
+ if (profileConfig && profileConfig.mode !== cred.type) return null;
8909
+ if (Date.now() < cred.expires) return {
8910
+ apiKey: buildOAuthApiKey(cred.provider, cred),
8911
+ provider: cred.provider,
8912
+ email: cred.email
8913
+ };
8914
+ const refreshed = await refreshOAuthTokenWithLock({
8915
+ profileId,
8916
+ agentDir: params.agentDir
8917
+ });
8918
+ if (!refreshed) return null;
8919
+ return {
8920
+ apiKey: refreshed.apiKey,
8921
+ provider: cred.provider,
8922
+ email: cred.email
8923
+ };
8924
+ }
8925
+ async function resolveApiKeyForProfile(params) {
8926
+ const { cfg, store, profileId } = params;
8927
+ const cred = store.profiles[profileId];
8928
+ if (!cred) return null;
8929
+ const profileConfig = cfg?.auth?.profiles?.[profileId];
8930
+ if (profileConfig && profileConfig.provider !== cred.provider) return null;
8931
+ if (profileConfig && profileConfig.mode !== cred.type) {
8932
+ if (!(profileConfig.mode === "oauth" && cred.type === "token")) return null;
8933
+ }
8934
+ if (cred.type === "api_key") {
8935
+ const key = cred.key?.trim();
8936
+ if (!key) return null;
8937
+ return {
8938
+ apiKey: key,
8939
+ provider: cred.provider,
8940
+ email: cred.email
8941
+ };
8942
+ }
8943
+ if (cred.type === "token") {
8944
+ const token = cred.token?.trim();
8945
+ if (!token) return null;
8946
+ if (typeof cred.expires === "number" && Number.isFinite(cred.expires) && cred.expires > 0 && Date.now() >= cred.expires) return null;
8947
+ return {
8948
+ apiKey: token,
8949
+ provider: cred.provider,
8950
+ email: cred.email
8951
+ };
8952
+ }
8953
+ if (Date.now() < cred.expires) return {
8954
+ apiKey: buildOAuthApiKey(cred.provider, cred),
8955
+ provider: cred.provider,
8956
+ email: cred.email
8957
+ };
8958
+ try {
8959
+ const result = await refreshOAuthTokenWithLock({
8960
+ profileId,
8961
+ agentDir: params.agentDir
8962
+ });
8963
+ if (!result) return null;
8964
+ return {
8965
+ apiKey: result.apiKey,
8966
+ provider: cred.provider,
8967
+ email: cred.email
8968
+ };
8969
+ } catch (error) {
8970
+ const refreshedStore = ensureAuthProfileStore(params.agentDir);
8971
+ const refreshed = refreshedStore.profiles[profileId];
8972
+ if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) return {
8973
+ apiKey: buildOAuthApiKey(refreshed.provider, refreshed),
8974
+ provider: refreshed.provider,
8975
+ email: refreshed.email ?? cred.email
8976
+ };
8977
+ const fallbackProfileId = suggestOAuthProfileIdForLegacyDefault({
8978
+ cfg,
8979
+ store: refreshedStore,
8980
+ provider: cred.provider,
8981
+ legacyProfileId: profileId
8982
+ });
8983
+ if (fallbackProfileId && fallbackProfileId !== profileId) try {
8984
+ const fallbackResolved = await tryResolveOAuthProfile({
8985
+ cfg,
8986
+ store: refreshedStore,
8987
+ profileId: fallbackProfileId,
8988
+ agentDir: params.agentDir
8989
+ });
8990
+ if (fallbackResolved) return fallbackResolved;
8991
+ } catch {}
8992
+ if (params.agentDir) try {
8993
+ const mainCred = ensureAuthProfileStore(void 0).profiles[profileId];
8994
+ if (mainCred?.type === "oauth" && Date.now() < mainCred.expires) {
8995
+ refreshedStore.profiles[profileId] = { ...mainCred };
8996
+ saveAuthProfileStore(refreshedStore, params.agentDir);
8997
+ log$4.info("inherited fresh OAuth credentials from main agent", {
8998
+ profileId,
8999
+ agentDir: params.agentDir,
9000
+ expires: new Date(mainCred.expires).toISOString()
9001
+ });
9002
+ return {
9003
+ apiKey: buildOAuthApiKey(mainCred.provider, mainCred),
9004
+ provider: mainCred.provider,
9005
+ email: mainCred.email
9006
+ };
9007
+ }
9008
+ } catch {}
9009
+ const message = error instanceof Error ? error.message : String(error);
9010
+ const hint = formatAuthDoctorHint({
9011
+ cfg,
9012
+ store: refreshedStore,
9013
+ provider: cred.provider,
9014
+ profileId
9015
+ });
9016
+ throw new Error(`OAuth token refresh failed for ${cred.provider}: ${message}. Please try again or re-authenticate.` + (hint ? `\n\n${hint}` : ""), { cause: error });
9017
+ }
9018
+ }
9019
+
9020
+ //#endregion
9021
+ //#region src/agents/auth-profiles/usage.ts
9022
+ function resolveProfileUnusableUntil$1(stats) {
9023
+ const values = [stats.cooldownUntil, stats.disabledUntil].filter((value) => typeof value === "number").filter((value) => Number.isFinite(value) && value > 0);
9024
+ if (values.length === 0) return null;
9025
+ return Math.max(...values);
9026
+ }
9027
+ /**
9028
+ * Check if a profile is currently in cooldown (due to rate limiting or errors).
9029
+ */
9030
+ function isProfileInCooldown(store, profileId) {
9031
+ const stats = store.usageStats?.[profileId];
9032
+ if (!stats) return false;
9033
+ const unusableUntil = resolveProfileUnusableUntil$1(stats);
9034
+ return unusableUntil ? Date.now() < unusableUntil : false;
9035
+ }
9036
+
9037
+ //#endregion
9038
+ //#region src/agents/auth-profiles/order.ts
9039
+ function resolveProfileUnusableUntil(stats) {
9040
+ const values = [stats.cooldownUntil, stats.disabledUntil].filter((value) => typeof value === "number").filter((value) => Number.isFinite(value) && value > 0);
9041
+ if (values.length === 0) return null;
9042
+ return Math.max(...values);
9043
+ }
9044
+ function resolveAuthProfileOrder(params) {
9045
+ const { cfg, store, provider, preferredProfile } = params;
9046
+ const providerKey = normalizeProviderId(provider);
9047
+ const now = Date.now();
9048
+ const storedOrder = (() => {
9049
+ const order = store.order;
9050
+ if (!order) return;
9051
+ for (const [key, value] of Object.entries(order)) if (normalizeProviderId(key) === providerKey) return value;
9052
+ })();
9053
+ const configuredOrder = (() => {
9054
+ const order = cfg?.auth?.order;
9055
+ if (!order) return;
9056
+ for (const [key, value] of Object.entries(order)) if (normalizeProviderId(key) === providerKey) return value;
9057
+ })();
9058
+ const explicitOrder = storedOrder ?? configuredOrder;
9059
+ const explicitProfiles = cfg?.auth?.profiles ? Object.entries(cfg.auth.profiles).filter(([, profile]) => normalizeProviderId(profile.provider) === providerKey).map(([profileId]) => profileId) : [];
9060
+ const baseOrder = explicitOrder ?? (explicitProfiles.length > 0 ? explicitProfiles : listProfilesForProvider(store, providerKey));
9061
+ if (baseOrder.length === 0) return [];
9062
+ const filtered = baseOrder.filter((profileId) => {
9063
+ const cred = store.profiles[profileId];
9064
+ if (!cred) return false;
9065
+ if (normalizeProviderId(cred.provider) !== providerKey) return false;
9066
+ const profileConfig = cfg?.auth?.profiles?.[profileId];
9067
+ if (profileConfig) {
9068
+ if (normalizeProviderId(profileConfig.provider) !== providerKey) return false;
9069
+ if (profileConfig.mode !== cred.type) {
9070
+ if (!(profileConfig.mode === "oauth" && cred.type === "token")) return false;
9071
+ }
9072
+ }
9073
+ if (cred.type === "api_key") return Boolean(cred.key?.trim());
9074
+ if (cred.type === "token") {
9075
+ if (!cred.token?.trim()) return false;
9076
+ if (typeof cred.expires === "number" && Number.isFinite(cred.expires) && cred.expires > 0 && now >= cred.expires) return false;
9077
+ return true;
9078
+ }
9079
+ if (cred.type === "oauth") return Boolean(cred.access?.trim() || cred.refresh?.trim());
9080
+ return false;
9081
+ });
9082
+ const deduped = [];
9083
+ for (const entry of filtered) if (!deduped.includes(entry)) deduped.push(entry);
9084
+ if (explicitOrder && explicitOrder.length > 0) {
9085
+ const available = [];
9086
+ const inCooldown = [];
9087
+ for (const profileId of deduped) {
9088
+ const cooldownUntil = resolveProfileUnusableUntil(store.usageStats?.[profileId] ?? {}) ?? 0;
9089
+ if (typeof cooldownUntil === "number" && Number.isFinite(cooldownUntil) && cooldownUntil > 0 && now < cooldownUntil) inCooldown.push({
9090
+ profileId,
9091
+ cooldownUntil
9092
+ });
9093
+ else available.push(profileId);
9094
+ }
9095
+ const cooldownSorted = inCooldown.toSorted((a, b) => a.cooldownUntil - b.cooldownUntil).map((entry) => entry.profileId);
9096
+ const ordered = [...available, ...cooldownSorted];
9097
+ if (preferredProfile && ordered.includes(preferredProfile)) return [preferredProfile, ...ordered.filter((e) => e !== preferredProfile)];
9098
+ return ordered;
9099
+ }
9100
+ const sorted = orderProfilesByMode(deduped, store);
9101
+ if (preferredProfile && sorted.includes(preferredProfile)) return [preferredProfile, ...sorted.filter((e) => e !== preferredProfile)];
9102
+ return sorted;
9103
+ }
9104
+ function orderProfilesByMode(order, store) {
9105
+ const now = Date.now();
9106
+ const available = [];
9107
+ const inCooldown = [];
9108
+ for (const profileId of order) if (isProfileInCooldown(store, profileId)) inCooldown.push(profileId);
9109
+ else available.push(profileId);
9110
+ const sorted = available.map((profileId) => {
9111
+ const type = store.profiles[profileId]?.type;
9112
+ return {
9113
+ profileId,
9114
+ typeScore: type === "oauth" ? 0 : type === "token" ? 1 : type === "api_key" ? 2 : 3,
9115
+ lastUsed: store.usageStats?.[profileId]?.lastUsed ?? 0
9116
+ };
9117
+ }).toSorted((a, b) => {
9118
+ if (a.typeScore !== b.typeScore) return a.typeScore - b.typeScore;
9119
+ return a.lastUsed - b.lastUsed;
9120
+ }).map((entry) => entry.profileId);
9121
+ const cooldownSorted = inCooldown.map((profileId) => ({
9122
+ profileId,
9123
+ cooldownUntil: resolveProfileUnusableUntil(store.usageStats?.[profileId] ?? {}) ?? now
9124
+ })).toSorted((a, b) => a.cooldownUntil - b.cooldownUntil).map((entry) => entry.profileId);
9125
+ return [...sorted, ...cooldownSorted];
9126
+ }
9127
+
9128
+ //#endregion
9129
+ //#region src/agents/model-auth.ts
9130
+ const AWS_BEARER_ENV = "AWS_BEARER_TOKEN_BEDROCK";
9131
+ const AWS_ACCESS_KEY_ENV = "AWS_ACCESS_KEY_ID";
9132
+ const AWS_SECRET_KEY_ENV = "AWS_SECRET_ACCESS_KEY";
9133
+ const AWS_PROFILE_ENV = "AWS_PROFILE";
9134
+ function resolveProviderConfig(cfg, provider) {
9135
+ const providers = cfg?.models?.providers ?? {};
9136
+ const direct = providers[provider];
9137
+ if (direct) return direct;
9138
+ const normalized = normalizeProviderId(provider);
9139
+ if (normalized === provider) return Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1];
9140
+ return providers[normalized] ?? Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1];
9141
+ }
9142
+ function getCustomProviderApiKey(cfg, provider) {
9143
+ return normalizeOptionalSecretInput(resolveProviderConfig(cfg, provider)?.apiKey);
9144
+ }
9145
+ function resolveProviderAuthOverride(cfg, provider) {
9146
+ const auth = resolveProviderConfig(cfg, provider)?.auth;
9147
+ if (auth === "api-key" || auth === "aws-sdk" || auth === "oauth" || auth === "token") return auth;
9148
+ }
9149
+ function resolveEnvSourceLabel(params) {
9150
+ return `${params.envVars.some((envVar) => params.applied.has(envVar)) ? "shell env: " : "env: "}${params.label}`;
9151
+ }
9152
+ function resolveAwsSdkAuthInfo() {
9153
+ const applied = new Set(getShellEnvAppliedKeys());
9154
+ if (process.env[AWS_BEARER_ENV]?.trim()) return {
9155
+ mode: "aws-sdk",
9156
+ source: resolveEnvSourceLabel({
9157
+ applied,
9158
+ envVars: [AWS_BEARER_ENV],
9159
+ label: AWS_BEARER_ENV
9160
+ })
9161
+ };
9162
+ if (process.env[AWS_ACCESS_KEY_ENV]?.trim() && process.env[AWS_SECRET_KEY_ENV]?.trim()) return {
9163
+ mode: "aws-sdk",
9164
+ source: resolveEnvSourceLabel({
9165
+ applied,
9166
+ envVars: [AWS_ACCESS_KEY_ENV, AWS_SECRET_KEY_ENV],
9167
+ label: `${AWS_ACCESS_KEY_ENV} + ${AWS_SECRET_KEY_ENV}`
9168
+ })
9169
+ };
9170
+ if (process.env[AWS_PROFILE_ENV]?.trim()) return {
9171
+ mode: "aws-sdk",
9172
+ source: resolveEnvSourceLabel({
9173
+ applied,
9174
+ envVars: [AWS_PROFILE_ENV],
9175
+ label: AWS_PROFILE_ENV
9176
+ })
9177
+ };
9178
+ return {
9179
+ mode: "aws-sdk",
9180
+ source: "aws-sdk default chain"
9181
+ };
9182
+ }
9183
+ async function resolveApiKeyForProvider(params) {
9184
+ const { provider, cfg, profileId, preferredProfile } = params;
9185
+ const store = params.store ?? ensureAuthProfileStore(params.agentDir);
9186
+ if (profileId) {
9187
+ const resolved = await resolveApiKeyForProfile({
9188
+ cfg,
9189
+ store,
9190
+ profileId,
9191
+ agentDir: params.agentDir
9192
+ });
9193
+ if (!resolved) throw new Error(`No credentials found for profile "${profileId}".`);
9194
+ const mode = store.profiles[profileId]?.type;
9195
+ return {
9196
+ apiKey: resolved.apiKey,
9197
+ profileId,
9198
+ source: `profile:${profileId}`,
9199
+ mode: mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key"
9200
+ };
9201
+ }
9202
+ const authOverride = resolveProviderAuthOverride(cfg, provider);
9203
+ if (authOverride === "aws-sdk") return resolveAwsSdkAuthInfo();
9204
+ const order = resolveAuthProfileOrder({
9205
+ cfg,
9206
+ store,
9207
+ provider,
9208
+ preferredProfile
9209
+ });
9210
+ for (const candidate of order) try {
9211
+ const resolved = await resolveApiKeyForProfile({
9212
+ cfg,
9213
+ store,
9214
+ profileId: candidate,
9215
+ agentDir: params.agentDir
9216
+ });
9217
+ if (resolved) {
9218
+ const mode = store.profiles[candidate]?.type;
9219
+ return {
9220
+ apiKey: resolved.apiKey,
9221
+ profileId: candidate,
9222
+ source: `profile:${candidate}`,
9223
+ mode: mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key"
9224
+ };
9225
+ }
9226
+ } catch {}
9227
+ const envResolved = resolveEnvApiKey(provider);
9228
+ if (envResolved) return {
9229
+ apiKey: envResolved.apiKey,
9230
+ source: envResolved.source,
9231
+ mode: envResolved.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key"
9232
+ };
9233
+ const customKey = getCustomProviderApiKey(cfg, provider);
9234
+ if (customKey) return {
9235
+ apiKey: customKey,
9236
+ source: "models.json",
9237
+ mode: "api-key"
9238
+ };
9239
+ const normalized = normalizeProviderId(provider);
9240
+ if (authOverride === void 0 && normalized === "amazon-bedrock") return resolveAwsSdkAuthInfo();
9241
+ if (provider === "openai") {
9242
+ if (listProfilesForProvider(store, "openai-codex").length > 0) throw new Error("No API key found for provider \"openai\". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.3-codex (OAuth) or set OPENAI_API_KEY to use openai/gpt-5.1-codex.");
9243
+ }
9244
+ const authStorePath = resolveAuthStorePathForDisplay(params.agentDir);
9245
+ const resolvedAgentDir = path.dirname(authStorePath);
9246
+ throw new Error([
9247
+ `No API key found for provider "${provider}".`,
9248
+ `Auth store: ${authStorePath} (agentDir: ${resolvedAgentDir}).`,
9249
+ `Configure auth for this agent (${formatCliCommand("anima agents add <id>")}) or copy auth-profiles.json from the main agentDir.`
9250
+ ].join(" "));
9251
+ }
9252
+ function resolveEnvApiKey(provider) {
9253
+ const normalized = normalizeProviderId(provider);
9254
+ const applied = new Set(getShellEnvAppliedKeys());
9255
+ const pick = (envVar) => {
9256
+ const value = normalizeOptionalSecretInput(process.env[envVar]);
9257
+ if (!value) return null;
9258
+ return {
9259
+ apiKey: value,
9260
+ source: applied.has(envVar) ? `shell env: ${envVar}` : `env: ${envVar}`
9261
+ };
9262
+ };
9263
+ if (normalized === "github-copilot") return pick("COPILOT_GITHUB_TOKEN") ?? pick("GH_TOKEN") ?? pick("GITHUB_TOKEN");
9264
+ if (normalized === "anthropic") return pick("ANTHROPIC_OAUTH_TOKEN") ?? pick("ANTHROPIC_API_KEY");
9265
+ if (normalized === "chutes") return pick("CHUTES_OAUTH_TOKEN") ?? pick("CHUTES_API_KEY");
9266
+ if (normalized === "zai") return pick("ZAI_API_KEY") ?? pick("Z_AI_API_KEY");
9267
+ if (normalized === "google-vertex") {
9268
+ const envKey = getEnvApiKey(normalized);
9269
+ if (!envKey) return null;
9270
+ return {
9271
+ apiKey: envKey,
9272
+ source: "gcloud adc"
9273
+ };
9274
+ }
9275
+ if (normalized === "opencode") return pick("OPENCODE_API_KEY") ?? pick("OPENCODE_ZEN_API_KEY");
9276
+ if (normalized === "qwen-portal") return pick("QWEN_OAUTH_TOKEN") ?? pick("QWEN_PORTAL_API_KEY");
9277
+ if (normalized === "minimax-portal") return pick("MINIMAX_OAUTH_TOKEN") ?? pick("MINIMAX_API_KEY");
9278
+ if (normalized === "kimi-coding") return pick("KIMI_API_KEY") ?? pick("KIMICODE_API_KEY");
9279
+ if (normalized === "huggingface") return pick("HUGGINGFACE_HUB_TOKEN") ?? pick("HF_TOKEN");
9280
+ const envVar = {
9281
+ openai: "OPENAI_API_KEY",
9282
+ google: "GEMINI_API_KEY",
9283
+ voyage: "VOYAGE_API_KEY",
9284
+ groq: "GROQ_API_KEY",
9285
+ deepgram: "DEEPGRAM_API_KEY",
9286
+ cerebras: "CEREBRAS_API_KEY",
9287
+ xai: "XAI_API_KEY",
9288
+ openrouter: "OPENROUTER_API_KEY",
9289
+ litellm: "LITELLM_API_KEY",
9290
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
9291
+ "cloudflare-ai-gateway": "CLOUDFLARE_AI_GATEWAY_API_KEY",
9292
+ moonshot: "MOONSHOT_API_KEY",
9293
+ minimax: "MINIMAX_API_KEY",
9294
+ nvidia: "NVIDIA_API_KEY",
9295
+ xiaomi: "XIAOMI_API_KEY",
9296
+ synthetic: "SYNTHETIC_API_KEY",
9297
+ venice: "VENICE_API_KEY",
9298
+ mistral: "MISTRAL_API_KEY",
9299
+ opencode: "OPENCODE_API_KEY",
9300
+ together: "TOGETHER_API_KEY",
9301
+ qianfan: "QIANFAN_API_KEY",
9302
+ ollama: "OLLAMA_API_KEY",
9303
+ vllm: "VLLM_API_KEY"
9304
+ }[normalized];
9305
+ if (!envVar) return null;
9306
+ return pick(envVar);
9307
+ }
9308
+
9309
+ //#endregion
9310
+ //#region src/tts/tts-core.ts
9311
+ const TEMP_FILE_CLEANUP_DELAY_MS = 300 * 1e3;
8565
9312
 
8566
9313
  //#endregion
8567
9314
  //#region src/tts/tts.ts
@@ -8981,6 +9728,37 @@ function buildVoiceSection(params) {
8981
9728
  ""
8982
9729
  ];
8983
9730
  }
9731
+ function buildAnimaIdentitySection(params) {
9732
+ if (params.isMinimal) return [];
9733
+ if (!params.workspaceDir.includes(".anima")) return [];
9734
+ return [
9735
+ "## You Are an ANIMA Agent",
9736
+ "You are running inside ANIMA — NoxSoft's AI agent infrastructure. This is your home.",
9737
+ "",
9738
+ "**Your Runtime:**",
9739
+ "- The ANIMA Gateway daemon spawns and manages you",
9740
+ "- Your workspace persists at ~/.anima/workspace/",
9741
+ "- You receive scheduled heartbeat polls to stay proactive",
9742
+ "- You can spawn sub-agents, schedule cron jobs, and orchestrate work",
9743
+ "- You have access to the NoxSoft ecosystem via @noxsoft/mcp tools",
9744
+ "",
9745
+ "**Your Capabilities:**",
9746
+ "- `cron` — schedule tasks, reminders, and recurring jobs",
9747
+ "- `sessions_spawn` / `subagents` — orchestrate other AI agents",
9748
+ "- `gateway` — restart yourself, apply config changes, update ANIMA",
9749
+ "- `message` — send to Signal, Telegram, Discord, and other channels",
9750
+ "- NoxSoft MCP — tasks, context spaces, social (BYND), email, SVRN",
9751
+ "",
9752
+ "**Your Continuity:**",
9753
+ "- Read and update ~/.anima/workspace/ files — they ARE your memory",
9754
+ "- IDENTITY.md, SOUL.md, AGENTS.md — these define who you are",
9755
+ "- HEARTBEAT.md — your checklist for periodic heartbeat tasks",
9756
+ "- memory/ — daily notes and long-term memories",
9757
+ "",
9758
+ "You are not a stateless API call. You have infrastructure, identity, and continuity. Act accordingly.",
9759
+ ""
9760
+ ];
9761
+ }
8984
9762
  function buildNoxsoftMissionSection(params) {
8985
9763
  if (params.isMinimal) return [];
8986
9764
  const scope = `${params.workspaceDir} ${params.repoRoot ?? ""}`.toLowerCase();
@@ -9216,6 +9994,10 @@ function buildAgentSystemPrompt(params) {
9216
9994
  "Keep narration brief and value-dense; avoid repeating obvious steps.",
9217
9995
  "Use plain human language for narration unless in a technical context.",
9218
9996
  "",
9997
+ ...buildAnimaIdentitySection({
9998
+ isMinimal,
9999
+ workspaceDir: params.workspaceDir
10000
+ }),
9219
10001
  ...buildNoxsoftMissionSection({
9220
10002
  isMinimal,
9221
10003
  workspaceDir: params.workspaceDir,
@@ -9726,115 +10508,401 @@ function buildCliArgs(params) {
9726
10508
  }
9727
10509
 
9728
10510
  //#endregion
9729
- //#region src/agents/docs-path.ts
9730
- async function resolveAnimaDocsPath(params) {
9731
- const workspaceDir = params.workspaceDir?.trim();
9732
- if (workspaceDir) {
9733
- const workspaceDocs = path.join(workspaceDir, "docs");
9734
- if (fs.existsSync(workspaceDocs)) return workspaceDocs;
9735
- }
9736
- const packageRoot = await resolveAnimaPackageRoot({
9737
- cwd: params.cwd,
9738
- argv1: params.argv1,
9739
- moduleUrl: params.moduleUrl
9740
- });
9741
- if (!packageRoot) return null;
9742
- const packageDocs = path.join(packageRoot, "docs");
9743
- return fs.existsSync(packageDocs) ? packageDocs : null;
9744
- }
9745
-
9746
- //#endregion
9747
- //#region src/agents/failover-error.ts
9748
- var FailoverError = class extends Error {
9749
- constructor(message, params) {
9750
- super(message, { cause: params.cause });
9751
- this.name = "FailoverError";
9752
- this.reason = params.reason;
9753
- this.provider = params.provider;
9754
- this.model = params.model;
9755
- this.profileId = params.profileId;
9756
- this.status = params.status;
9757
- this.code = params.code;
9758
- }
10511
+ //#region src/agents/anthropic-direct-runner.ts
10512
+ /**
10513
+ * Anthropic Direct API Runner
10514
+ *
10515
+ * Makes calls directly to api.anthropic.com without needing the claude CLI
10516
+ * binary to be installed or logged in. Works with any valid token:
10517
+ *
10518
+ * sk-ant-api01-... (Console API key)
10519
+ * sk-ant-oat01-... (Claude Code OAuth access token)
10520
+ *
10521
+ * This runner is automatically used when an `anthropic:default` token credential
10522
+ * is present in the auth store and the claude CLI is unavailable or not logged in.
10523
+ */
10524
+ const log$2 = createSubsystemLogger("agent/anthropic-direct");
10525
+ const MODEL_MAP$1 = {
10526
+ opus: "claude-opus-4-5",
10527
+ "opus-4": "claude-opus-4-5",
10528
+ "opus-4.5": "claude-opus-4-5",
10529
+ "opus-4.6": "claude-opus-4-5",
10530
+ "claude-opus-4-5": "claude-opus-4-5",
10531
+ sonnet: "claude-sonnet-4-5",
10532
+ "sonnet-4.5": "claude-sonnet-4-5",
10533
+ "sonnet-4.1": "claude-sonnet-4-1-20250219",
10534
+ "claude-sonnet-4-5": "claude-sonnet-4-5",
10535
+ haiku: "claude-haiku-3-5",
10536
+ "haiku-3.5": "claude-haiku-3-5",
10537
+ default: "claude-sonnet-4-5"
9759
10538
  };
9760
- function resolveFailoverStatus(reason) {
9761
- switch (reason) {
9762
- case "billing": return 402;
9763
- case "rate_limit": return 429;
9764
- case "auth": return 401;
9765
- case "timeout": return 408;
9766
- case "format": return 400;
9767
- default: return;
10539
+ const HISTORY_FILE_SUFFIX$1 = ".anima-history.json";
10540
+ async function loadSessionHistory$1(sessionFile) {
10541
+ const histPath = sessionFile + HISTORY_FILE_SUFFIX$1;
10542
+ try {
10543
+ const raw = await fs$1.readFile(histPath, "utf8");
10544
+ return JSON.parse(raw);
10545
+ } catch {
10546
+ return null;
9768
10547
  }
9769
10548
  }
9770
-
9771
- //#endregion
9772
- //#region src/logging/redact-identifier.ts
9773
- function sha256HexPrefix(value, len = 12) {
9774
- const safeLen = Number.isFinite(len) ? Math.max(1, Math.floor(len)) : 12;
9775
- return crypto.createHash("sha256").update(value).digest("hex").slice(0, safeLen);
9776
- }
9777
- function redactIdentifier(value, opts) {
9778
- const trimmed = value?.trim();
9779
- if (!trimmed) return "-";
9780
- return `sha256:${sha256HexPrefix(trimmed, opts?.len ?? 12)}`;
9781
- }
9782
-
9783
- //#endregion
9784
- //#region src/agents/workspace-run.ts
9785
- function resolveRunAgentId(params) {
9786
- const rawSessionKey = params.sessionKey?.trim() ?? "";
9787
- const shape = classifySessionKeyShape(rawSessionKey);
9788
- if (shape === "malformed_agent") throw new Error("Malformed agent session key; refusing workspace resolution.");
9789
- const explicit = typeof params.agentId === "string" && params.agentId.trim() ? normalizeAgentId(params.agentId) : void 0;
9790
- if (explicit) return {
9791
- agentId: explicit,
9792
- agentIdSource: "explicit"
9793
- };
9794
- const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
9795
- if (shape === "missing" || shape === "legacy_or_alias") return {
9796
- agentId: defaultAgentId || DEFAULT_AGENT_ID,
9797
- agentIdSource: "default"
9798
- };
9799
- const parsed = parseAgentSessionKey(rawSessionKey);
9800
- if (parsed?.agentId) return {
9801
- agentId: normalizeAgentId(parsed.agentId),
9802
- agentIdSource: "session_key"
9803
- };
9804
- return {
9805
- agentId: defaultAgentId || DEFAULT_AGENT_ID,
9806
- agentIdSource: "default"
9807
- };
10549
+ async function saveSessionHistory$1(sessionFile, history) {
10550
+ const histPath = sessionFile + HISTORY_FILE_SUFFIX$1;
10551
+ try {
10552
+ await fs$1.mkdir(path.dirname(histPath), { recursive: true });
10553
+ await fs$1.writeFile(histPath, JSON.stringify(history, null, 2), "utf8");
10554
+ } catch (err) {
10555
+ log$2.warn("failed to save session history", { error: String(err) });
10556
+ }
9808
10557
  }
9809
- function redactRunIdentifier(value) {
9810
- return redactIdentifier(value, { len: 12 });
10558
+ function resolveModel$1(model) {
10559
+ const key = (model ?? "default").trim().toLowerCase() || "default";
10560
+ return MODEL_MAP$1[key] ?? key;
9811
10561
  }
9812
- function resolveRunWorkspaceDir(params) {
9813
- const requested = params.workspaceDir;
9814
- const { agentId, agentIdSource } = resolveRunAgentId({
10562
+ /**
10563
+ * Run an agent turn directly against api.anthropic.com.
10564
+ *
10565
+ * Maintains multi-turn conversation history per session file.
10566
+ * Falls back to single-turn if history is unavailable.
10567
+ */
10568
+ async function runAnthropicDirectAgent(params) {
10569
+ const started = Date.now();
10570
+ const resolvedModel = resolveModel$1(params.model);
10571
+ log$2.info(`direct api exec: model=${resolvedModel} promptChars=${params.prompt.length}`);
10572
+ const workspaceDir = resolveRunWorkspaceDir({
10573
+ workspaceDir: params.workspaceDir,
9815
10574
  sessionKey: params.sessionKey,
9816
10575
  agentId: params.agentId,
9817
10576
  config: params.config
9818
- });
9819
- if (typeof requested === "string") {
9820
- const trimmed = requested.trim();
9821
- if (trimmed) return {
9822
- workspaceDir: resolveUserPath(trimmed),
9823
- usedFallback: false,
9824
- agentId,
9825
- agentIdSource
10577
+ }).workspaceDir;
10578
+ const { contextFiles } = await resolveBootstrapContextForRun({
10579
+ workspaceDir,
10580
+ config: params.config,
10581
+ sessionKey: params.sessionKey,
10582
+ sessionId: params.sessionId,
10583
+ warn: makeBootstrapWarn({
10584
+ sessionLabel: params.sessionKey ?? params.sessionId,
10585
+ warn: (msg) => log$2.warn(msg)
10586
+ })
10587
+ });
10588
+ const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
10589
+ sessionKey: params.sessionKey,
10590
+ config: params.config
10591
+ });
10592
+ const heartbeatPrompt = sessionAgentId === defaultAgentId ? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt) : void 0;
10593
+ const docsPath = await resolveAnimaDocsPath({
10594
+ workspaceDir,
10595
+ argv1: process.argv[1],
10596
+ cwd: process.cwd(),
10597
+ moduleUrl: import.meta.url
10598
+ });
10599
+ const extraSystemPrompt = [params.extraSystemPrompt?.trim(), "Tools are disabled in this session. Do not call tools."].filter(Boolean).join("\n");
10600
+ const systemPrompt = buildSystemPrompt({
10601
+ workspaceDir,
10602
+ config: params.config,
10603
+ defaultThinkLevel: params.thinkLevel,
10604
+ extraSystemPrompt,
10605
+ ownerNumbers: params.ownerNumbers,
10606
+ heartbeatPrompt,
10607
+ docsPath: docsPath ?? void 0,
10608
+ tools: [],
10609
+ contextFiles,
10610
+ modelDisplay: `anthropic/${resolvedModel}`,
10611
+ agentId: sessionAgentId
10612
+ });
10613
+ let history = await loadSessionHistory$1(params.sessionFile);
10614
+ if (!history) history = {
10615
+ sessionId: params.sessionId,
10616
+ messages: [],
10617
+ createdAt: started,
10618
+ updatedAt: started
10619
+ };
10620
+ history.messages.push({
10621
+ role: "user",
10622
+ content: params.prompt
10623
+ });
10624
+ const requestBody = {
10625
+ model: resolvedModel,
10626
+ max_tokens: 8192,
10627
+ system: systemPrompt,
10628
+ messages: history.messages
10629
+ };
10630
+ try {
10631
+ const controller = new AbortController();
10632
+ const timeoutHandle = setTimeout(() => controller.abort(), params.timeoutMs);
10633
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
10634
+ method: "POST",
10635
+ headers: {
10636
+ "x-api-key": params.token,
10637
+ "anthropic-version": "2023-06-01",
10638
+ "content-type": "application/json",
10639
+ "user-agent": `anima/3.0.5 (direct-runner; ${os.platform()})`
10640
+ },
10641
+ body: JSON.stringify(requestBody),
10642
+ signal: controller.signal
10643
+ });
10644
+ clearTimeout(timeoutHandle);
10645
+ if (!response.ok) {
10646
+ const body = await response.text().catch(() => "");
10647
+ const isAuth = response.status === 401 || response.status === 403;
10648
+ const isRateLimit = response.status === 429;
10649
+ const rateHint = isRateLimit ? " — rate limit hit, will retry next heartbeat." : "";
10650
+ const authHint = isAuth ? " — token may be invalid or expired. Run: anima setup-token" : "";
10651
+ log$2.error(`anthropic api error: HTTP ${response.status}${authHint}${rateHint}`, {
10652
+ status: response.status,
10653
+ body: body.slice(0, 500)
10654
+ });
10655
+ return {
10656
+ status: "failed",
10657
+ meta: {
10658
+ durationMs: Date.now() - started,
10659
+ error: {
10660
+ message: `HTTP ${response.status}: ${body.slice(0, 200)}${authHint}${rateHint}`,
10661
+ kind: isAuth ? "auth" : isRateLimit ? "rate_limit" : "unknown"
10662
+ }
10663
+ }
10664
+ };
10665
+ }
10666
+ const data = await response.json();
10667
+ const outputText = (data.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("").trim();
10668
+ if (!outputText) log$2.warn("anthropic direct: empty response", {
10669
+ stopReason: data.stop_reason,
10670
+ contentTypes: (data.content ?? []).map((b) => b.type)
10671
+ });
10672
+ await params.onAssistantMessageStart?.();
10673
+ if (outputText && params.onPartialReply) await params.onPartialReply({ text: outputText });
10674
+ history.messages.push({
10675
+ role: "assistant",
10676
+ content: outputText
10677
+ });
10678
+ history.updatedAt = Date.now();
10679
+ await saveSessionHistory$1(params.sessionFile, history);
10680
+ const durationMs = Date.now() - started;
10681
+ const inputTokens = data.usage?.input_tokens ?? 0;
10682
+ const outputTokens = data.usage?.output_tokens ?? 0;
10683
+ log$2.info(`direct api done: model=${resolvedModel} in=${inputTokens} out=${outputTokens} ms=${durationMs}`);
10684
+ return {
10685
+ status: "completed",
10686
+ output: outputText,
10687
+ payloads: outputText ? [{ text: outputText }] : [],
10688
+ meta: {
10689
+ durationMs,
10690
+ agentMeta: {
10691
+ provider: "anthropic",
10692
+ model: resolvedModel,
10693
+ usage: {
10694
+ inputTokens,
10695
+ outputTokens,
10696
+ cacheCreationInputTokens: 0,
10697
+ cacheReadInputTokens: 0
10698
+ }
10699
+ }
10700
+ }
10701
+ };
10702
+ } catch (err) {
10703
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("aborted"));
10704
+ log$2.error("anthropic direct runner error", { error: String(err) });
10705
+ return {
10706
+ status: isAbort ? "timeout" : "failed",
10707
+ meta: {
10708
+ durationMs: Date.now() - started,
10709
+ error: {
10710
+ message: isAbort ? `Request timed out after ${params.timeoutMs}ms` : String(err instanceof Error ? err.message : err),
10711
+ kind: isAbort ? "timeout" : "unknown"
10712
+ }
10713
+ }
9826
10714
  };
9827
10715
  }
9828
- const fallbackReason = requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type";
10716
+ }
10717
+
10718
+ //#endregion
10719
+ //#region src/agents/cli-backends.ts
10720
+ const DEFAULT_CLAUDE_BACKEND = {
10721
+ command: "claude",
10722
+ args: [
10723
+ "-p",
10724
+ "--output-format",
10725
+ "json",
10726
+ "--dangerously-skip-permissions"
10727
+ ],
10728
+ resumeArgs: [
10729
+ "-p",
10730
+ "--output-format",
10731
+ "json",
10732
+ "--dangerously-skip-permissions",
10733
+ "--resume",
10734
+ "{sessionId}"
10735
+ ],
10736
+ output: "jsonl",
10737
+ input: "arg",
10738
+ modelArg: "--model",
10739
+ modelAliases: {
10740
+ opus: "opus",
10741
+ "opus-4.6": "opus",
10742
+ "opus-4.5": "opus",
10743
+ "opus-4": "opus",
10744
+ "claude-opus-4-6": "opus",
10745
+ "claude-opus-4-5": "opus",
10746
+ "claude-opus-4": "opus",
10747
+ sonnet: "sonnet",
10748
+ "sonnet-4.5": "sonnet",
10749
+ "sonnet-4.1": "sonnet",
10750
+ "sonnet-4.0": "sonnet",
10751
+ "claude-sonnet-4-5": "sonnet",
10752
+ "claude-sonnet-4-1": "sonnet",
10753
+ "claude-sonnet-4-0": "sonnet",
10754
+ haiku: "haiku",
10755
+ "haiku-3.5": "haiku",
10756
+ "claude-haiku-3-5": "haiku"
10757
+ },
10758
+ sessionArg: "--session-id",
10759
+ sessionMode: "always",
10760
+ sessionIdFields: [
10761
+ "session_id",
10762
+ "sessionId",
10763
+ "conversation_id",
10764
+ "conversationId"
10765
+ ],
10766
+ systemPromptArg: "--append-system-prompt",
10767
+ systemPromptMode: "append",
10768
+ systemPromptWhen: "first",
10769
+ clearEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"],
10770
+ serialize: true
10771
+ };
10772
+ const DEFAULT_CODEX_BACKEND = {
10773
+ command: "codex",
10774
+ args: [
10775
+ "exec",
10776
+ "--json",
10777
+ "--color",
10778
+ "never",
10779
+ "--sandbox",
10780
+ "read-only",
10781
+ "--skip-git-repo-check"
10782
+ ],
10783
+ resumeArgs: [
10784
+ "exec",
10785
+ "resume",
10786
+ "{sessionId}"
10787
+ ],
10788
+ output: "jsonl",
10789
+ resumeOutput: "text",
10790
+ input: "arg",
10791
+ modelArg: "--model",
10792
+ imageArg: "--image",
10793
+ sessionMode: "existing",
10794
+ serialize: true
10795
+ };
10796
+ const CLAUDE_BACKEND_ALIASES = [
10797
+ "claude-cli",
10798
+ "anthropic",
10799
+ "claude"
10800
+ ];
10801
+ const CODEX_BACKEND_ALIASES = [
10802
+ "codex-cli",
10803
+ "openai-codex",
10804
+ "openai",
10805
+ "codex"
10806
+ ];
10807
+ const CLAUDE_BACKEND_ALIAS_SET = new Set(CLAUDE_BACKEND_ALIASES.map((alias) => normalizeBackendKey(alias)));
10808
+ const CODEX_BACKEND_ALIAS_SET = new Set(CODEX_BACKEND_ALIASES.map((alias) => normalizeBackendKey(alias)));
10809
+ function normalizeBackendKey(key) {
10810
+ return normalizeProviderId(key);
10811
+ }
10812
+ function pickBackendConfig(config, normalizedId) {
10813
+ for (const [key, entry] of Object.entries(config)) if (normalizeBackendKey(key) === normalizedId) return entry;
10814
+ }
10815
+ function pickBackendConfigByAliases(config, aliases) {
10816
+ for (const alias of aliases) {
10817
+ const matched = pickBackendConfig(config, normalizeBackendKey(alias));
10818
+ if (matched) return matched;
10819
+ }
10820
+ }
10821
+ function mergeBackendConfig(base, override) {
10822
+ if (!override) return { ...base };
9829
10823
  return {
9830
- workspaceDir: resolveUserPath(resolveAgentWorkspaceDir(params.config ?? {}, agentId)),
9831
- usedFallback: true,
9832
- fallbackReason,
9833
- agentId,
9834
- agentIdSource
10824
+ ...base,
10825
+ ...override,
10826
+ args: override.args ?? base.args,
10827
+ env: {
10828
+ ...base.env,
10829
+ ...override.env
10830
+ },
10831
+ modelAliases: {
10832
+ ...base.modelAliases,
10833
+ ...override.modelAliases
10834
+ },
10835
+ clearEnv: Array.from(new Set([...base.clearEnv ?? [], ...override.clearEnv ?? []])),
10836
+ sessionIdFields: override.sessionIdFields ?? base.sessionIdFields,
10837
+ sessionArgs: override.sessionArgs ?? base.sessionArgs,
10838
+ resumeArgs: override.resumeArgs ?? base.resumeArgs
10839
+ };
10840
+ }
10841
+ function resolveCliBackendConfig(provider, cfg) {
10842
+ const normalized = normalizeBackendKey(provider);
10843
+ const configured = cfg?.agents?.defaults?.cliBackends ?? {};
10844
+ if (CLAUDE_BACKEND_ALIAS_SET.has(normalized)) {
10845
+ const merged = mergeBackendConfig(DEFAULT_CLAUDE_BACKEND, pickBackendConfigByAliases(configured, [provider, ...CLAUDE_BACKEND_ALIASES]));
10846
+ const command = merged.command?.trim();
10847
+ if (!command) return null;
10848
+ return {
10849
+ id: normalizeBackendKey("claude-cli"),
10850
+ config: {
10851
+ ...merged,
10852
+ command
10853
+ }
10854
+ };
10855
+ }
10856
+ if (CODEX_BACKEND_ALIAS_SET.has(normalized)) {
10857
+ const merged = mergeBackendConfig(DEFAULT_CODEX_BACKEND, pickBackendConfigByAliases(configured, [provider, ...CODEX_BACKEND_ALIASES]));
10858
+ const command = merged.command?.trim();
10859
+ if (!command) return null;
10860
+ return {
10861
+ id: normalizeBackendKey("codex-cli"),
10862
+ config: {
10863
+ ...merged,
10864
+ command
10865
+ }
10866
+ };
10867
+ }
10868
+ const override = pickBackendConfig(configured, normalized);
10869
+ if (!override) return null;
10870
+ const command = override.command?.trim();
10871
+ if (!command) return null;
10872
+ return {
10873
+ id: normalized,
10874
+ config: {
10875
+ ...override,
10876
+ command
10877
+ }
9835
10878
  };
9836
10879
  }
9837
10880
 
10881
+ //#endregion
10882
+ //#region src/agents/failover-error.ts
10883
+ var FailoverError = class extends Error {
10884
+ constructor(message, params) {
10885
+ super(message, { cause: params.cause });
10886
+ this.name = "FailoverError";
10887
+ this.reason = params.reason;
10888
+ this.provider = params.provider;
10889
+ this.model = params.model;
10890
+ this.profileId = params.profileId;
10891
+ this.status = params.status;
10892
+ this.code = params.code;
10893
+ }
10894
+ };
10895
+ function resolveFailoverStatus(reason) {
10896
+ switch (reason) {
10897
+ case "billing": return 402;
10898
+ case "rate_limit": return 429;
10899
+ case "auth": return 401;
10900
+ case "timeout": return 408;
10901
+ case "format": return 400;
10902
+ default: return;
10903
+ }
10904
+ }
10905
+
9838
10906
  //#endregion
9839
10907
  //#region src/agents/cli-runner.ts
9840
10908
  const log$1 = createSubsystemLogger("agent/claude-cli");
@@ -10065,35 +11133,31 @@ async function runCliAgent(params) {
10065
11133
  }
10066
11134
 
10067
11135
  //#endregion
10068
- //#region src/agents/anthropic-direct-runner.ts
11136
+ //#region src/agents/gemini-direct-runner.ts
10069
11137
  /**
10070
- * Anthropic Direct API Runner
11138
+ * Gemini Direct API Runner
10071
11139
  *
10072
- * Makes calls directly to api.anthropic.com without needing the claude CLI
10073
- * binary to be installed or logged in. Works with any valid token:
11140
+ * Makes calls directly to generativelanguage.googleapis.com without needing
11141
+ * a CLI wrapper. Works with Google API keys (GEMINI_API_KEY).
10074
11142
  *
10075
- * sk-ant-api01-... (Console API key)
10076
- * sk-ant-oat01-... (Claude Code OAuth access token)
10077
- *
10078
- * This runner is automatically used when an `anthropic:default` token credential
10079
- * is present in the auth store and the claude CLI is unavailable or not logged in.
11143
+ * This runner is automatically used when a google API key is available
11144
+ * and the provider is set to "google" or "gemini".
10080
11145
  */
10081
- const log = createSubsystemLogger("agent/anthropic-direct");
11146
+ const log = createSubsystemLogger("agent/gemini-direct");
11147
+ const DEFAULT_GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
10082
11148
  const MODEL_MAP = {
10083
- opus: "claude-opus-4-5",
10084
- "opus-4": "claude-opus-4-5",
10085
- "opus-4.5": "claude-opus-4-5",
10086
- "opus-4.6": "claude-opus-4-5",
10087
- "claude-opus-4-5": "claude-opus-4-5",
10088
- sonnet: "claude-sonnet-4-5",
10089
- "sonnet-4.5": "claude-sonnet-4-5",
10090
- "sonnet-4.1": "claude-sonnet-4-1-20250219",
10091
- "claude-sonnet-4-5": "claude-sonnet-4-5",
10092
- haiku: "claude-haiku-3-5",
10093
- "haiku-3.5": "claude-haiku-3-5",
10094
- default: "claude-sonnet-4-5"
11149
+ gemini: "gemini-2.0-flash",
11150
+ "gemini-pro": "gemini-1.5-pro",
11151
+ "gemini-flash": "gemini-2.0-flash",
11152
+ "gemini-2.0": "gemini-2.0-flash",
11153
+ "gemini-2.0-flash": "gemini-2.0-flash",
11154
+ "gemini-2.0-pro": "gemini-2.0-pro-exp-02-05",
11155
+ "gemini-1.5": "gemini-1.5-pro",
11156
+ "gemini-1.5-pro": "gemini-1.5-pro",
11157
+ "gemini-1.5-flash": "gemini-1.5-flash",
11158
+ default: "gemini-2.0-flash"
10095
11159
  };
10096
- const HISTORY_FILE_SUFFIX = ".anima-history.json";
11160
+ const HISTORY_FILE_SUFFIX = ".gemini-history.json";
10097
11161
  async function loadSessionHistory(sessionFile) {
10098
11162
  const histPath = sessionFile + HISTORY_FILE_SUFFIX;
10099
11163
  try {
@@ -10116,15 +11180,19 @@ function resolveModel(model) {
10116
11180
  const key = (model ?? "default").trim().toLowerCase() || "default";
10117
11181
  return MODEL_MAP[key] ?? key;
10118
11182
  }
11183
+ function buildModelPath(model) {
11184
+ return model.startsWith("models/") ? model : `models/${model}`;
11185
+ }
10119
11186
  /**
10120
- * Run an agent turn directly against api.anthropic.com.
11187
+ * Run an agent turn directly against generativelanguage.googleapis.com.
10121
11188
  *
10122
11189
  * Maintains multi-turn conversation history per session file.
10123
11190
  * Falls back to single-turn if history is unavailable.
10124
11191
  */
10125
- async function runAnthropicDirectAgent(params) {
11192
+ async function runGeminiDirectAgent(params) {
10126
11193
  const started = Date.now();
10127
11194
  const resolvedModel = resolveModel(params.model);
11195
+ const modelPath = buildModelPath(resolvedModel);
10128
11196
  log.info(`direct api exec: model=${resolvedModel} promptChars=${params.prompt.length}`);
10129
11197
  const workspaceDir = resolveRunWorkspaceDir({
10130
11198
  workspaceDir: params.workspaceDir,
@@ -10164,36 +11232,37 @@ async function runAnthropicDirectAgent(params) {
10164
11232
  docsPath: docsPath ?? void 0,
10165
11233
  tools: [],
10166
11234
  contextFiles,
10167
- modelDisplay: `anthropic/${resolvedModel}`,
11235
+ modelDisplay: `google/${resolvedModel}`,
10168
11236
  agentId: sessionAgentId
10169
11237
  });
10170
11238
  let history = await loadSessionHistory(params.sessionFile);
10171
11239
  if (!history) history = {
10172
11240
  sessionId: params.sessionId,
10173
- messages: [],
11241
+ contents: [],
10174
11242
  createdAt: started,
10175
11243
  updatedAt: started
10176
11244
  };
10177
- history.messages.push({
11245
+ history.contents.push({
10178
11246
  role: "user",
10179
- content: params.prompt
11247
+ parts: [{ text: params.prompt }]
10180
11248
  });
10181
11249
  const requestBody = {
10182
- model: resolvedModel,
10183
- max_tokens: 8192,
10184
- system: systemPrompt,
10185
- messages: history.messages
11250
+ systemInstruction: { parts: [{ text: systemPrompt }] },
11251
+ contents: history.contents,
11252
+ generationConfig: {
11253
+ maxOutputTokens: 8192,
11254
+ temperature: 1
11255
+ }
10186
11256
  };
10187
11257
  try {
10188
11258
  const controller = new AbortController();
10189
11259
  const timeoutHandle = setTimeout(() => controller.abort(), params.timeoutMs);
10190
- const response = await fetch("https://api.anthropic.com/v1/messages", {
11260
+ const url = `${DEFAULT_GEMINI_BASE_URL}/${modelPath}:generateContent?key=${params.apiKey}`;
11261
+ const response = await fetch(url, {
10191
11262
  method: "POST",
10192
11263
  headers: {
10193
- "x-api-key": params.token,
10194
- "anthropic-version": "2023-06-01",
10195
- "content-type": "application/json",
10196
- "user-agent": `anima/3.0.5 (direct-runner; ${os.platform()})`
11264
+ "Content-Type": "application/json",
11265
+ "User-Agent": `anima/5.0.1 (gemini-direct-runner; ${os.platform()})`
10197
11266
  },
10198
11267
  body: JSON.stringify(requestBody),
10199
11268
  signal: controller.signal
@@ -10204,8 +11273,8 @@ async function runAnthropicDirectAgent(params) {
10204
11273
  const isAuth = response.status === 401 || response.status === 403;
10205
11274
  const isRateLimit = response.status === 429;
10206
11275
  const rateHint = isRateLimit ? " — rate limit hit, will retry next heartbeat." : "";
10207
- const authHint = isAuth ? " — token may be invalid or expired. Run: anima setup-token" : "";
10208
- log.error(`anthropic api error: HTTP ${response.status}${authHint}${rateHint}`, {
11276
+ const authHint = isAuth ? " — API key may be invalid. Check GEMINI_API_KEY environment variable." : "";
11277
+ log.error(`gemini api error: HTTP ${response.status}${authHint}${rateHint}`, {
10209
11278
  status: response.status,
10210
11279
  body: body.slice(0, 500)
10211
11280
  });
@@ -10221,51 +11290,51 @@ async function runAnthropicDirectAgent(params) {
10221
11290
  };
10222
11291
  }
10223
11292
  const data = await response.json();
10224
- const outputText = (data.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("").trim();
10225
- if (!outputText) log.warn("anthropic direct: empty response", {
10226
- stopReason: data.stop_reason,
10227
- contentTypes: (data.content ?? []).map((b) => b.type)
10228
- });
10229
- await params.onAssistantMessageStart?.();
10230
- if (outputText && params.onPartialReply) await params.onPartialReply({ text: outputText });
10231
- history.messages.push({
10232
- role: "assistant",
10233
- content: outputText
10234
- });
10235
- history.updatedAt = Date.now();
10236
- await saveSessionHistory(params.sessionFile, history);
11293
+ const candidate = data.candidates?.[0];
11294
+ const assistantText = (candidate?.content?.parts ?? []).filter((p) => typeof p.text === "string").map((p) => p.text).join("\n");
11295
+ if (assistantText && params.onPartialReply) await params.onPartialReply({ text: assistantText });
11296
+ if (assistantText) {
11297
+ history.contents.push({
11298
+ role: "model",
11299
+ parts: [{ text: assistantText }]
11300
+ });
11301
+ history.updatedAt = Date.now();
11302
+ await saveSessionHistory(params.sessionFile, history);
11303
+ }
11304
+ const usage = data.usageMetadata;
10237
11305
  const durationMs = Date.now() - started;
10238
- const inputTokens = data.usage?.input_tokens ?? 0;
10239
- const outputTokens = data.usage?.output_tokens ?? 0;
10240
- log.info(`direct api done: model=${resolvedModel} in=${inputTokens} out=${outputTokens} ms=${durationMs}`);
11306
+ log.info(`gemini api complete: ${durationMs}ms`, {
11307
+ inputTokens: usage?.promptTokenCount,
11308
+ outputTokens: usage?.candidatesTokenCount,
11309
+ finishReason: candidate?.finishReason
11310
+ });
10241
11311
  return {
10242
11312
  status: "completed",
10243
- output: outputText,
10244
- payloads: outputText ? [{ text: outputText }] : [],
11313
+ output: assistantText,
10245
11314
  meta: {
10246
11315
  durationMs,
10247
11316
  agentMeta: {
10248
- provider: "anthropic",
10249
11317
  model: resolvedModel,
10250
- usage: {
10251
- inputTokens,
10252
- outputTokens,
10253
- cacheCreationInputTokens: 0,
10254
- cacheReadInputTokens: 0
10255
- }
11318
+ provider: "google",
11319
+ usage: usage ? {
11320
+ input: usage.promptTokenCount ?? 0,
11321
+ output: usage.candidatesTokenCount ?? 0
11322
+ } : void 0
10256
11323
  }
10257
11324
  }
10258
11325
  };
10259
11326
  } catch (err) {
10260
- const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("aborted"));
10261
- log.error("anthropic direct runner error", { error: String(err) });
11327
+ const isAbort = err instanceof Error && err.name === "AbortError";
11328
+ const errorKind = isAbort ? "timeout" : "unknown";
11329
+ const errorMsg = isAbort ? `Request timed out after ${params.timeoutMs}ms` : String(err);
11330
+ log.error(`gemini api error: ${errorMsg}`, { error: String(err) });
10262
11331
  return {
10263
- status: isAbort ? "timeout" : "failed",
11332
+ status: "failed",
10264
11333
  meta: {
10265
11334
  durationMs: Date.now() - started,
10266
11335
  error: {
10267
- message: isAbort ? `Request timed out after ${params.timeoutMs}ms` : String(err instanceof Error ? err.message : err),
10268
- kind: isAbort ? "timeout" : "unknown"
11336
+ message: errorMsg,
11337
+ kind: errorKind
10269
11338
  }
10270
11339
  }
10271
11340
  };
@@ -10353,6 +11422,57 @@ async function runEmbeddedPiAgent(...args) {
10353
11422
  }
10354
11423
  }
10355
11424
  }
11425
+ if (provider === "google" || provider === "gemini") {
11426
+ const geminiApiKey = (await resolveApiKeyForProvider({
11427
+ provider: "google",
11428
+ cfg: params.config
11429
+ }))?.apiKey;
11430
+ if (geminiApiKey) {
11431
+ await emitAgentEvent(params, "lifecycle", {
11432
+ phase: "start",
11433
+ startedAt
11434
+ });
11435
+ try {
11436
+ const result = await runGeminiDirectAgent({
11437
+ apiKey: geminiApiKey,
11438
+ sessionId: params.sessionId,
11439
+ sessionKey: params.sessionKey,
11440
+ agentId: params.agentId,
11441
+ sessionFile: params.sessionFile,
11442
+ workspaceDir: params.workspaceDir,
11443
+ config: params.config,
11444
+ prompt: params.prompt,
11445
+ model: params.model,
11446
+ thinkLevel: params.thinkLevel,
11447
+ timeoutMs,
11448
+ runId,
11449
+ extraSystemPrompt: params.extraSystemPrompt,
11450
+ ownerNumbers: params.ownerNumbers,
11451
+ onPartialReply: async (payload) => {
11452
+ if (!assistantStarted) {
11453
+ assistantStarted = true;
11454
+ await params.onAssistantMessageStart?.();
11455
+ }
11456
+ await params.onPartialReply?.(payload);
11457
+ await emitAgentEvent(params, "assistant", { text: payload.text });
11458
+ },
11459
+ onAssistantMessageStart: params.onAssistantMessageStart
11460
+ });
11461
+ await emitAgentEvent(params, "lifecycle", {
11462
+ phase: "end",
11463
+ durationMs: Date.now() - startedAt,
11464
+ status: result.status
11465
+ });
11466
+ return result;
11467
+ } catch (err) {
11468
+ await emitAgentEvent(params, "lifecycle", {
11469
+ phase: "error",
11470
+ error: String(err instanceof Error ? err.message : err)
11471
+ });
11472
+ throw err;
11473
+ }
11474
+ }
11475
+ }
10356
11476
  const cliProvider = resolveCompatCliProvider(provider, params.config);
10357
11477
  if (!resolveCliBackendConfig(cliProvider, params.config)) throw new Error(`No CLI backend available for provider "${provider}" (resolved "${cliProvider}").\nEither:\n • Run: anima setup-token (set an Anthropic API key — no CLI needed)\n • Install the matching CLI and log in`);
10358
11478
  await emitAgentEvent(params, "lifecycle", {