@phuetz/code-buddy 1.0.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 (312) hide show
  1. package/README.md +160 -164
  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 +101 -13
  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/lesson-auto-proposer.js +10 -0
  20. package/dist/agent/middleware/index.d.ts +1 -0
  21. package/dist/agent/middleware/index.js +1 -0
  22. package/dist/agent/middleware/session-duration.d.ts +36 -0
  23. package/dist/agent/middleware/session-duration.js +78 -0
  24. package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
  25. package/dist/agent/middleware/visual-validation-middleware.js +67 -0
  26. package/dist/agent/model-benchmark.d.ts +77 -0
  27. package/dist/agent/model-benchmark.js +309 -0
  28. package/dist/agent/model-tier.d.ts +14 -0
  29. package/dist/agent/model-tier.js +71 -0
  30. package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
  31. package/dist/agent/multi-agent/multi-agent-system.js +3 -1
  32. package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
  33. package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
  34. package/dist/agent/multi-agent/session-tools.js +22 -5
  35. package/dist/agent/reasoning/mcts.js +20 -6
  36. package/dist/agent/session-end-flush.d.ts +71 -0
  37. package/dist/agent/session-end-flush.js +233 -0
  38. package/dist/agent/streaming/streaming-handler.d.ts +7 -0
  39. package/dist/agent/streaming/streaming-handler.js +8 -1
  40. package/dist/agent/tool-executor.js +1 -0
  41. package/dist/agent/tool-handler.d.ts +5 -0
  42. package/dist/agent/tool-handler.js +130 -4
  43. package/dist/browser-automation/browser-operator-executor.js +15 -0
  44. package/dist/browser-automation/browser-use-runner.d.ts +96 -0
  45. package/dist/browser-automation/browser-use-runner.js +492 -0
  46. package/dist/browser-automation/camofox-runner.d.ts +107 -0
  47. package/dist/browser-automation/camofox-runner.js +287 -0
  48. package/dist/channels/dingtalk/index.js +3 -0
  49. package/dist/channels/discord/client.d.ts +9 -0
  50. package/dist/channels/discord/client.js +12 -0
  51. package/dist/channels/feishu/index.d.ts +167 -1
  52. package/dist/channels/feishu/index.js +432 -7
  53. package/dist/channels/gateway-lifecycle.d.ts +132 -0
  54. package/dist/channels/gateway-lifecycle.js +219 -0
  55. package/dist/channels/google-chat/index.d.ts +5 -1
  56. package/dist/channels/google-chat/index.js +5 -1
  57. package/dist/channels/imessage/index.d.ts +14 -0
  58. package/dist/channels/imessage/index.js +64 -17
  59. package/dist/channels/index.d.ts +4 -0
  60. package/dist/channels/index.js +4 -0
  61. package/dist/channels/irc/index.d.ts +73 -3
  62. package/dist/channels/irc/index.js +446 -11
  63. package/dist/channels/line/index.js +3 -0
  64. package/dist/channels/mattermost/index.d.ts +51 -4
  65. package/dist/channels/mattermost/index.js +303 -20
  66. package/dist/channels/nextcloud-talk/index.d.ts +89 -8
  67. package/dist/channels/nextcloud-talk/index.js +367 -16
  68. package/dist/channels/nostr/index.d.ts +121 -1
  69. package/dist/channels/nostr/index.js +396 -8
  70. package/dist/channels/ntfy/index.js +3 -0
  71. package/dist/channels/qq/index.js +3 -0
  72. package/dist/channels/slash-parity.d.ts +117 -0
  73. package/dist/channels/slash-parity.js +185 -0
  74. package/dist/channels/synology-chat/index.js +3 -0
  75. package/dist/channels/teams/index.d.ts +3 -0
  76. package/dist/channels/teams/index.js +3 -0
  77. package/dist/channels/telegram/client.d.ts +9 -0
  78. package/dist/channels/telegram/client.js +12 -0
  79. package/dist/channels/twilio-voice/index.js +3 -0
  80. package/dist/channels/webchat/index.d.ts +7 -1
  81. package/dist/channels/webchat/index.js +7 -1
  82. package/dist/channels/wecom/index.js +3 -0
  83. package/dist/channels/weixin/index.js +3 -0
  84. package/dist/channels/zalo/index.js +3 -0
  85. package/dist/cli/config-loader.js +1 -1
  86. package/dist/codebuddy/client.d.ts +30 -1
  87. package/dist/codebuddy/client.js +167 -20
  88. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
  89. package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
  90. package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
  91. package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
  92. package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
  93. package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
  94. package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
  95. package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
  96. package/dist/codebuddy/tool-definitions/index.js +2 -1
  97. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
  98. package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
  99. package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
  100. package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
  101. package/dist/codebuddy/tools.d.ts +1 -1
  102. package/dist/codebuddy/tools.js +27 -24
  103. package/dist/commands/cli/hermes-commands.js +108 -0
  104. package/dist/commands/cli/native-engine-commands.js +107 -3
  105. package/dist/commands/cli/utility-commands.js +45 -0
  106. package/dist/commands/enhanced-command-handler.js +5 -0
  107. package/dist/commands/goal-cli.d.ts +71 -0
  108. package/dist/commands/goal-cli.js +280 -0
  109. package/dist/commands/handlers/goal-handler.d.ts +34 -0
  110. package/dist/commands/handlers/goal-handler.js +155 -0
  111. package/dist/commands/handlers/index.d.ts +1 -0
  112. package/dist/commands/handlers/index.js +2 -0
  113. package/dist/commands/handlers/infra-handlers.js +36 -0
  114. package/dist/commands/handlers/memory-handlers.js +96 -1
  115. package/dist/commands/headless-slash.d.ts +2 -0
  116. package/dist/commands/headless-slash.js +12 -0
  117. package/dist/commands/llm-provider-resolution.d.ts +5 -3
  118. package/dist/commands/llm-provider-resolution.js +87 -33
  119. package/dist/commands/ollama.d.ts +25 -0
  120. package/dist/commands/ollama.js +100 -0
  121. package/dist/commands/provider.d.ts +5 -0
  122. package/dist/commands/provider.js +106 -55
  123. package/dist/commands/slash/builtin-commands.js +20 -0
  124. package/dist/commands/spec-next.js +2 -1
  125. package/dist/commands/spec-plan.js +5 -16
  126. package/dist/commands/tunnel.d.ts +2 -0
  127. package/dist/commands/tunnel.js +48 -0
  128. package/dist/config/config-resolver.d.ts +2 -1
  129. package/dist/config/config-resolver.js +54 -41
  130. package/dist/config/constants.d.ts +28 -0
  131. package/dist/config/constants.js +7 -0
  132. package/dist/config/env-schema.js +770 -0
  133. package/dist/config/feature-flags.js +7 -0
  134. package/dist/config/model-tools.js +4 -4
  135. package/dist/config/toml-config.d.ts +16 -0
  136. package/dist/config/toml-config.js +3 -0
  137. package/dist/context/context-manager-v2.d.ts +39 -0
  138. package/dist/context/context-manager-v2.js +91 -0
  139. package/dist/daemon/agent-task-executor.js +12 -1
  140. package/dist/daemon/autonomous-daemon.d.ts +1 -1
  141. package/dist/daemon/autonomous-daemon.js +5 -3
  142. package/dist/daemon/autonomous-loop.d.ts +21 -1
  143. package/dist/daemon/autonomous-loop.js +69 -0
  144. package/dist/daemon/colab-goal.d.ts +38 -0
  145. package/dist/daemon/colab-goal.js +81 -0
  146. package/dist/daemon/cron-agent-bridge.d.ts +12 -3
  147. package/dist/daemon/cron-agent-bridge.js +25 -9
  148. package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
  149. package/dist/desktop/codebuddy-engine-adapter.js +257 -80
  150. package/dist/desktop/engine-adapter.d.ts +14 -0
  151. package/dist/desktop-automation/automation-manager.js +16 -0
  152. package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
  153. package/dist/desktop-automation/omniparser-runner.js +115 -0
  154. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  155. package/dist/desktop-automation/smart-snapshot.js +72 -5
  156. package/dist/doctor/index.js +40 -23
  157. package/dist/events/types.d.ts +11 -0
  158. package/dist/fleet/colab-store.d.ts +21 -0
  159. package/dist/fleet/colab-store.js +28 -0
  160. package/dist/fleet/peer-session-bridge.d.ts +1 -1
  161. package/dist/fleet/peer-session-bridge.js +243 -2
  162. package/dist/fleet/peer-session-store.d.ts +3 -0
  163. package/dist/fleet/peer-tool-bridge.js +14 -0
  164. package/dist/fleet/privacy-lint.d.ts +8 -0
  165. package/dist/fleet/privacy-lint.js +22 -0
  166. package/dist/goals/goal-decomposer.d.ts +27 -0
  167. package/dist/goals/goal-decomposer.js +293 -0
  168. package/dist/goals/goal-judge-client.d.ts +15 -0
  169. package/dist/goals/goal-judge-client.js +45 -0
  170. package/dist/goals/goal-judge.d.ts +38 -0
  171. package/dist/goals/goal-judge.js +158 -0
  172. package/dist/goals/goal-loop.d.ts +37 -0
  173. package/dist/goals/goal-loop.js +101 -0
  174. package/dist/goals/goal-manager.d.ts +77 -0
  175. package/dist/goals/goal-manager.js +289 -0
  176. package/dist/goals/goal-state.d.ts +99 -0
  177. package/dist/goals/goal-state.js +315 -0
  178. package/dist/goals/goal-store.d.ts +28 -0
  179. package/dist/goals/goal-store.js +87 -0
  180. package/dist/goals/index.d.ts +5 -0
  181. package/dist/goals/index.js +6 -0
  182. package/dist/hooks/use-input-handler.js +36 -1
  183. package/dist/hooks/user-hooks.js +17 -3
  184. package/dist/index.js +506 -25
  185. package/dist/input/text-to-speech.d.ts +2 -6
  186. package/dist/input/text-to-speech.js +2 -27
  187. package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
  188. package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
  189. package/dist/integrations/tailscale.d.ts +13 -0
  190. package/dist/integrations/tailscale.js +87 -34
  191. package/dist/knowledge/workspace-indexer.js +53 -9
  192. package/dist/mcp/client.js +1 -0
  193. package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
  194. package/dist/mcp/mcp-desktop-tools.js +158 -0
  195. package/dist/mcp/mcp-server.js +2 -0
  196. package/dist/memory/enhanced-memory.d.ts +10 -0
  197. package/dist/memory/enhanced-memory.js +33 -2
  198. package/dist/memory/index.d.ts +3 -1
  199. package/dist/memory/index.js +4 -1
  200. package/dist/memory/memory-auto-proposer.d.ts +23 -0
  201. package/dist/memory/memory-auto-proposer.js +308 -0
  202. package/dist/memory/memory-candidate-queue.d.ts +92 -0
  203. package/dist/memory/memory-candidate-queue.js +261 -0
  204. package/dist/memory/persistent-memory.d.ts +45 -2
  205. package/dist/memory/persistent-memory.js +236 -40
  206. package/dist/ml/bayesian-qualifier.d.ts +1 -1
  207. package/dist/ml/bayesian-qualifier.js +35 -2
  208. package/dist/observability/run-store.d.ts +1 -1
  209. package/dist/openclaw/gateway-bridge.js +5 -0
  210. package/dist/plugins/marketplace.d.ts +1 -0
  211. package/dist/plugins/marketplace.js +7 -0
  212. package/dist/prompts/execution-discipline.d.ts +14 -0
  213. package/dist/prompts/execution-discipline.js +29 -0
  214. package/dist/prompts/variation-injector.js +9 -2
  215. package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
  216. package/dist/protocols/acp/acp-agentic-runner.js +115 -18
  217. package/dist/protocols/acp/acp-session-store.d.ts +23 -0
  218. package/dist/protocols/acp/acp-session-store.js +77 -0
  219. package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
  220. package/dist/protocols/acp/acp-stdio-server.js +71 -16
  221. package/dist/providers/active-llm-registry.d.ts +37 -0
  222. package/dist/providers/active-llm-registry.js +186 -0
  223. package/dist/providers/auxiliary-provider.d.ts +25 -0
  224. package/dist/providers/auxiliary-provider.js +192 -0
  225. package/dist/providers/codex-oauth.d.ts +1 -1
  226. package/dist/providers/codex-oauth.js +27 -4
  227. package/dist/providers/index.d.ts +3 -0
  228. package/dist/providers/index.js +4 -0
  229. package/dist/providers/provider-catalog.d.ts +70 -0
  230. package/dist/providers/provider-catalog.js +738 -0
  231. package/dist/providers/provider-fallback.d.ts +35 -0
  232. package/dist/providers/provider-fallback.js +171 -0
  233. package/dist/providers/xai-oauth.d.ts +128 -0
  234. package/dist/providers/xai-oauth.js +735 -0
  235. package/dist/scheduler/cron-scheduler.d.ts +15 -3
  236. package/dist/scheduler/cron-scheduler.js +53 -7
  237. package/dist/scheduler/script-runner.d.ts +2 -0
  238. package/dist/scheduler/script-runner.js +19 -4
  239. package/dist/scheduler/watchdog-handlers.js +7 -20
  240. package/dist/search/usearch-index.js +7 -2
  241. package/dist/security/tool-policy/tool-groups.js +2 -0
  242. package/dist/server/index.js +17 -2
  243. package/dist/server/routes/mobile.d.ts +12 -4
  244. package/dist/server/routes/mobile.js +116 -1
  245. package/dist/server/tls-config.d.ts +35 -0
  246. package/dist/server/tls-config.js +142 -0
  247. package/dist/server/tunnel-manager.d.ts +20 -0
  248. package/dist/server/tunnel-manager.js +58 -0
  249. package/dist/server/websocket/fleet-bridge.d.ts +13 -1
  250. package/dist/server/websocket/fleet-bridge.js +16 -0
  251. package/dist/services/prompt-builder.d.ts +1 -0
  252. package/dist/services/prompt-builder.js +49 -8
  253. package/dist/shared/engine-types.d.ts +15 -1
  254. package/dist/sidecar/sidecar-bridge.d.ts +1 -0
  255. package/dist/sidecar/sidecar-bridge.js +21 -10
  256. package/dist/spec/spec-store.js +8 -1
  257. package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
  258. package/dist/talk-mode/providers/audioreader-tts.js +8 -8
  259. package/dist/templates/project-scaffolding.js +3 -2
  260. package/dist/themes/theme-schema.d.ts +10 -10
  261. package/dist/tools/application-profiles.js +38 -0
  262. package/dist/tools/bash/bash-tool.d.ts +1 -0
  263. package/dist/tools/bash/bash-tool.js +16 -2
  264. package/dist/tools/bash/command-validator.js +3 -0
  265. package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
  266. package/dist/tools/bash/rtk-rewrite.js +100 -0
  267. package/dist/tools/bash/streaming-executor.js +14 -1
  268. package/dist/tools/computer-control-tool.d.ts +16 -1
  269. package/dist/tools/computer-control-tool.js +317 -5
  270. package/dist/tools/document-generator.d.ts +14 -0
  271. package/dist/tools/document-generator.js +79 -0
  272. package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
  273. package/dist/tools/execute-code-rpc-invoker.js +46 -5
  274. package/dist/tools/metadata.js +28 -0
  275. package/dist/tools/office-macro-tool.d.ts +10 -0
  276. package/dist/tools/office-macro-tool.js +93 -0
  277. package/dist/tools/registry/index.d.ts +2 -1
  278. package/dist/tools/registry/index.js +2 -1
  279. package/dist/tools/registry/memory-tools.d.ts +20 -0
  280. package/dist/tools/registry/memory-tools.js +225 -3
  281. package/dist/tools/registry/multimodal-tools.js +2 -2
  282. package/dist/tools/registry/vision-tools.d.ts +33 -0
  283. package/dist/tools/registry/vision-tools.js +308 -6
  284. package/dist/tools/registry/windows-tools.d.ts +2 -0
  285. package/dist/tools/registry/windows-tools.js +47 -0
  286. package/dist/tools/route-peer-tool.js +14 -0
  287. package/dist/tools/screenshot-tool.js +14 -2
  288. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  289. package/dist/tools/text-to-speech-tool.js +2 -12
  290. package/dist/tools/tool-selector.js +3 -59
  291. package/dist/utils/config-validation/schema.d.ts +40 -7
  292. package/dist/utils/config-validation/schema.js +130 -1
  293. package/dist/utils/cost-tracker.js +21 -1
  294. package/dist/utils/disk-guard.d.ts +180 -0
  295. package/dist/utils/disk-guard.js +404 -0
  296. package/dist/utils/interactive-setup.js +35 -2
  297. package/dist/utils/model-utils.js +11 -2
  298. package/dist/utils/output-sanitizer.js +11 -0
  299. package/dist/utils/provider-detector.d.ts +8 -7
  300. package/dist/utils/provider-detector.js +19 -77
  301. package/dist/utils/settings-manager.d.ts +7 -0
  302. package/dist/utils/token-display.d.ts +3 -3
  303. package/dist/utils/token-display.js +22 -4
  304. package/dist/wizard/onboarding.d.ts +39 -0
  305. package/dist/wizard/onboarding.js +433 -21
  306. package/dist/wizard/provider-onboarding.d.ts +8 -23
  307. package/dist/wizard/provider-onboarding.js +39 -158
  308. package/package.json +4 -2
  309. package/dist/plugins/provider-onboarding.d.ts +0 -23
  310. package/dist/plugins/provider-onboarding.js +0 -116
  311. package/dist/utils/kokoro-tts.d.ts +0 -8
  312. package/dist/utils/kokoro-tts.js +0 -67
@@ -9,6 +9,16 @@ import { buildHeartbeatStatusReport } from '../../daemon/status-reports.js';
9
9
  // Heartbeat commands
10
10
  // ============================================================================
11
11
  export { buildHeartbeatStatusReport };
12
+ function parsePositiveIntegerCliOption(value, flagName) {
13
+ if (value === undefined)
14
+ return undefined;
15
+ const trimmed = value.trim();
16
+ const n = Number(trimmed);
17
+ if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(n)) {
18
+ throw new Error(`${flagName} must be a positive integer`);
19
+ }
20
+ return n;
21
+ }
12
22
  export function registerHeartbeatCommands(program) {
13
23
  const heartbeat = program
14
24
  .command('heartbeat')
@@ -745,7 +755,7 @@ export function registerFleetAutonomyCommands(program) {
745
755
  .action(async (opts) => {
746
756
  const { createDefaultAutonomousLoop, FleetAutonomousDaemon, watchFleetTasks } = await import('../../daemon/autonomous-daemon.js');
747
757
  const path = await import('path');
748
- const loop = createDefaultAutonomousLoop({
758
+ const loop = await createDefaultAutonomousLoop({
749
759
  ...(opts.dir ? { dir: opts.dir } : {}),
750
760
  ...(opts.outputDir ? { outputDir: opts.outputDir } : {}),
751
761
  });
@@ -780,6 +790,86 @@ export function registerFleetAutonomyCommands(program) {
780
790
  console.log(`\nDone: ${summary.ticks} tick(s), ${JSON.stringify(summary.outcomes)} (${summary.stoppedReason}).`);
781
791
  }
782
792
  });
793
+ fleet
794
+ .command('bench')
795
+ .description('Benchmark live Tailnet Ollama peers and rank the network model tier')
796
+ .option('--peer <pattern>', 'only benchmark peers whose hostname or IP contains the pattern')
797
+ .option('--models <csv>', 'only benchmark models whose name contains one of the comma-separated patterns')
798
+ .option('--prompt-set <name>', 'prompt set: balanced, coding, or latency', 'coding')
799
+ .option('--runs <n>', 'repeat the prompt set N times per candidate', '1')
800
+ .option('--timeout <ms>', 'request timeout per prompt', '60000')
801
+ .option('--json', 'output JSON')
802
+ .option('--no-write-cache', 'do not persist the score cache used by the router')
803
+ .action(async (opts) => {
804
+ const { TailscaleManager } = await import('../../integrations/tailscale.js');
805
+ const { BENCHMARK_PROMPT_SETS, benchmarkCandidates, writeBenchmarkIndex, defaultBenchmarkIndexPath, } = await import('../../agent/model-benchmark.js');
806
+ const peers = await TailscaleManager.getInstance().discoverOllamaPeers();
807
+ const filteredPeers = opts.peer
808
+ ? peers.filter((peer) => peer.hostname.toLowerCase().includes(opts.peer.toLowerCase())
809
+ || peer.ip.includes(opts.peer.trim()))
810
+ : peers;
811
+ const modelFilters = opts.models
812
+ ? opts.models.split(',').map((part) => part.trim()).filter(Boolean)
813
+ : [];
814
+ const candidates = filteredPeers.flatMap((peer) => peer.models
815
+ .filter((model) => modelFilters.length === 0
816
+ ? true
817
+ : modelFilters.some((filter) => model.toLowerCase().includes(filter.toLowerCase())))
818
+ .map((model) => ({
819
+ model,
820
+ baseUrl: peer.baseURL,
821
+ label: peer.hostname,
822
+ })));
823
+ if (candidates.length === 0) {
824
+ const message = opts.peer
825
+ ? `No Tailnet Ollama peers matched "${opts.peer}".`
826
+ : 'No Tailnet Ollama peers were discovered.';
827
+ if (opts.json) {
828
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
829
+ return;
830
+ }
831
+ console.log(message);
832
+ return;
833
+ }
834
+ const suite = BENCHMARK_PROMPT_SETS[opts.promptSet] ? opts.promptSet : 'coding';
835
+ const reports = await benchmarkCandidates(candidates, {
836
+ promptSet: suite,
837
+ runs: Math.max(1, parseInt(opts.runs, 10) || 1),
838
+ timeoutMs: Math.max(5_000, parseInt(opts.timeout, 10) || 60_000),
839
+ });
840
+ const ranked = [...reports].sort((a, b) => b.summary.score - a.summary.score);
841
+ let indexPath;
842
+ if (opts.writeCache !== false) {
843
+ indexPath = defaultBenchmarkIndexPath();
844
+ await writeBenchmarkIndex(ranked, suite, indexPath);
845
+ }
846
+ const best = ranked[0];
847
+ if (opts.json) {
848
+ console.log(JSON.stringify({
849
+ ok: true,
850
+ suite,
851
+ indexPath,
852
+ best,
853
+ reports: ranked,
854
+ }, null, 2));
855
+ return;
856
+ }
857
+ console.log(`\nTailnet Ollama benchmark (${suite})`);
858
+ if (indexPath)
859
+ console.log(` Cache: ${indexPath}`);
860
+ console.log(` Candidates: ${ranked.length}`);
861
+ if (best) {
862
+ console.log(` Best: ${best.candidate.label ?? best.candidate.model} / ${best.candidate.model}`);
863
+ console.log(` Score: ${best.summary.score.toFixed(1)} compliance ${(best.summary.complianceRate * 100).toFixed(0)}% ttft ${Math.round(best.summary.avgTtftMs)}ms total ${Math.round(best.summary.avgTotalMs)}ms`);
864
+ }
865
+ console.log('');
866
+ ranked.forEach((report, index) => {
867
+ console.log(`${String(index + 1).padStart(2, ' ')}. ${report.candidate.label ?? report.candidate.model} / ${report.candidate.model}`);
868
+ console.log(` ${report.candidate.baseUrl}`);
869
+ console.log(` score=${report.summary.score.toFixed(1)} compliance=${(report.summary.complianceRate * 100).toFixed(0)}% ttft=${Math.round(report.summary.avgTtftMs)}ms total=${Math.round(report.summary.avgTotalMs)}ms`);
870
+ });
871
+ console.log('');
872
+ });
783
873
  fleet
784
874
  .command('status')
785
875
  .description('Show the fleet task queue + presence')
@@ -817,9 +907,20 @@ export function registerFleetAutonomyCommands(program) {
817
907
  .option('--priority <p>', 'critical | high | medium | low', 'medium')
818
908
  .option('--depends-on <ids>', 'comma-separated task ids this task depends on')
819
909
  .option('--description <text>', 'task description')
910
+ .option('--goal-mode', 'judge-gated loop: the worker keeps going until an LLM judge confirms the task is done, then blocks for human review when the budget is spent')
911
+ .option('--goal-max-turns <n>', 'goal-mode turn budget (default 5)')
820
912
  .option('--dir <path>', 'colab dir')
821
913
  .option('--json', 'output JSON')
822
914
  .action(async (title, opts) => {
915
+ let goalMaxTurns;
916
+ try {
917
+ goalMaxTurns = parsePositiveIntegerCliOption(opts.goalMaxTurns, '--goal-max-turns');
918
+ }
919
+ catch (error) {
920
+ console.error(error instanceof Error ? error.message : String(error));
921
+ process.exit(1);
922
+ return;
923
+ }
823
924
  const { FleetColabStore } = await import('../../fleet/colab-store.js');
824
925
  const store = new FleetColabStore({ ...(opts.dir ? { dir: opts.dir } : {}) });
825
926
  const priority = ['critical', 'high', 'medium', 'low'].find((p) => p === opts.priority) ?? 'medium';
@@ -828,12 +929,15 @@ export function registerFleetAutonomyCommands(program) {
828
929
  priority,
829
930
  ...(opts.description ? { description: opts.description } : {}),
830
931
  ...(opts.dependsOn ? { dependsOn: opts.dependsOn.split(',').map((s) => s.trim()).filter(Boolean) } : {}),
932
+ ...(opts.goalMode ? { goalMode: true } : {}),
933
+ ...(goalMaxTurns !== undefined ? { goalMaxTurns } : {}),
831
934
  });
832
935
  if (opts.json) {
833
936
  console.log(JSON.stringify({ task }, null, 2));
834
937
  return;
835
938
  }
836
- console.log(`Added task ${task.id} [${task.priority}]${task.dependsOn ? ` depends on ${task.dependsOn.join(', ')}` : ''}`);
939
+ const goalNote = task.goalMode ? ` goal-mode(${task.goalMaxTurns ?? 5} turns)` : '';
940
+ console.log(`Added task ${task.id} [${task.priority}]${goalNote}${task.dependsOn ? ` depends on ${task.dependsOn.join(', ')}` : ''}`);
837
941
  });
838
942
  fleet
839
943
  .command('swarm <goal>')
@@ -1124,7 +1228,7 @@ export function registerCompanionCommands(program) {
1124
1228
  .option('--no-set-model', 'Do not set the project model to the ChatGPT companion default')
1125
1229
  .option('--language <lang>', 'Voice language', 'fr')
1126
1230
  .option('--stt-provider <provider>', 'Voice input provider (system, whisper-local, whisper-api)')
1127
- .option('--tts-provider <provider>', 'TTS provider (edge-tts, espeak, say, piper, audioreader, kokoro)')
1231
+ .option('--tts-provider <provider>', 'TTS provider (edge-tts, espeak, say, piper, audioreader)')
1128
1232
  .option('--tts-voice <voice>', 'TTS voice name')
1129
1233
  .option('--model <model>', 'ChatGPT model to use when OAuth credentials are present')
1130
1234
  .action(async (opts) => {
@@ -4,6 +4,7 @@
4
4
  * Extracted from index.ts for modularity.
5
5
  */
6
6
  import { resolve } from 'path';
7
+ import { buildOllamaUpdatePlan, fetchOllamaStatus, runOllamaUpdatePlan } from '../ollama.js';
7
8
  function resolveCommandDirectory(program) {
8
9
  const directory = program.opts().directory;
9
10
  return resolve(process.cwd(), directory || process.cwd());
@@ -93,6 +94,50 @@ export function registerUtilityCommands(program) {
93
94
  const { runOnboarding } = await import('../../wizard/onboarding.js');
94
95
  await runOnboarding();
95
96
  });
97
+ const ollamaCommand = program
98
+ .command('ollama')
99
+ .description('Inspect or update the local Ollama runtime');
100
+ ollamaCommand
101
+ .command('status')
102
+ .description('Show local Ollama version and models')
103
+ .option('--url <url>', 'Ollama base URL', process.env.OLLAMA_HOST ?? 'http://localhost:11434')
104
+ .option('--json', 'Output JSON')
105
+ .action(async (options) => {
106
+ const status = await fetchOllamaStatus(options.url);
107
+ if (options.json) {
108
+ console.log(JSON.stringify(status, null, 2));
109
+ return;
110
+ }
111
+ console.log('\nOllama status\n');
112
+ console.log(` Base URL: ${status.baseUrl}`);
113
+ console.log(` Reachable: ${status.reachable ? 'YES' : 'NO'}`);
114
+ console.log(` Version: ${status.version ?? '(unknown)'}`);
115
+ console.log(` Models: ${status.models.length > 0 ? status.models.join(', ') : '(none)'}`);
116
+ if (status.error) {
117
+ console.log(` Error: ${status.error}`);
118
+ }
119
+ console.log('');
120
+ });
121
+ ollamaCommand
122
+ .command('update')
123
+ .description('Run the official Ollama Windows update script')
124
+ .option('--script-url <url>', 'Official installer/update script URL', 'https://ollama.com/install.ps1')
125
+ .option('--repo-root <dir>', 'Repository root', resolveCommandDirectory(program))
126
+ .action(async (options) => {
127
+ const plan = buildOllamaUpdatePlan({
128
+ repoRoot: options.repoRoot,
129
+ scriptUrl: options.scriptUrl,
130
+ });
131
+ if (!plan.supported) {
132
+ console.error(plan.message);
133
+ console.error(`Use the update script directly on Windows: ${plan.scriptPath}`);
134
+ process.exit(1);
135
+ return;
136
+ }
137
+ console.log(`Running Ollama updater: ${plan.scriptPath}`);
138
+ await runOllamaUpdatePlan(plan);
139
+ console.log('Ollama update completed.');
140
+ });
96
141
  // Webhook command
97
142
  const webhookCommand = program
98
143
  .command('webhook')
@@ -62,6 +62,8 @@ handleFastMode,
62
62
  handleBtw, setBtwClient,
63
63
  // Heartbeat handler (V4.x — wire user-facing activation of HeartbeatEngine)
64
64
  handleHeartbeat,
65
+ // Goal handler (Hermes Agent parity — Ralph loop)
66
+ handleGoal, handleSubgoal,
65
67
  // Daily reset handler (audit OpenClaw heritage activation)
66
68
  handleDailyReset,
67
69
  // Team session handler — slash /share (audit OpenClaw heritage activation, TeamSessionManager wake)
@@ -337,6 +339,9 @@ export class EnhancedCommandHandler {
337
339
  ['__BTW__', (args) => handleBtw(args)],
338
340
  // Heartbeat engine activation (fleet AUTONOMOUS-FLEET-PROTOCOL v0.1)
339
341
  ['__HEARTBEAT__', (args) => handleHeartbeat(args)],
342
+ // Standing goal — judge + auto-continue loop (Hermes Agent parity)
343
+ ['__GOAL__', (args) => handleGoal(args, { client: this.codebuddyClient })],
344
+ ['__SUBGOAL__', (args) => handleSubgoal(args)],
340
345
  // Daily reset scheduler (audit OpenClaw heritage activation)
341
346
  ['__DAILY_RESET__', (args) => handleDailyReset(args)],
342
347
  // Team session manager — slash /share (audit OpenClaw heritage, TeamSessionManager wake)
@@ -0,0 +1,71 @@
1
+ /**
2
+ * buddy goal — headless Ralph loop.
3
+ *
4
+ * Runs the full agentic loop toward a standing goal: each turn the agent
5
+ * works with tools, then the goal judge decides done/continue. Continuation
6
+ * prompts are fed back in-process until the goal is achieved, the turn
7
+ * budget is exhausted, or the judge auto-pauses.
8
+ *
9
+ * Usage:
10
+ * buddy goal "Fix every failing test in tests/auth/"
11
+ * buddy goal "Ship the feature" --max-turns 10 --judge-model qwen3:8b
12
+ *
13
+ * Exit codes: 0 = goal done, 1 = paused (budget/judge) or error.
14
+ */
15
+ import { Command } from 'commander';
16
+ import type { ChatEntry } from '../agent/codebuddy-agent.js';
17
+ import type { CodeBuddyClient } from '../codebuddy/client.js';
18
+ import { type GoalJudgeProviderInfo } from '../goals/goal-judge-client.js';
19
+ import { GoalStatus } from '../goals/goal-state.js';
20
+ /** The slice of CodeBuddyAgent the loop needs — injectable for tests. */
21
+ export interface GoalLoopAgent {
22
+ processUserMessage(input: string): Promise<ChatEntry[]>;
23
+ getClient(): CodeBuddyClient;
24
+ }
25
+ export interface GoalCommandRuntimeAgent extends GoalLoopAgent {
26
+ systemPromptReady: Promise<unknown>;
27
+ setSystemPrompt?: (prompt: string) => void;
28
+ dispose?: () => void | Promise<void>;
29
+ }
30
+ export interface GoalLoopRunOptions {
31
+ maxTurns?: number;
32
+ /** Optional standalone judge client. Defaults to the agent client. */
33
+ judgeClient?: CodeBuddyClient;
34
+ /** Progress sink (status lines ⊙/↻/✓/⏸). Defaults to silent. */
35
+ onMessage?: (text: string) => void;
36
+ }
37
+ export interface GoalLoopRunResult {
38
+ status: GoalStatus | 'unknown';
39
+ turnsUsed: number;
40
+ lastReason?: string;
41
+ }
42
+ export interface GoalCommandRuntimeOptions {
43
+ maxTurns?: number;
44
+ judgeModel?: string;
45
+ provider: GoalJudgeProviderInfo;
46
+ workingDirectory?: string;
47
+ onMessage?: (text: string) => void;
48
+ createJudgeClient?: typeof createStandaloneJudgeClient;
49
+ runLoop?: typeof runGoalLoop;
50
+ }
51
+ export declare const LOCAL_GOAL_ACTOR_SYSTEM_PROMPT: string;
52
+ export declare function buildLocalGoalActorSystemPrompt(workingDirectory?: string): string;
53
+ export declare function shouldUseLocalGoalActorPrompt(provider: GoalJudgeProviderInfo): boolean;
54
+ export declare function resolveLocalGoalActorSystemPrompt(provider: GoalJudgeProviderInfo, workingDirectory?: string): string | undefined;
55
+ /**
56
+ * Drive the goal loop headlessly on an in-process agent. Sets the goal,
57
+ * runs the first turn with the goal text (mirroring the interactive
58
+ * `/goal <text>` kick-off), then follows judge verdicts until the loop
59
+ * stops continuing.
60
+ */
61
+ export declare function runGoalLoop(agent: GoalLoopAgent, goalText: string, options?: GoalLoopRunOptions): Promise<GoalLoopRunResult>;
62
+ declare function createStandaloneJudgeClient(judgeModel: string | undefined, agentProvider: GoalJudgeProviderInfo, currentClient: CodeBuddyClient): Promise<CodeBuddyClient | undefined>;
63
+ export declare function runGoalCommandWithAgent(agent: GoalCommandRuntimeAgent, goal: string, options: GoalCommandRuntimeOptions): Promise<GoalLoopRunResult>;
64
+ export declare function resolveGoalCliJudgeModel(explicitJudgeModel: string | undefined): string | undefined;
65
+ export declare function resolveGoalCliMaxToolRounds(commandValue: number | undefined, command?: Command): number;
66
+ export declare function resolveGoalCliWorkingDirectory(command?: Command): string | undefined;
67
+ export declare function applyGoalCliWorkingDirectory(command?: Command): string;
68
+ export declare function parsePositiveIntegerOption(value: string, optionName: string): number;
69
+ export declare function validateGoalCommandNumericOptions(argv: readonly string[]): void;
70
+ export declare function createGoalCommand(): Command;
71
+ export {};
@@ -0,0 +1,280 @@
1
+ /**
2
+ * buddy goal — headless Ralph loop.
3
+ *
4
+ * Runs the full agentic loop toward a standing goal: each turn the agent
5
+ * works with tools, then the goal judge decides done/continue. Continuation
6
+ * prompts are fed back in-process until the goal is achieved, the turn
7
+ * budget is exhausted, or the judge auto-pauses.
8
+ *
9
+ * Usage:
10
+ * buddy goal "Fix every failing test in tests/auth/"
11
+ * buddy goal "Ship the feature" --max-turns 10 --judge-model qwen3:8b
12
+ *
13
+ * Exit codes: 0 = goal done, 1 = paused (budget/judge) or error.
14
+ */
15
+ import { Command, InvalidArgumentError } from 'commander';
16
+ import path from 'path';
17
+ import { resolveGoalJudgeClient } from '../goals/goal-judge-client.js';
18
+ import { maybeContinueGoalAfterTurn } from '../goals/goal-loop.js';
19
+ import { getGoalManager, resolveGoalsConfig } from '../goals/goal-manager.js';
20
+ import { resolveCommandProvider } from './llm-provider-resolution.js';
21
+ const TOOL_RESULT_SNIPPET_CHARS = 1600;
22
+ export const LOCAL_GOAL_ACTOR_SYSTEM_PROMPT = 'You are Code Buddy running in headless goal mode on a local model. ' +
23
+ 'Work only toward the user standing goal. Use available tools when they are necessary, ' +
24
+ 'then report concrete progress or the final result. For simple answer-only goals, ' +
25
+ 'output exactly the requested answer and no extra text. Do not greet the user, add markdown, ' +
26
+ 'or explain unless the goal asks for it.';
27
+ export function buildLocalGoalActorSystemPrompt(workingDirectory = process.cwd()) {
28
+ return `${LOCAL_GOAL_ACTOR_SYSTEM_PROMPT}
29
+
30
+ Workspace:
31
+ - Current project directory: ${workingDirectory}
32
+ - Use paths relative to the current project directory whenever possible.
33
+ - Do not invent container paths such as /workspace or root paths such as /src unless a tool result proves they exist.`;
34
+ }
35
+ export function shouldUseLocalGoalActorPrompt(provider) {
36
+ const label = provider.providerLabel?.toLowerCase() ?? '';
37
+ const baseURL = provider.baseURL?.toLowerCase() ?? '';
38
+ return provider.apiKey === 'ollama' || label.includes('ollama') || baseURL.includes(':11434');
39
+ }
40
+ export function resolveLocalGoalActorSystemPrompt(provider, workingDirectory = process.cwd()) {
41
+ return shouldUseLocalGoalActorPrompt(provider)
42
+ ? buildLocalGoalActorSystemPrompt(workingDirectory)
43
+ : undefined;
44
+ }
45
+ function truncateGoalTurnPart(text) {
46
+ if (text.length <= TOOL_RESULT_SNIPPET_CHARS)
47
+ return text;
48
+ return `${text.slice(0, TOOL_RESULT_SNIPPET_CHARS)}... [truncated]`;
49
+ }
50
+ function buildGoalTurnSummary(entries) {
51
+ const parts = [];
52
+ let hasToolResult = false;
53
+ for (const entry of entries) {
54
+ if (entry.type === 'assistant' && entry.content.trim()) {
55
+ parts.push(entry.content.trim());
56
+ continue;
57
+ }
58
+ if (entry.type !== 'tool_result')
59
+ continue;
60
+ hasToolResult = true;
61
+ const toolOutput = (entry.toolResult?.success ? entry.toolResult.output : entry.toolResult?.error) ??
62
+ entry.content;
63
+ const trimmedOutput = String(toolOutput ?? '').trim();
64
+ if (!trimmedOutput)
65
+ continue;
66
+ const toolName = entry.toolCall?.function?.name ?? 'tool';
67
+ const status = entry.toolResult ? (entry.toolResult.success ? 'success' : 'error') : 'result';
68
+ parts.push(`[tool:${toolName} ${status}]\n${truncateGoalTurnPart(trimmedOutput)}`);
69
+ }
70
+ const summary = parts.join('\n\n');
71
+ if (summary && !hasToolResult) {
72
+ return `[tool evidence: none]\n\n${summary}`;
73
+ }
74
+ return summary;
75
+ }
76
+ function buildGoalRunSummary(turnSummaries) {
77
+ return turnSummaries
78
+ .map((summary, index) => `[Goal turn ${index + 1}]\n${summary}`)
79
+ .join('\n\n');
80
+ }
81
+ /**
82
+ * Drive the goal loop headlessly on an in-process agent. Sets the goal,
83
+ * runs the first turn with the goal text (mirroring the interactive
84
+ * `/goal <text>` kick-off), then follows judge verdicts until the loop
85
+ * stops continuing.
86
+ */
87
+ export async function runGoalLoop(agent, goalText, options = {}) {
88
+ const manager = getGoalManager();
89
+ const state = manager.set(goalText, options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {});
90
+ const emit = options.onMessage ?? (() => { });
91
+ const judgeClient = options.judgeClient ?? agent.getClient();
92
+ emit(`⊙ Goal set (${state.maxTurns}-turn budget): ${state.goal}`);
93
+ let prompt = state.goal;
94
+ const turnSummaries = [];
95
+ // Hard backstop on top of the manager's own budget/auto-pause guards.
96
+ const maxIterations = state.maxTurns + 1;
97
+ for (let i = 0; i < maxIterations; i++) {
98
+ const entries = await agent.processUserMessage(prompt);
99
+ const lastResponse = buildGoalTurnSummary(entries);
100
+ if (lastResponse.trim()) {
101
+ turnSummaries.push(lastResponse);
102
+ }
103
+ const outcome = await maybeContinueGoalAfterTurn({
104
+ client: judgeClient,
105
+ lastResponse: lastResponse.trim() ? buildGoalRunSummary(turnSummaries) : lastResponse,
106
+ interrupted: false,
107
+ });
108
+ if (outcome?.message)
109
+ emit(outcome.message);
110
+ if (!outcome && !lastResponse.trim() && manager.isActive()) {
111
+ manager.pause('empty response (nothing to evaluate)');
112
+ emit('⏸ Goal paused — the agent produced no judgeable response.');
113
+ break;
114
+ }
115
+ if (!outcome?.continuationPrompt)
116
+ break;
117
+ prompt = outcome.continuationPrompt;
118
+ }
119
+ const final = manager.state;
120
+ return {
121
+ status: final?.status ?? 'unknown',
122
+ turnsUsed: final?.turnsUsed ?? 0,
123
+ ...(final?.lastReason ? { lastReason: final.lastReason } : {}),
124
+ };
125
+ }
126
+ async function createStandaloneJudgeClient(judgeModel, agentProvider, currentClient) {
127
+ const resolved = await resolveGoalJudgeClient(currentClient, judgeModel, agentProvider);
128
+ return resolved === currentClient ? undefined : resolved;
129
+ }
130
+ export async function runGoalCommandWithAgent(agent, goal, options) {
131
+ try {
132
+ const createJudgeClient = options.createJudgeClient ?? createStandaloneJudgeClient;
133
+ const runLoop = options.runLoop ?? runGoalLoop;
134
+ const judgeClient = await createJudgeClient(options.judgeModel, options.provider, agent.getClient());
135
+ await agent.systemPromptReady;
136
+ if (shouldUseLocalGoalActorPrompt(options.provider)) {
137
+ agent.setSystemPrompt?.(buildLocalGoalActorSystemPrompt(options.workingDirectory));
138
+ }
139
+ return await runLoop(agent, goal, {
140
+ ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
141
+ ...(judgeClient ? { judgeClient } : {}),
142
+ ...(options.onMessage ? { onMessage: options.onMessage } : {}),
143
+ });
144
+ }
145
+ finally {
146
+ await Promise.resolve(agent.dispose?.());
147
+ }
148
+ }
149
+ export function resolveGoalCliJudgeModel(explicitJudgeModel) {
150
+ const explicit = explicitJudgeModel?.trim();
151
+ if (explicit)
152
+ return explicit;
153
+ const configured = resolveGoalsConfig().judgeModel.trim();
154
+ return configured || undefined;
155
+ }
156
+ export function resolveGoalCliMaxToolRounds(commandValue, command) {
157
+ const commandSource = command?.getOptionValueSource?.('maxToolRounds');
158
+ if (commandSource && commandSource !== 'default') {
159
+ return commandValue ?? 50;
160
+ }
161
+ const parentSource = command?.parent?.getOptionValueSource?.('maxToolRounds');
162
+ const parentValue = command?.optsWithGlobals?.()?.maxToolRounds;
163
+ if (parentSource && parentSource !== 'default' && parentValue !== undefined) {
164
+ return parsePositiveIntegerOption(String(parentValue), '--max-tool-rounds');
165
+ }
166
+ return commandValue ?? 50;
167
+ }
168
+ export function resolveGoalCliWorkingDirectory(command) {
169
+ const value = command?.optsWithGlobals?.()?.directory;
170
+ if (typeof value !== 'string')
171
+ return undefined;
172
+ const directory = value.trim();
173
+ if (!directory)
174
+ return undefined;
175
+ return path.resolve(process.cwd(), directory);
176
+ }
177
+ export function applyGoalCliWorkingDirectory(command) {
178
+ const directory = resolveGoalCliWorkingDirectory(command);
179
+ if (directory) {
180
+ process.chdir(directory);
181
+ }
182
+ return process.cwd();
183
+ }
184
+ async function loadGoalCliEnv(directory) {
185
+ const dotenv = await import('dotenv');
186
+ dotenv.config({ path: path.join(directory, '.env') });
187
+ }
188
+ async function prepareGoalCliWorkspace(command) {
189
+ const launchDirectory = process.cwd();
190
+ await loadGoalCliEnv(launchDirectory);
191
+ const cwd = applyGoalCliWorkingDirectory(command);
192
+ if (cwd !== launchDirectory) {
193
+ await loadGoalCliEnv(cwd);
194
+ }
195
+ const opts = command?.optsWithGlobals?.();
196
+ const { initializeWorkspaceIsolation } = await import('../workspace/workspace-isolation.js');
197
+ initializeWorkspaceIsolation({
198
+ allowOutside: opts?.allowOutside,
199
+ directory: cwd,
200
+ additionalPaths: opts?.addDir,
201
+ });
202
+ return cwd;
203
+ }
204
+ export function parsePositiveIntegerOption(value, optionName) {
205
+ const trimmed = value.trim();
206
+ const n = Number(trimmed);
207
+ if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(n)) {
208
+ throw new InvalidArgumentError(`${optionName} must be a positive integer`);
209
+ }
210
+ return n;
211
+ }
212
+ export function validateGoalCommandNumericOptions(argv) {
213
+ for (let i = 2; i < argv.length; i++) {
214
+ const arg = argv[i];
215
+ if (arg === undefined)
216
+ continue;
217
+ if (arg === '--')
218
+ break;
219
+ if (arg === '--max-turns' || arg === '--max-tool-rounds') {
220
+ const value = argv[i + 1];
221
+ if (value === undefined)
222
+ continue;
223
+ parsePositiveIntegerOption(value, arg);
224
+ i++;
225
+ continue;
226
+ }
227
+ const eqIndex = arg?.indexOf('=') ?? -1;
228
+ if (eqIndex <= 0)
229
+ continue;
230
+ const optionName = arg.slice(0, eqIndex);
231
+ if (optionName === '--max-turns' || optionName === '--max-tool-rounds') {
232
+ parsePositiveIntegerOption(arg.slice(eqIndex + 1), optionName);
233
+ }
234
+ }
235
+ }
236
+ export function createGoalCommand() {
237
+ const cmd = new Command('goal')
238
+ .description('Run the agent toward a standing goal until a judge model confirms it is done (Ralph loop)')
239
+ .argument('<goal>', 'The goal to pursue')
240
+ .option('--max-turns <n>', 'Turn budget (default 20, or goals.maxTurns from settings)', value => parsePositiveIntegerOption(value, '--max-turns'))
241
+ .option('--judge-model <model>', 'Model for the goal judge (default: session model)')
242
+ .option('-m, --model <model>', 'Override the agent model for this run')
243
+ .option('--max-tool-rounds <n>', 'Max tool rounds per turn', value => parsePositiveIntegerOption(value, '--max-tool-rounds'), 50)
244
+ .action(async (goal, options, command) => {
245
+ try {
246
+ const cwd = await prepareGoalCliWorkspace(command);
247
+ const modelOverride = options.model ?? command?.optsWithGlobals?.()?.model;
248
+ const resolved = resolveCommandProvider({ explicitModel: modelOverride });
249
+ if (!resolved) {
250
+ console.error('Error: No provider available — set an API key, run `buddy onboard`, or point CODEBUDDY_PROVIDER=ollama at a local Ollama.');
251
+ process.exit(1);
252
+ }
253
+ if (options.judgeModel) {
254
+ process.env.CODEBUDDY_GOAL_JUDGE_MODEL = options.judgeModel;
255
+ }
256
+ const judgeModel = resolveGoalCliJudgeModel(options.judgeModel);
257
+ const maxToolRounds = resolveGoalCliMaxToolRounds(options.maxToolRounds, command);
258
+ process.env.CODEBUDDY_DISABLE_MCP = process.env.CODEBUDDY_DISABLE_MCP ?? 'true';
259
+ process.env.CODEBUDDY_HEADLESS = 'true';
260
+ const { CodeBuddyAgent } = await import('../agent/codebuddy-agent.js');
261
+ const { ConfirmationService } = await import('../utils/confirmation-service.js');
262
+ ConfirmationService.getInstance().setSessionFlag('allOperations', true);
263
+ const agent = new CodeBuddyAgent(resolved.apiKey, resolved.baseURL, resolved.model, maxToolRounds, true, undefined, cwd, undefined, resolveLocalGoalActorSystemPrompt(resolved, cwd));
264
+ const result = await runGoalCommandWithAgent(agent, goal, {
265
+ provider: resolved,
266
+ judgeModel,
267
+ workingDirectory: cwd,
268
+ ...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
269
+ onMessage: text => console.log(`\n${text}`),
270
+ });
271
+ process.exit(result.status === 'done' ? 0 : 1);
272
+ }
273
+ catch (err) {
274
+ console.error('Goal error:', err instanceof Error ? err.message : err);
275
+ process.exit(1);
276
+ }
277
+ });
278
+ return cmd;
279
+ }
280
+ //# sourceMappingURL=goal-cli.js.map
@@ -0,0 +1,34 @@
1
+ import type { CodeBuddyClient } from '../../codebuddy/client.js';
2
+ import { type GoalPlan } from '../../goals/goal-decomposer.js';
3
+ import { CommandHandlerResult } from './branch-handlers.js';
4
+ export interface GoalHandlerOptions {
5
+ sessionKey?: string;
6
+ client?: CodeBuddyClient | null;
7
+ planner?: (goal: string, client: CodeBuddyClient) => Promise<GoalPlan | null>;
8
+ }
9
+ /**
10
+ * /goal — standing goal with judge + auto-continue loop (the Ralph loop,
11
+ * ported from Hermes Agent).
12
+ *
13
+ * Forms:
14
+ * /goal <text> set a new goal (replaces any old one) and start turn 1
15
+ * /goal | status show current state
16
+ * /goal pause halt auto-continuation, keep the goal
17
+ * /goal resume restart the loop (resets the turn budget)
18
+ * /goal clear discard the goal (aliases: stop, done)
19
+ */
20
+ export declare function handleGoal(args: string[], options?: GoalHandlerOptions): Promise<CommandHandlerResult>;
21
+ /**
22
+ * /subgoal — extra acceptance criteria added mid-loop.
23
+ *
24
+ * Forms:
25
+ * /subgoal show current subgoals
26
+ * /subgoal <text> append a criterion
27
+ * /subgoal remove <n> drop subgoal n (1-based)
28
+ * /subgoal clear wipe all subgoals
29
+ *
30
+ * Subgoals get appended to both the judge prompt (verdict must consider
31
+ * them) and the continuation prompt (agent sees them) on the next turn
32
+ * boundary — no special kick needed.
33
+ */
34
+ export declare function handleSubgoal(args: string[], options?: GoalHandlerOptions): Promise<CommandHandlerResult>;