@phuetz/code-buddy 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +160 -165
  2. package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
  3. package/dist/agent/codebuddy-agent.d.ts +1 -1
  4. package/dist/agent/codebuddy-agent.js +69 -14
  5. package/dist/agent/execution/agent-executor.js +122 -12
  6. package/dist/agent/execution/tool-dependency-graph.js +7 -0
  7. package/dist/agent/execution/tool-hooks.d.ts +6 -0
  8. package/dist/agent/execution/tool-hooks.js +13 -5
  9. package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
  10. package/dist/agent/execution/tool-selection-strategy.js +67 -10
  11. package/dist/agent/extended-thinking.d.ts +8 -0
  12. package/dist/agent/extended-thinking.js +27 -0
  13. package/dist/agent/hermes-browser-backends.js +151 -11
  14. package/dist/agent/hermes-claw-migrate.d.ts +59 -0
  15. package/dist/agent/hermes-claw-migrate.js +427 -26
  16. package/dist/agent/hermes-parity-manifest.js +78 -71
  17. package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
  18. package/dist/agent/hermes-runtime-lifecycle.js +468 -0
  19. package/dist/agent/middleware/index.d.ts +1 -0
  20. package/dist/agent/middleware/index.js +1 -0
  21. package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
  22. package/dist/agent/middleware/visual-validation-middleware.js +67 -0
  23. package/dist/agent/model-benchmark.d.ts +77 -0
  24. package/dist/agent/model-benchmark.js +309 -0
  25. package/dist/agent/model-tier.d.ts +14 -0
  26. package/dist/agent/model-tier.js +71 -0
  27. package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
  28. package/dist/agent/multi-agent/multi-agent-system.js +3 -1
  29. package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
  30. package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
  31. package/dist/agent/multi-agent/session-tools.js +22 -5
  32. package/dist/agent/reasoning/mcts.js +20 -6
  33. package/dist/agent/session-end-flush.d.ts +6 -1
  34. package/dist/agent/session-end-flush.js +20 -4
  35. package/dist/agent/streaming/streaming-handler.d.ts +7 -0
  36. package/dist/agent/streaming/streaming-handler.js +8 -1
  37. package/dist/agent/tool-executor.js +1 -0
  38. package/dist/agent/tool-handler.d.ts +5 -0
  39. package/dist/agent/tool-handler.js +130 -4
  40. package/dist/browser-automation/browser-operator-executor.js +15 -0
  41. package/dist/browser-automation/browser-use-runner.d.ts +96 -0
  42. package/dist/browser-automation/browser-use-runner.js +492 -0
  43. package/dist/browser-automation/camofox-runner.d.ts +107 -0
  44. package/dist/browser-automation/camofox-runner.js +287 -0
  45. package/dist/channels/dingtalk/index.js +3 -0
  46. package/dist/channels/discord/client.d.ts +9 -0
  47. package/dist/channels/discord/client.js +12 -0
  48. package/dist/channels/feishu/index.d.ts +167 -1
  49. package/dist/channels/feishu/index.js +432 -7
  50. package/dist/channels/gateway-lifecycle.d.ts +132 -0
  51. package/dist/channels/gateway-lifecycle.js +219 -0
  52. package/dist/channels/google-chat/index.d.ts +5 -1
  53. package/dist/channels/google-chat/index.js +5 -1
  54. package/dist/channels/imessage/index.d.ts +14 -0
  55. package/dist/channels/imessage/index.js +64 -17
  56. package/dist/channels/index.d.ts +4 -0
  57. package/dist/channels/index.js +4 -0
  58. package/dist/channels/irc/index.d.ts +73 -3
  59. package/dist/channels/irc/index.js +446 -11
  60. package/dist/channels/line/index.js +3 -0
  61. package/dist/channels/mattermost/index.d.ts +51 -4
  62. package/dist/channels/mattermost/index.js +303 -20
  63. package/dist/channels/nextcloud-talk/index.d.ts +89 -8
  64. package/dist/channels/nextcloud-talk/index.js +367 -16
  65. package/dist/channels/nostr/index.d.ts +121 -1
  66. package/dist/channels/nostr/index.js +396 -8
  67. package/dist/channels/ntfy/index.js +3 -0
  68. package/dist/channels/qq/index.js +3 -0
  69. package/dist/channels/slash-parity.d.ts +117 -0
  70. package/dist/channels/slash-parity.js +185 -0
  71. package/dist/channels/synology-chat/index.js +3 -0
  72. package/dist/channels/teams/index.d.ts +3 -0
  73. package/dist/channels/teams/index.js +3 -0
  74. package/dist/channels/telegram/client.d.ts +9 -0
  75. package/dist/channels/telegram/client.js +12 -0
  76. package/dist/channels/twilio-voice/index.js +3 -0
  77. package/dist/channels/webchat/index.d.ts +7 -1
  78. package/dist/channels/webchat/index.js +7 -1
  79. package/dist/channels/wecom/index.js +3 -0
  80. package/dist/channels/weixin/index.js +3 -0
  81. package/dist/channels/zalo/index.js +3 -0
  82. package/dist/cli/config-loader.js +1 -1
  83. package/dist/codebuddy/client.d.ts +30 -1
  84. package/dist/codebuddy/client.js +167 -20
  85. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
  86. package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
  87. package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
  88. package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
  89. package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
  90. package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
  91. package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
  92. package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
  93. package/dist/codebuddy/tool-definitions/index.js +2 -1
  94. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
  95. package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
  96. package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
  97. package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
  98. package/dist/codebuddy/tools.d.ts +1 -1
  99. package/dist/codebuddy/tools.js +27 -24
  100. package/dist/commands/cli/hermes-commands.js +108 -0
  101. package/dist/commands/cli/native-engine-commands.js +102 -3
  102. package/dist/commands/cli/utility-commands.js +45 -0
  103. package/dist/commands/enhanced-command-handler.js +1 -1
  104. package/dist/commands/goal-cli.d.ts +30 -0
  105. package/dist/commands/goal-cli.js +210 -27
  106. package/dist/commands/handlers/goal-handler.d.ts +9 -2
  107. package/dist/commands/handlers/goal-handler.js +36 -9
  108. package/dist/commands/handlers/infra-handlers.js +36 -0
  109. package/dist/commands/handlers/memory-handlers.js +96 -1
  110. package/dist/commands/headless-slash.d.ts +2 -0
  111. package/dist/commands/headless-slash.js +12 -0
  112. package/dist/commands/llm-provider-resolution.d.ts +5 -3
  113. package/dist/commands/llm-provider-resolution.js +87 -33
  114. package/dist/commands/ollama.d.ts +25 -0
  115. package/dist/commands/ollama.js +100 -0
  116. package/dist/commands/provider.d.ts +5 -0
  117. package/dist/commands/provider.js +106 -55
  118. package/dist/commands/spec-next.js +2 -1
  119. package/dist/commands/spec-plan.js +5 -16
  120. package/dist/commands/tunnel.d.ts +2 -0
  121. package/dist/commands/tunnel.js +48 -0
  122. package/dist/config/config-resolver.d.ts +2 -1
  123. package/dist/config/config-resolver.js +54 -41
  124. package/dist/config/constants.d.ts +28 -0
  125. package/dist/config/constants.js +7 -0
  126. package/dist/config/env-schema.js +756 -0
  127. package/dist/config/model-tools.js +4 -4
  128. package/dist/config/toml-config.d.ts +16 -0
  129. package/dist/config/toml-config.js +3 -0
  130. package/dist/context/context-manager-v2.js +1 -0
  131. package/dist/daemon/autonomous-daemon.d.ts +1 -1
  132. package/dist/daemon/autonomous-daemon.js +3 -3
  133. package/dist/daemon/autonomous-loop.js +11 -2
  134. package/dist/daemon/colab-goal.js +9 -1
  135. package/dist/daemon/cron-agent-bridge.d.ts +12 -3
  136. package/dist/daemon/cron-agent-bridge.js +25 -9
  137. package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
  138. package/dist/desktop/codebuddy-engine-adapter.js +257 -80
  139. package/dist/desktop/engine-adapter.d.ts +14 -0
  140. package/dist/desktop-automation/automation-manager.js +16 -0
  141. package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
  142. package/dist/desktop-automation/omniparser-runner.js +115 -0
  143. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  144. package/dist/desktop-automation/smart-snapshot.js +72 -5
  145. package/dist/doctor/index.js +40 -23
  146. package/dist/events/types.d.ts +11 -0
  147. package/dist/fleet/colab-store.js +14 -2
  148. package/dist/fleet/peer-session-bridge.js +50 -12
  149. package/dist/fleet/peer-tool-bridge.js +14 -0
  150. package/dist/goals/goal-decomposer.d.ts +27 -0
  151. package/dist/goals/goal-decomposer.js +293 -0
  152. package/dist/goals/goal-judge-client.d.ts +15 -0
  153. package/dist/goals/goal-judge-client.js +45 -0
  154. package/dist/goals/goal-judge.d.ts +2 -0
  155. package/dist/goals/goal-judge.js +35 -6
  156. package/dist/goals/goal-loop.d.ts +14 -0
  157. package/dist/goals/goal-loop.js +47 -2
  158. package/dist/goals/goal-manager.d.ts +6 -0
  159. package/dist/goals/goal-manager.js +67 -14
  160. package/dist/goals/goal-state.d.ts +13 -0
  161. package/dist/goals/goal-state.js +83 -13
  162. package/dist/goals/goal-store.d.ts +3 -0
  163. package/dist/goals/goal-store.js +31 -15
  164. package/dist/hooks/user-hooks.js +17 -3
  165. package/dist/index.js +464 -25
  166. package/dist/input/text-to-speech.d.ts +2 -6
  167. package/dist/input/text-to-speech.js +2 -27
  168. package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
  169. package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
  170. package/dist/integrations/tailscale.d.ts +13 -0
  171. package/dist/integrations/tailscale.js +87 -34
  172. package/dist/knowledge/workspace-indexer.js +53 -9
  173. package/dist/mcp/client.js +1 -0
  174. package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
  175. package/dist/mcp/mcp-desktop-tools.js +158 -0
  176. package/dist/mcp/mcp-server.js +2 -0
  177. package/dist/memory/enhanced-memory.d.ts +10 -0
  178. package/dist/memory/enhanced-memory.js +33 -2
  179. package/dist/memory/index.d.ts +3 -1
  180. package/dist/memory/index.js +4 -1
  181. package/dist/memory/memory-auto-proposer.d.ts +23 -0
  182. package/dist/memory/memory-auto-proposer.js +308 -0
  183. package/dist/memory/memory-candidate-queue.d.ts +92 -0
  184. package/dist/memory/memory-candidate-queue.js +261 -0
  185. package/dist/memory/persistent-memory.d.ts +45 -2
  186. package/dist/memory/persistent-memory.js +236 -40
  187. package/dist/ml/bayesian-qualifier.d.ts +1 -1
  188. package/dist/ml/bayesian-qualifier.js +35 -2
  189. package/dist/openclaw/gateway-bridge.js +5 -0
  190. package/dist/plugins/marketplace.d.ts +1 -0
  191. package/dist/plugins/marketplace.js +7 -0
  192. package/dist/prompts/execution-discipline.d.ts +14 -0
  193. package/dist/prompts/execution-discipline.js +29 -0
  194. package/dist/prompts/variation-injector.js +9 -2
  195. package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
  196. package/dist/protocols/acp/acp-agentic-runner.js +115 -18
  197. package/dist/protocols/acp/acp-session-store.d.ts +23 -0
  198. package/dist/protocols/acp/acp-session-store.js +77 -0
  199. package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
  200. package/dist/protocols/acp/acp-stdio-server.js +71 -16
  201. package/dist/providers/active-llm-registry.d.ts +37 -0
  202. package/dist/providers/active-llm-registry.js +186 -0
  203. package/dist/providers/auxiliary-provider.d.ts +25 -0
  204. package/dist/providers/auxiliary-provider.js +192 -0
  205. package/dist/providers/codex-oauth.d.ts +1 -1
  206. package/dist/providers/codex-oauth.js +27 -4
  207. package/dist/providers/index.d.ts +3 -0
  208. package/dist/providers/index.js +4 -0
  209. package/dist/providers/provider-catalog.d.ts +70 -0
  210. package/dist/providers/provider-catalog.js +738 -0
  211. package/dist/providers/provider-fallback.d.ts +35 -0
  212. package/dist/providers/provider-fallback.js +171 -0
  213. package/dist/providers/xai-oauth.d.ts +128 -0
  214. package/dist/providers/xai-oauth.js +735 -0
  215. package/dist/scheduler/cron-scheduler.d.ts +15 -3
  216. package/dist/scheduler/cron-scheduler.js +53 -7
  217. package/dist/scheduler/script-runner.d.ts +2 -0
  218. package/dist/scheduler/script-runner.js +19 -4
  219. package/dist/scheduler/watchdog-handlers.js +7 -20
  220. package/dist/search/usearch-index.js +7 -2
  221. package/dist/security/tool-policy/tool-groups.js +2 -0
  222. package/dist/server/index.js +17 -2
  223. package/dist/server/routes/mobile.d.ts +12 -4
  224. package/dist/server/routes/mobile.js +116 -1
  225. package/dist/server/tls-config.d.ts +35 -0
  226. package/dist/server/tls-config.js +142 -0
  227. package/dist/server/tunnel-manager.d.ts +20 -0
  228. package/dist/server/tunnel-manager.js +58 -0
  229. package/dist/server/websocket/fleet-bridge.js +5 -0
  230. package/dist/services/prompt-builder.d.ts +1 -0
  231. package/dist/services/prompt-builder.js +49 -8
  232. package/dist/shared/engine-types.d.ts +15 -1
  233. package/dist/sidecar/sidecar-bridge.d.ts +1 -0
  234. package/dist/sidecar/sidecar-bridge.js +21 -10
  235. package/dist/spec/spec-store.js +8 -1
  236. package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
  237. package/dist/talk-mode/providers/audioreader-tts.js +8 -8
  238. package/dist/templates/project-scaffolding.js +3 -2
  239. package/dist/themes/theme-schema.d.ts +10 -10
  240. package/dist/tools/application-profiles.js +38 -0
  241. package/dist/tools/bash/bash-tool.d.ts +1 -0
  242. package/dist/tools/bash/bash-tool.js +16 -2
  243. package/dist/tools/bash/command-validator.js +3 -0
  244. package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
  245. package/dist/tools/bash/rtk-rewrite.js +100 -0
  246. package/dist/tools/bash/streaming-executor.js +14 -1
  247. package/dist/tools/computer-control-tool.d.ts +16 -1
  248. package/dist/tools/computer-control-tool.js +317 -5
  249. package/dist/tools/document-generator.d.ts +14 -0
  250. package/dist/tools/document-generator.js +79 -0
  251. package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
  252. package/dist/tools/execute-code-rpc-invoker.js +46 -5
  253. package/dist/tools/metadata.js +28 -0
  254. package/dist/tools/office-macro-tool.d.ts +10 -0
  255. package/dist/tools/office-macro-tool.js +93 -0
  256. package/dist/tools/registry/index.d.ts +2 -1
  257. package/dist/tools/registry/index.js +2 -1
  258. package/dist/tools/registry/memory-tools.d.ts +20 -0
  259. package/dist/tools/registry/memory-tools.js +225 -3
  260. package/dist/tools/registry/multimodal-tools.js +2 -2
  261. package/dist/tools/registry/vision-tools.d.ts +33 -0
  262. package/dist/tools/registry/vision-tools.js +308 -6
  263. package/dist/tools/registry/windows-tools.d.ts +2 -0
  264. package/dist/tools/registry/windows-tools.js +47 -0
  265. package/dist/tools/route-peer-tool.js +14 -0
  266. package/dist/tools/screenshot-tool.js +14 -2
  267. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  268. package/dist/tools/text-to-speech-tool.js +2 -12
  269. package/dist/tools/tool-selector.js +3 -59
  270. package/dist/utils/config-validation/schema.d.ts +40 -7
  271. package/dist/utils/config-validation/schema.js +130 -1
  272. package/dist/utils/cost-tracker.js +21 -1
  273. package/dist/utils/disk-guard.d.ts +180 -0
  274. package/dist/utils/disk-guard.js +404 -0
  275. package/dist/utils/interactive-setup.js +35 -2
  276. package/dist/utils/model-utils.js +11 -2
  277. package/dist/utils/output-sanitizer.js +11 -0
  278. package/dist/utils/provider-detector.d.ts +8 -7
  279. package/dist/utils/provider-detector.js +19 -77
  280. package/dist/utils/settings-manager.d.ts +7 -0
  281. package/dist/utils/token-display.d.ts +3 -3
  282. package/dist/utils/token-display.js +22 -4
  283. package/dist/wizard/onboarding.d.ts +39 -0
  284. package/dist/wizard/onboarding.js +433 -21
  285. package/dist/wizard/provider-onboarding.d.ts +8 -23
  286. package/dist/wizard/provider-onboarding.js +39 -158
  287. package/package.json +4 -2
  288. package/dist/plugins/provider-onboarding.d.ts +0 -23
  289. package/dist/plugins/provider-onboarding.js +0 -116
  290. package/dist/utils/kokoro-tts.d.ts +0 -8
  291. package/dist/utils/kokoro-tts.js +0 -67
@@ -1,3 +1,4 @@
1
+ import { decomposeGoal, formatGoalPlan, shouldAutoDecomposeGoal, } from '../../goals/goal-decomposer.js';
1
2
  import { getGoalManager } from '../../goals/goal-manager.js';
2
3
  /**
3
4
  * /goal — standing goal with judge + auto-continue loop (the Ralph loop,
@@ -10,10 +11,10 @@ import { getGoalManager } from '../../goals/goal-manager.js';
10
11
  * /goal resume restart the loop (resets the turn budget)
11
12
  * /goal clear discard the goal (aliases: stop, done)
12
13
  */
13
- export async function handleGoal(args) {
14
+ export async function handleGoal(args, options = {}) {
14
15
  const arg = args.join(' ').trim();
15
16
  const lower = arg.toLowerCase();
16
- const mgr = getGoalManager();
17
+ const mgr = getGoalManager(options.sessionKey);
17
18
  if (!arg || lower === 'status') {
18
19
  return textResult(mgr.statusLine());
19
20
  }
@@ -35,13 +36,37 @@ export async function handleGoal(args) {
35
36
  return textResult(had ? '✓ Goal cleared.' : 'No active goal.');
36
37
  }
37
38
  // Otherwise treat the arg as the goal text.
39
+ let plan = null;
40
+ let planAttempted = false;
41
+ let planError = '';
42
+ if (options.client && shouldAutoDecomposeGoal(arg)) {
43
+ planAttempted = true;
44
+ try {
45
+ plan = await (options.planner ?? decomposeGoal)(arg, options.client);
46
+ }
47
+ catch (error) {
48
+ planError = error instanceof Error ? error.message : String(error);
49
+ }
50
+ }
38
51
  let state;
39
52
  try {
40
- state = mgr.set(arg);
53
+ state = mgr.set(arg, {
54
+ ...(plan ? { goalPlan: plan } : {}),
55
+ ...(planAttempted ? { goalPlanAttempted: true } : {}),
56
+ });
57
+ if (planAttempted && !plan && planError) {
58
+ mgr.markGoalPlanAttempted(planError);
59
+ state = mgr.state ?? state;
60
+ }
41
61
  }
42
62
  catch (error) {
43
63
  return textResult(`Invalid goal: ${error instanceof Error ? error.message : String(error)}`);
44
64
  }
65
+ const planBlock = state.goalPlan
66
+ ? `\n\nHermes-style task graph attached:\n${formatGoalPlan(state.goalPlan)}`
67
+ : planError
68
+ ? `\n\nGoal planning was skipped after an LLM planner error: ${planError}`
69
+ : '';
45
70
  return {
46
71
  handled: true,
47
72
  entry: {
@@ -51,7 +76,8 @@ export async function handleGoal(args) {
51
76
  'keeps working until it is, you pause/clear it, or the budget is ' +
52
77
  'exhausted. Use /goal status, /goal pause, /goal resume, /goal clear. ' +
53
78
  'Tip: for unattended runs, enable auto-approval (/yolo or auto-edit) so ' +
54
- 'the loop is not blocked on confirmations.',
79
+ 'the loop is not blocked on confirmations.' +
80
+ planBlock,
55
81
  timestamp: new Date(),
56
82
  },
57
83
  // Kick the loop off immediately — the dispatcher feeds the goal text as
@@ -73,9 +99,9 @@ export async function handleGoal(args) {
73
99
  * them) and the continuation prompt (agent sees them) on the next turn
74
100
  * boundary — no special kick needed.
75
101
  */
76
- export async function handleSubgoal(args) {
102
+ export async function handleSubgoal(args, options = {}) {
77
103
  const arg = args.join(' ').trim();
78
- const mgr = getGoalManager();
104
+ const mgr = getGoalManager(options.sessionKey);
79
105
  if (!mgr.hasGoal()) {
80
106
  return textResult('No active goal. Set one with /goal <text>.');
81
107
  }
@@ -88,9 +114,10 @@ export async function handleSubgoal(args) {
88
114
  if (!rest) {
89
115
  return textResult('Usage: /subgoal remove <n>');
90
116
  }
91
- const idx = Number(rest.split(/\s+/)[0]);
92
- if (!Number.isInteger(idx)) {
93
- return textResult('/subgoal remove: <n> must be an integer (1-based index).');
117
+ const indexToken = rest.split(/\s+/)[0] ?? '';
118
+ const idx = Number(indexToken);
119
+ if (!/^[1-9]\d*$/.test(indexToken) || !Number.isSafeInteger(idx)) {
120
+ return textResult('/subgoal remove: <n> must be a positive integer (1-based index).');
94
121
  }
95
122
  try {
96
123
  const removed = mgr.removeSubgoal(idx);
@@ -6,6 +6,7 @@
6
6
  * and the current TurboQuant routing statistics.
7
7
  */
8
8
  import { logger } from '../../utils/logger.js';
9
+ import { fetchOllamaStatus } from '../ollama.js';
9
10
  async function checkEndpoint(name, url, timeoutMs = 4000) {
10
11
  const start = Date.now();
11
12
  try {
@@ -49,6 +50,35 @@ function formatRoutingStats(stats) {
49
50
  }
50
51
  return lines.join('\n');
51
52
  }
53
+ function formatOllamaSummary(status) {
54
+ const lines = [
55
+ ` Reachable: ${status.reachable ? 'YES' : 'NO'}`,
56
+ ` Version: ${status.version ?? '(unknown)'}`,
57
+ ` Models: ${status.models.length > 0 ? status.models.join(', ') : '(none)'}`,
58
+ ];
59
+ if (status.error) {
60
+ lines.push(` Error: ${status.error}`);
61
+ }
62
+ lines.push(` Gemma 4 ready: ${isGemma4Ready(status.version, status.models) ? 'YES' : 'NO'}`);
63
+ return lines.join('\n');
64
+ }
65
+ function isGemma4Ready(version, models) {
66
+ if (!version)
67
+ return false;
68
+ const [majorRaw, minorRaw, patchRaw] = version.split('.');
69
+ const major = Number.parseInt(majorRaw ?? '', 10);
70
+ const minor = Number.parseInt(minorRaw ?? '', 10);
71
+ const patch = Number.parseInt(patchRaw ?? '', 10);
72
+ if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch))
73
+ return false;
74
+ if (major > 0)
75
+ return true;
76
+ if (minor > 24)
77
+ return true;
78
+ if (minor === 24 && patch >= 1)
79
+ return true;
80
+ return models.some((model) => model.toLowerCase().includes('gemma4'));
81
+ }
52
82
  // ---------------------------------------------------------------------------
53
83
  // Main handler
54
84
  // ---------------------------------------------------------------------------
@@ -102,6 +132,9 @@ async function handleInfraDashboard() {
102
132
  lines.push(formatStatus(s));
103
133
  }
104
134
  lines.push('');
135
+ lines.push('Ollama Summary:');
136
+ lines.push(formatOllamaSummary(await fetchOllamaStatus(ollamaUrl)));
137
+ lines.push('');
105
138
  // Routing stats from TurboQuant plugin
106
139
  try {
107
140
  const { getTurboQuantStats } = await import('../../plugins/bundled/turboquant-plugin.js');
@@ -159,6 +192,9 @@ async function handleInfraHealth() {
159
192
  for (const s of statuses) {
160
193
  lines.push(formatStatus(s));
161
194
  }
195
+ lines.push('');
196
+ lines.push('Ollama Summary:');
197
+ lines.push(formatOllamaSummary(await fetchOllamaStatus(ollamaUrl)));
162
198
  return { handled: true, response: lines.join('\n') };
163
199
  }
164
200
  //# sourceMappingURL=infra-handlers.js.map
@@ -27,6 +27,9 @@ function formatTimeAgo(date, now = new Date()) {
27
27
  const yr = Math.floor(day / 365);
28
28
  return `${yr} year${yr === 1 ? "" : "s"} ago`;
29
29
  }
30
+ function clip(text, max = 180) {
31
+ return text.length > max ? text.slice(0, max - 1) + "…" : text;
32
+ }
30
33
  /**
31
34
  * Memory - Manage persistent memory using PersistentMemoryManager (Markdown) and EnhancedMemory (SQLite/Vector)
32
35
  */
@@ -103,10 +106,10 @@ export async function handleMemory(args) {
103
106
  content = `Usage: /memory remember <key> <content> [project|user]`;
104
107
  break;
105
108
  }
106
- const value = args.slice(2).join(" ");
107
109
  const scope = (args[args.length - 1] === "user" || args[args.length - 1] === "project")
108
110
  ? args.pop()
109
111
  : "project";
112
+ const value = args.slice(2).join(" ");
110
113
  // Store in both for redundancy and better retrieval
111
114
  await persistentMemory.remember(key, value, { scope, category: "custom" });
112
115
  await enhancedMemory.store({
@@ -121,6 +124,30 @@ export async function handleMemory(args) {
121
124
  content = `Usage: /memory remember <key> <content> [project|user]`;
122
125
  }
123
126
  break;
127
+ case "replace":
128
+ case "update":
129
+ if (args.length >= 3) {
130
+ const key = args[1];
131
+ if (key === undefined) {
132
+ content = `Usage: /memory replace <key> <content> [project|user]`;
133
+ break;
134
+ }
135
+ const scope = (args[args.length - 1] === "user" || args[args.length - 1] === "project")
136
+ ? args.pop()
137
+ : "project";
138
+ const value = args.slice(2).join(" ");
139
+ const result = await persistentMemory.replace(key, value, { scope });
140
+ if (result.status === 'missing') {
141
+ content = result.message;
142
+ }
143
+ else {
144
+ content = `✅ ${result.message}\nCapacity: ${result.usage.used}/${result.usage.limit} chars (${result.usage.percent}%).`;
145
+ }
146
+ }
147
+ else {
148
+ content = `Usage: /memory replace <key> <content> [project|user]`;
149
+ }
150
+ break;
124
151
  case "context":
125
152
  const enhancedContext = await enhancedMemory.buildContext({
126
153
  includeProject: true,
@@ -154,6 +181,74 @@ export async function handleMemory(args) {
154
181
  }
155
182
  break;
156
183
  }
184
+ case "candidates":
185
+ case "candidate":
186
+ case "pending": {
187
+ const { getMemoryCandidateQueue } = await import("../../memory/memory-candidate-queue.js");
188
+ const statusArg = args[1];
189
+ const status = statusArg === "pending" || statusArg === "accepted" || statusArg === "rejected"
190
+ ? statusArg
191
+ : undefined;
192
+ const rawLimit = parseInt(args[status ? 2 : 1] ?? "20", 10);
193
+ const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 20));
194
+ const candidates = getMemoryCandidateQueue(process.cwd()).list(status).slice(0, limit);
195
+ if (candidates.length === 0) {
196
+ content = status ? `No ${status} memory candidates.` : "No memory candidates yet.";
197
+ }
198
+ else {
199
+ const lines = [`🧠 Memory candidates (${candidates.length}${status ? ` ${status}` : ""})`, "═".repeat(50)];
200
+ for (const candidate of candidates) {
201
+ const created = formatTimeAgo(new Date(candidate.createdAt));
202
+ const confidence = typeof candidate.confidence === "number"
203
+ ? ` | confidence ${(candidate.confidence * 100).toFixed(0)}%`
204
+ : "";
205
+ lines.push(`[${candidate.status}] ${candidate.id} | ${candidate.scope}/${candidate.category}${confidence} | ${created}`);
206
+ lines.push(` ${candidate.key}: ${clip(candidate.value)}`);
207
+ const citation = candidate.citations?.[0];
208
+ if (citation) {
209
+ const label = citation.sessionId
210
+ ? `${citation.sessionId}${citation.messageId ? `#${citation.messageId}` : citation.messageIndex ? `#${citation.messageIndex}` : ""}`
211
+ : citation.messageIndex ? `message ${citation.messageIndex}` : "evidence";
212
+ lines.push(` citation: ${label}${citation.role ? ` (${citation.role})` : ""} — ${clip(citation.snippet, 140)}`);
213
+ }
214
+ lines.push("");
215
+ }
216
+ lines.push("Approve: /memory accept <id> [reviewer]");
217
+ lines.push("Reject: /memory reject <id> [reason]");
218
+ content = lines.join("\n").trimEnd();
219
+ }
220
+ break;
221
+ }
222
+ case "accept":
223
+ case "approve": {
224
+ const id = args[1];
225
+ if (!id) {
226
+ content = `Usage: /memory accept <candidate-id> [reviewer]`;
227
+ break;
228
+ }
229
+ const reviewedBy = args.slice(2).join(" ").trim() || "user";
230
+ const { getMemoryCandidateQueue } = await import("../../memory/memory-candidate-queue.js");
231
+ const { candidate, write } = await getMemoryCandidateQueue(process.cwd()).accept(id, { reviewedBy });
232
+ content = `✅ Accepted ${candidate.id} into ${candidate.scope} memory as "${candidate.key}".\n` +
233
+ `Write status: ${write.status}. Capacity: ${write.usage.used}/${write.usage.limit} chars (${write.usage.percent}%).`;
234
+ break;
235
+ }
236
+ case "reject":
237
+ case "discard": {
238
+ const id = args[1];
239
+ if (!id) {
240
+ content = `Usage: /memory reject <candidate-id> [reason]`;
241
+ break;
242
+ }
243
+ const reason = args.slice(2).join(" ").trim();
244
+ const { getMemoryCandidateQueue } = await import("../../memory/memory-candidate-queue.js");
245
+ const candidate = getMemoryCandidateQueue(process.cwd()).reject(id, {
246
+ reviewedBy: "user",
247
+ ...(reason ? { reason } : {}),
248
+ });
249
+ content = `🗑️ Rejected memory candidate ${candidate.id}.`;
250
+ break;
251
+ }
157
252
  case "status":
158
253
  case "list":
159
254
  default:
@@ -33,6 +33,8 @@ export interface HeadlessSlashContext {
33
33
  conversationHistory?: ChatEntry[];
34
34
  /** LLM client, for tokens that call the model directly (ai-test, btw). */
35
35
  client?: CodeBuddyClient;
36
+ /** Optional goal-state key for non-TUI surfaces with their own session ids. */
37
+ goalSessionKey?: string;
36
38
  }
37
39
  /** Result of running a special token outside the TUI. */
38
40
  export interface HeadlessSlashResult {
@@ -55,6 +55,18 @@ export async function executeHeadlessSlashToken(token, args, allow, ctx = {}) {
55
55
  if (ctx.client)
56
56
  handler.setCodeBuddyClient(ctx.client);
57
57
  try {
58
+ if (ctx.goalSessionKey && (token === '__GOAL__' || token === '__SUBGOAL__')) {
59
+ const { handleGoal, handleSubgoal } = await import('./handlers/goal-handler.js');
60
+ const result = token === '__GOAL__'
61
+ ? await handleGoal(args, { sessionKey: ctx.goalSessionKey, client: ctx.client ?? null })
62
+ : await handleSubgoal(args, { sessionKey: ctx.goalSessionKey });
63
+ return {
64
+ handled: result.handled,
65
+ output: result.entry?.content,
66
+ prompt: result.prompt,
67
+ passToAI: result.passToAI,
68
+ };
69
+ }
58
70
  const result = await handler.handleCommand(token, args, [token, ...args].join(" "));
59
71
  return {
60
72
  handled: result.handled,
@@ -6,9 +6,11 @@
6
6
  * provider + the `PROVIDERS` map (paid keys), so a machine running on
7
7
  * local Ollama (`CODEBUDDY_PROVIDER=ollama`, $0) hit the "no API key"
8
8
  * exit even though the rest of Code Buddy — including the autonomous
9
- * daemon — runs fine there. This resolver keeps the legacy path first
10
- * (backward compatible) and falls back to {@link detectProviderFromEnv}
11
- * (ollama / chatgpt-oauth / env-detected providers) when either:
9
+ * daemon — runs fine there. This resolver keeps the legacy path for
10
+ * ambient paid-key defaults, but honors explicit local / ChatGPT
11
+ * subscription model choices before a cloud key can hijack them. It
12
+ * falls back to {@link detectProviderFromEnv} (ollama / chatgpt-oauth /
13
+ * env-detected providers) when either:
12
14
  * - `CODEBUDDY_PROVIDER` is explicitly set (operator intent wins), or
13
15
  * - the legacy path found no key.
14
16
  */
@@ -6,58 +6,69 @@
6
6
  * provider + the `PROVIDERS` map (paid keys), so a machine running on
7
7
  * local Ollama (`CODEBUDDY_PROVIDER=ollama`, $0) hit the "no API key"
8
8
  * exit even though the rest of Code Buddy — including the autonomous
9
- * daemon — runs fine there. This resolver keeps the legacy path first
10
- * (backward compatible) and falls back to {@link detectProviderFromEnv}
11
- * (ollama / chatgpt-oauth / env-detected providers) when either:
9
+ * daemon — runs fine there. This resolver keeps the legacy path for
10
+ * ambient paid-key defaults, but honors explicit local / ChatGPT
11
+ * subscription model choices before a cloud key can hijack them. It
12
+ * falls back to {@link detectProviderFromEnv} (ollama / chatgpt-oauth /
13
+ * env-detected providers) when either:
12
14
  * - `CODEBUDDY_PROVIDER` is explicitly set (operator intent wins), or
13
15
  * - the legacy path found no key.
14
16
  */
15
17
  import { getSettingsManager } from '../utils/settings-manager.js';
16
18
  import { detectProviderFromEnv } from '../utils/provider-detector.js';
17
- import { PROVIDERS } from './provider.js';
19
+ import { inferProvider } from '../config/resolve-model.js';
20
+ import { hasCodexCredentials } from '../providers/codex-oauth.js';
21
+ import { resolveProviderFromCatalog } from '../providers/provider-catalog.js';
22
+ import { PROVIDERS, resolveProviderCommandKey } from './provider.js';
18
23
  export function resolveCommandProvider(options = {}) {
24
+ const settingsManager = getSettingsManager();
25
+ const settings = settingsManager.loadUserSettings();
26
+ const configuredModel = options.explicitModel || settingsManager.getCurrentModel();
19
27
  const detectFirst = Boolean(process.env.CODEBUDDY_PROVIDER);
20
28
  if (detectFirst) {
21
29
  const detected = fromEnvDetection(options.explicitModel);
22
30
  if (detected)
23
31
  return detected;
24
32
  }
25
- const settingsManager = getSettingsManager();
26
- const settings = settingsManager.loadUserSettings();
33
+ const explicitLocal = resolveExplicitOllamaModel(configuredModel);
34
+ if (explicitLocal)
35
+ return explicitLocal;
36
+ const explicitChatGpt = resolveExplicitChatGptModel(configuredModel);
37
+ if (explicitChatGpt)
38
+ return explicitChatGpt;
27
39
  const currentProviderKey = settings.provider || 'grok';
28
- const providerInfo = PROVIDERS[currentProviderKey];
29
- let apiKey = process.env[providerInfo?.envVar || ''] || '';
30
- if (!apiKey && currentProviderKey === 'grok')
31
- apiKey = process.env.XAI_API_KEY || '';
32
- if (!apiKey && currentProviderKey === 'gemini')
33
- apiKey = process.env.GOOGLE_API_KEY || '';
34
- if (!apiKey) {
35
- apiKey =
36
- process.env.GROK_API_KEY ||
37
- process.env.ANTHROPIC_API_KEY ||
38
- process.env.OPENAI_API_KEY ||
39
- process.env.GOOGLE_API_KEY ||
40
- '';
40
+ const providerInfoKey = resolveProviderCommandKey(currentProviderKey) || currentProviderKey;
41
+ const providerInfo = PROVIDERS[providerInfoKey];
42
+ if (providerInfo) {
43
+ const configuredProvider = resolveProviderFromCatalog({
44
+ providerOverride: providerInfo.providerId,
45
+ hasChatGptOAuth: hasCodexCredentials(),
46
+ requireConfigured: providerInfo.authMode === 'api-key',
47
+ });
48
+ if (configuredProvider) {
49
+ return {
50
+ apiKey: configuredProvider.apiKey,
51
+ baseURL: configuredProvider.baseURL,
52
+ model: configuredModel || configuredProvider.defaultModel,
53
+ providerLabel: configuredProvider.provider,
54
+ };
55
+ }
41
56
  }
42
- if (apiKey) {
43
- const providerEnvBaseURL = {
44
- grok: process.env.GROK_BASE_URL,
45
- claude: process.env.ANTHROPIC_BASE_URL,
46
- openai: process.env.OPENAI_BASE_URL,
47
- gemini: process.env.GEMINI_BASE_URL,
48
- };
57
+ const ambientProvider = resolveProviderFromCatalog({
58
+ hasChatGptOAuth: hasCodexCredentials(),
59
+ requireConfigured: true,
60
+ });
61
+ if (ambientProvider) {
49
62
  return {
50
- apiKey,
51
- baseURL: providerEnvBaseURL[currentProviderKey] || providerInfo?.baseURL,
52
- model: options.explicitModel ||
53
- settingsManager.getCurrentModel() ||
54
- providerInfo?.defaultModel,
55
- providerLabel: providerInfo?.name || currentProviderKey,
63
+ apiKey: ambientProvider.apiKey,
64
+ baseURL: ambientProvider.baseURL,
65
+ model: configuredModel || ambientProvider.defaultModel,
66
+ providerLabel: ambientProvider.provider,
56
67
  };
57
68
  }
58
69
  // Legacy path found nothing — fall back to env detection (local
59
70
  // Ollama, ChatGPT OAuth, …) so a $0 local machine can still run.
60
- return fromEnvDetection(options.explicitModel);
71
+ return fromEnvDetection(configuredModel);
61
72
  }
62
73
  function fromEnvDetection(explicitModel) {
63
74
  const detected = detectProviderFromEnv();
@@ -70,4 +81,47 @@ function fromEnvDetection(explicitModel) {
70
81
  providerLabel: detected.provider,
71
82
  };
72
83
  }
84
+ function resolveExplicitOllamaModel(explicitModel) {
85
+ const model = explicitModel?.trim();
86
+ if (!model || !isLocalOllamaModel(model))
87
+ return null;
88
+ let host = process.env.OLLAMA_HOST || 'http://localhost:11434';
89
+ if (!/^https?:\/\//i.test(host))
90
+ host = `http://${host}`;
91
+ if (!host.endsWith('/v1'))
92
+ host = host.replace(/\/+$/, '') + '/v1';
93
+ return {
94
+ apiKey: 'ollama',
95
+ baseURL: host,
96
+ model,
97
+ providerLabel: 'ollama',
98
+ };
99
+ }
100
+ function isLocalOllamaModel(model) {
101
+ const normalized = model.trim().toLowerCase();
102
+ return inferProvider(normalized) === 'ollama' || normalized.startsWith('devstral-small-2');
103
+ }
104
+ function resolveExplicitChatGptModel(explicitModel) {
105
+ const model = explicitModel?.trim();
106
+ if (!model || !isChatGptSubscriptionModel(model))
107
+ return null;
108
+ const detected = detectProviderFromEnv();
109
+ if (detected?.provider !== 'chatgpt')
110
+ return null;
111
+ return {
112
+ apiKey: detected.apiKey,
113
+ baseURL: detected.baseURL,
114
+ model,
115
+ providerLabel: detected.provider,
116
+ };
117
+ }
118
+ function isChatGptSubscriptionModel(model) {
119
+ const m = model.trim().toLowerCase();
120
+ return (m === 'gpt-5.2' ||
121
+ m === 'gpt-5.5' ||
122
+ m.startsWith('gpt-5.5-') ||
123
+ m.includes('-codex') ||
124
+ m === 'codex-1' ||
125
+ m.startsWith('codex-mini'));
126
+ }
73
127
  //# sourceMappingURL=llm-provider-resolution.js.map
@@ -0,0 +1,25 @@
1
+ export interface OllamaStatus {
2
+ baseUrl: string;
3
+ reachable: boolean;
4
+ version: string | null;
5
+ models: string[];
6
+ error: string | null;
7
+ }
8
+ export interface OllamaUpdatePlan {
9
+ supported: boolean;
10
+ platform: NodeJS.Platform;
11
+ repoRoot: string;
12
+ scriptPath: string;
13
+ scriptUrl: string;
14
+ command?: string;
15
+ args?: string[];
16
+ message: string;
17
+ }
18
+ export declare function normalizeOllamaBaseUrl(rawUrl?: string): string;
19
+ export declare function fetchOllamaStatus(baseUrl?: string): Promise<OllamaStatus>;
20
+ export declare function buildOllamaUpdatePlan(options: {
21
+ platform?: NodeJS.Platform;
22
+ repoRoot: string;
23
+ scriptUrl?: string;
24
+ }): OllamaUpdatePlan;
25
+ export declare function runOllamaUpdatePlan(plan: OllamaUpdatePlan): Promise<void>;
@@ -0,0 +1,100 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ export function normalizeOllamaBaseUrl(rawUrl) {
5
+ const value = (rawUrl || 'http://localhost:11434').trim();
6
+ const withoutV1 = value.replace(/\/v1\/?$/, '');
7
+ return withoutV1.replace(/\/+$/, '');
8
+ }
9
+ export async function fetchOllamaStatus(baseUrl) {
10
+ const normalizedBaseUrl = normalizeOllamaBaseUrl(baseUrl);
11
+ const apiBase = `${normalizedBaseUrl}/api`;
12
+ try {
13
+ const [versionResponse, tagsResponse] = await Promise.all([
14
+ fetch(`${apiBase}/version`, { signal: AbortSignal.timeout(5000) }),
15
+ fetch(`${apiBase}/tags`, { signal: AbortSignal.timeout(5000) }),
16
+ ]);
17
+ const versionJson = (await versionResponse.json());
18
+ const tagsJson = (await tagsResponse.json());
19
+ return {
20
+ baseUrl: normalizedBaseUrl,
21
+ reachable: versionResponse.ok && tagsResponse.ok,
22
+ version: typeof versionJson.version === 'string' ? versionJson.version : null,
23
+ models: Array.isArray(tagsJson.models)
24
+ ? tagsJson.models
25
+ .map((model) => (typeof model.name === 'string' ? model.name : null))
26
+ .filter((name) => Boolean(name))
27
+ : [],
28
+ error: versionResponse.ok && tagsResponse.ok ? null : `HTTP ${versionResponse.status}/${tagsResponse.status}`,
29
+ };
30
+ }
31
+ catch (error) {
32
+ return {
33
+ baseUrl: normalizedBaseUrl,
34
+ reachable: false,
35
+ version: null,
36
+ models: [],
37
+ error: error instanceof Error ? error.message : String(error),
38
+ };
39
+ }
40
+ }
41
+ export function buildOllamaUpdatePlan(options) {
42
+ const platform = options.platform ?? process.platform;
43
+ const repoRoot = resolve(options.repoRoot);
44
+ const scriptPath = resolve(repoRoot, 'scripts', 'update-ollama-windows.ps1');
45
+ const scriptUrl = options.scriptUrl ?? 'https://ollama.com/install.ps1';
46
+ const supported = platform === 'win32';
47
+ if (!supported) {
48
+ return {
49
+ supported,
50
+ platform,
51
+ repoRoot,
52
+ scriptPath,
53
+ scriptUrl,
54
+ message: 'Ollama update automation is implemented for Windows. Use the official install script on this platform.',
55
+ };
56
+ }
57
+ const command = 'powershell';
58
+ const args = [
59
+ '-ExecutionPolicy',
60
+ 'Bypass',
61
+ '-File',
62
+ scriptPath,
63
+ '-InstallerScriptUrl',
64
+ scriptUrl,
65
+ ];
66
+ return {
67
+ supported,
68
+ platform,
69
+ repoRoot,
70
+ scriptPath,
71
+ scriptUrl,
72
+ command,
73
+ args,
74
+ message: `Windows update script ready: ${scriptPath}`,
75
+ };
76
+ }
77
+ export async function runOllamaUpdatePlan(plan) {
78
+ if (!plan.supported || !plan.command || !plan.args) {
79
+ throw new Error(plan.message);
80
+ }
81
+ if (!existsSync(plan.scriptPath)) {
82
+ throw new Error(`Missing Ollama update script: ${plan.scriptPath}`);
83
+ }
84
+ await new Promise((resolvePromise, rejectPromise) => {
85
+ const child = spawn(plan.command, plan.args, {
86
+ stdio: 'inherit',
87
+ cwd: plan.repoRoot,
88
+ shell: false,
89
+ });
90
+ child.once('error', rejectPromise);
91
+ child.once('exit', (code) => {
92
+ if (code === 0) {
93
+ resolvePromise();
94
+ return;
95
+ }
96
+ rejectPromise(new Error(`Ollama update script exited with code ${code ?? 'unknown'}`));
97
+ });
98
+ });
99
+ }
100
+ //# sourceMappingURL=ollama.js.map
@@ -4,13 +4,18 @@
4
4
  * CLI commands for managing AI providers (Claude, ChatGPT, Grok, Gemini)
5
5
  */
6
6
  import { Command } from 'commander';
7
+ import { type RuntimeProviderCatalogEntry, type RuntimeProviderId } from '../providers/provider-catalog.js';
7
8
  interface ProviderInfo {
8
9
  name: string;
9
10
  envVar: string;
11
+ envVars: string[];
10
12
  models: string[];
11
13
  defaultModel: string;
12
14
  baseURL?: string;
15
+ providerId: RuntimeProviderId;
16
+ authMode: RuntimeProviderCatalogEntry['authMode'];
13
17
  }
14
18
  export declare const PROVIDERS: Record<string, ProviderInfo>;
15
19
  export declare function createProviderCommand(): Command;
20
+ export declare function resolveProviderCommandKey(provider: string): string | null;
16
21
  export default createProviderCommand;