@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
@@ -13,7 +13,7 @@ import path from 'path';
13
13
  import { getSettingsHierarchy } from '../config/settings-hierarchy.js';
14
14
  import { getSessionStore } from '../persistence/session-store.js';
15
15
  import { logger } from '../utils/logger.js';
16
- import { DEFAULT_JUDGE_MAX_TOKENS, DEFAULT_JUDGE_TIMEOUT_MS, DEFAULT_MAX_TURNS, applyJudgeOutcome, buildContinuationPrompt, createGoalState, formatGoalStatusLine, renderSubgoalsBlock, } from './goal-state.js';
16
+ import { DEFAULT_JUDGE_MAX_TOKENS, DEFAULT_JUDGE_TIMEOUT_MS, DEFAULT_MAX_TURNS, applyJudgeOutcome, buildContinuationPrompt, createGoalState, formatGoalStatusLine, getGoalJudgeCriteria, renderSubgoalsBlock, } from './goal-state.js';
17
17
  import { GoalStore } from './goal-store.js';
18
18
  export class GoalManager {
19
19
  sessionKey;
@@ -47,13 +47,40 @@ export class GoalManager {
47
47
  if (!text) {
48
48
  throw new Error('goal text is empty');
49
49
  }
50
- const state = createGoalState(text, options.maxTurns || this.defaultMaxTurns);
50
+ const state = createGoalState(text, options.maxTurns ?? this.defaultMaxTurns);
51
+ if (options.goalPlan)
52
+ state.goalPlan = options.goalPlan;
53
+ if (typeof options.goalPlanAttempted === 'boolean') {
54
+ state.goalPlanAttempted = options.goalPlanAttempted;
55
+ }
51
56
  this._state = state;
52
57
  this.store.save(this.sessionKey, state);
53
58
  return state;
54
59
  }
60
+ attachGoalPlan(plan) {
61
+ if (!this.hasGoal() || !this._state)
62
+ return null;
63
+ this._state.goalPlan = plan;
64
+ this._state.goalPlanAttempted = true;
65
+ delete this._state.goalPlanLastError;
66
+ this.store.save(this.sessionKey, this._state);
67
+ return this._state;
68
+ }
69
+ markGoalPlanAttempted(error) {
70
+ if (!this.hasGoal() || !this._state)
71
+ return null;
72
+ this._state.goalPlanAttempted = true;
73
+ if (error) {
74
+ this._state.goalPlanLastError = error;
75
+ }
76
+ else {
77
+ delete this._state.goalPlanLastError;
78
+ }
79
+ this.store.save(this.sessionKey, this._state);
80
+ return this._state;
81
+ }
55
82
  pause(reason = 'user-paused') {
56
- if (!this._state)
83
+ if (!this._state || !['active', 'paused'].includes(this._state.status))
57
84
  return null;
58
85
  this._state.status = 'paused';
59
86
  this._state.pausedReason = reason;
@@ -61,10 +88,13 @@ export class GoalManager {
61
88
  return this._state;
62
89
  }
63
90
  resume(options = {}) {
64
- if (!this._state)
91
+ if (!this._state || this._state.status !== 'paused')
65
92
  return null;
66
93
  this._state.status = 'active';
67
94
  delete this._state.pausedReason;
95
+ delete this._state.lastVerdict;
96
+ delete this._state.lastReason;
97
+ this._state.consecutiveParseFailures = 0;
68
98
  if (options.resetBudget ?? true) {
69
99
  this._state.turnsUsed = 0;
70
100
  }
@@ -103,7 +133,10 @@ export class GoalManager {
103
133
  if (!this.hasGoal() || !this._state) {
104
134
  throw new Error('no active goal');
105
135
  }
106
- const idx = Math.trunc(index1Based) - 1;
136
+ if (!Number.isSafeInteger(index1Based) || index1Based < 1) {
137
+ throw new Error('index must be a positive integer');
138
+ }
139
+ const idx = index1Based - 1;
107
140
  if (idx < 0 || idx >= this._state.subgoals.length) {
108
141
  throw new Error(`index out of range (1..${this._state.subgoals.length})`);
109
142
  }
@@ -145,10 +178,11 @@ export class GoalManager {
145
178
  message: '',
146
179
  };
147
180
  }
181
+ const criteria = getGoalJudgeCriteria(state);
148
182
  const outcome = await deps.judge({
149
183
  goal: state.goal,
150
184
  lastResponse,
151
- ...(state.subgoals.length ? { subgoals: state.subgoals } : {}),
185
+ ...(criteria.length ? { subgoals: criteria } : {}),
152
186
  });
153
187
  const decision = applyJudgeOutcome(state, outcome);
154
188
  this.store.save(this.sessionKey, state);
@@ -166,7 +200,9 @@ export class GoalManager {
166
200
  export function resolveGoalsConfig() {
167
201
  let raw = {};
168
202
  try {
169
- const settings = getSettingsHierarchy().getAllSettings();
203
+ const hierarchy = getSettingsHierarchy(process.cwd());
204
+ hierarchy.loadAllLevels?.();
205
+ const settings = hierarchy.getAllSettings();
170
206
  if (settings && typeof settings.goals === 'object' && settings.goals !== null) {
171
207
  raw = settings.goals;
172
208
  }
@@ -174,21 +210,38 @@ export function resolveGoalsConfig() {
174
210
  catch (error) {
175
211
  logger.debug('goals: settings hierarchy unavailable, using defaults', { error: String(error) });
176
212
  }
177
- const envMaxTurns = Number(process.env.CODEBUDDY_GOAL_MAX_TURNS);
178
- const maxTurns = Number.isFinite(envMaxTurns) && envMaxTurns > 0
179
- ? Math.trunc(envMaxTurns)
180
- : positiveInt(raw.maxTurns, DEFAULT_MAX_TURNS);
181
- const judgeModel = process.env.CODEBUDDY_GOAL_JUDGE_MODEL || String(raw.judgeModel ?? '').trim();
213
+ const maxTurns = positiveInt(process.env.CODEBUDDY_GOAL_MAX_TURNS, 0)
214
+ || positiveInt(raw.maxTurns, DEFAULT_MAX_TURNS);
215
+ const judgeModel = nonEmptyString(process.env.CODEBUDDY_GOAL_JUDGE_MODEL)
216
+ || nonEmptyString(raw.judgeModel);
217
+ const plannerModel = nonEmptyString(process.env.CODEBUDDY_GOAL_PLANNER_MODEL)
218
+ || nonEmptyString(raw.plannerModel);
182
219
  return {
183
220
  maxTurns,
184
221
  judgeModel,
222
+ plannerModel,
185
223
  judgeMaxTokens: positiveInt(raw.judgeMaxTokens, DEFAULT_JUDGE_MAX_TOKENS),
186
224
  judgeTimeoutMs: positiveInt(raw.judgeTimeoutMs, DEFAULT_JUDGE_TIMEOUT_MS),
187
225
  };
188
226
  }
189
227
  function positiveInt(value, fallback) {
190
- const n = Number(value);
191
- return Number.isFinite(n) && n > 0 ? Math.trunc(n) : fallback;
228
+ const n = parsePositiveSafeInteger(value);
229
+ return Number.isSafeInteger(n) && n > 0 ? n : fallback;
230
+ }
231
+ function nonEmptyString(value) {
232
+ if (typeof value !== 'string')
233
+ return '';
234
+ return value.trim();
235
+ }
236
+ function parsePositiveSafeInteger(value) {
237
+ if (typeof value === 'number')
238
+ return value;
239
+ if (typeof value !== 'string')
240
+ return Number.NaN;
241
+ const trimmed = value.trim();
242
+ if (!/^[1-9]\d*$/.test(trimmed))
243
+ return Number.NaN;
244
+ return Number(trimmed);
192
245
  }
193
246
  // ============================================================================
194
247
  // Singleton registry (one manager per session key)
@@ -16,6 +16,7 @@
16
16
  * - A real user message mid-loop preempts the continuation prompt; the judge
17
17
  * re-runs after that turn.
18
18
  */
19
+ import type { GoalPlan } from './goal-decomposer.js';
19
20
  export type GoalStatus = 'active' | 'paused' | 'done' | 'cleared';
20
21
  export type GoalVerdict = 'done' | 'continue' | 'skipped';
21
22
  export interface GoalState {
@@ -37,6 +38,16 @@ export interface GoalState {
37
38
  * empty so old persisted state loads unchanged.
38
39
  */
39
40
  subgoals: string[];
41
+ /**
42
+ * Optional Hermes-style decomposition graph generated for complex goals.
43
+ * Its acceptance criteria are fed to the judge alongside user-added
44
+ * subgoals, while remaining separate so /subgoal stays a manual control.
45
+ */
46
+ goalPlan?: GoalPlan;
47
+ /** True once an LLM planning attempt was made for this goal. */
48
+ goalPlanAttempted?: boolean;
49
+ /** Last planning failure, kept for status/debug without retrying forever. */
50
+ goalPlanLastError?: string;
40
51
  }
41
52
  export declare const DEFAULT_MAX_TURNS = 20;
42
53
  export declare const DEFAULT_JUDGE_TIMEOUT_MS = 30000;
@@ -47,12 +58,14 @@ export declare const JUDGE_RESPONSE_SNIPPET_CHARS = 4000;
47
58
  export declare const MAX_CONSECUTIVE_PARSE_FAILURES = 3;
48
59
  export declare const CONTINUATION_PROMPT_TEMPLATE: string;
49
60
  export declare const CONTINUATION_PROMPT_WITH_SUBGOALS_TEMPLATE: string;
61
+ export declare const CONTINUATION_PROMPT_WITH_PLAN_TEMPLATE: string;
50
62
  export declare const JUDGE_SYSTEM_PROMPT: string;
51
63
  export declare const JUDGE_USER_PROMPT_TEMPLATE: string;
52
64
  export declare const JUDGE_USER_PROMPT_WITH_SUBGOALS_TEMPLATE: string;
53
65
  export declare function truncateText(text: string, limit: number): string;
54
66
  /** Render subgoals as a numbered `- N. text` block. Empty string when none. */
55
67
  export declare function renderSubgoalsBlock(subgoals: string[]): string;
68
+ export declare function getGoalJudgeCriteria(state: Pick<GoalState, 'goalPlan' | 'subgoals'>): string[];
56
69
  export declare function createGoalState(goal: string, maxTurns?: number): GoalState;
57
70
  /**
58
71
  * Defensive deserialization of a persisted goal state. Returns null when the
@@ -16,6 +16,7 @@
16
16
  * - A real user message mid-loop preempts the continuation prompt; the judge
17
17
  * re-runs after that turn.
18
18
  */
19
+ import { formatGoalPlan, goalPlanToCriteria, normalizeGoalPlan, } from './goal-decomposer.js';
19
20
  // ──────────────────────────────────────────────────────────────────────
20
21
  // Constants & defaults
21
22
  // ──────────────────────────────────────────────────────────────────────
@@ -47,27 +48,56 @@ export const CONTINUATION_PROMPT_WITH_SUBGOALS_TEMPLATE = '[Continuing toward yo
47
48
  'additional criterion are complete, state so explicitly and stop. ' +
48
49
  'If you are blocked and need input from the user, say so clearly ' +
49
50
  'and stop.';
51
+ export const CONTINUATION_PROMPT_WITH_PLAN_TEMPLATE = '[Continuing toward your standing goal]\n' +
52
+ 'Goal: {goal}\n\n' +
53
+ 'Decomposition plan:\n' +
54
+ '{goal_plan_block}\n\n' +
55
+ '{manual_subgoals_block}' +
56
+ 'Continue working through the plan. Prefer ready tasks whose dependencies ' +
57
+ 'are already satisfied, keep independent lanes separate, and produce ' +
58
+ 'concrete evidence for every acceptance criterion. If you believe the goal ' +
59
+ 'and every criterion are complete, state so explicitly and stop. If you are ' +
60
+ 'blocked and need input from the user, say so clearly and stop.';
50
61
  export const JUDGE_SYSTEM_PROMPT = "You are a strict judge evaluating whether an autonomous agent has " +
51
62
  "achieved a user's stated goal. You receive the goal text and the " +
52
63
  "agent's most recent response. Your only job is to decide whether " +
53
64
  'the goal is fully satisfied based on that response.\n\n' +
65
+ 'The response may begin with internal evidence metadata such as ' +
66
+ '`[tool evidence: none]` or `[tool:<name> success]`. Treat those ' +
67
+ 'bracketed metadata lines as evidence annotations only. They are NOT ' +
68
+ "part of the assistant's answer text for exact-answer goals; ignore " +
69
+ 'the metadata itself when comparing exact requested output. For ' +
70
+ 'side-effect goals, use the metadata and any following tool output to ' +
71
+ 'decide whether concrete evidence exists.\n\n' +
54
72
  'A goal is DONE only when:\n' +
55
- '- The response explicitly confirms the goal was completed, OR\n' +
73
+ '- For answer-only or explanation goals, the response directly answers the requested question, OR\n' +
56
74
  '- The response clearly shows the final deliverable was produced, OR\n' +
57
75
  '- The response explains the goal is unachievable / blocked / needs ' +
58
76
  'user input (treat this as DONE with reason describing the block).\n\n' +
77
+ 'For goals that require external side effects such as creating, editing, ' +
78
+ 'moving, deleting, reading, or verifying files; running commands; calling ' +
79
+ 'tools; changing repository state; or producing artifacts, unsupported ' +
80
+ 'assistant claims are NOT evidence. Do not accept statements like "I ' +
81
+ 'created the file" or "I verified it" unless the response includes concrete ' +
82
+ 'evidence such as a tool result, command output, file contents excerpt, diff, ' +
83
+ 'test output, or artifact path backed by tool output. If a side-effect goal ' +
84
+ 'has no concrete evidence, return CONTINUE.\n\n' +
59
85
  'Otherwise the goal is NOT done — CONTINUE.\n\n' +
60
86
  'Reply ONLY with a single JSON object on one line:\n' +
61
87
  '{"done": <true|false>, "reason": "<one-sentence rationale>"}';
62
88
  export const JUDGE_USER_PROMPT_TEMPLATE = 'Goal:\n{goal}\n\n' +
63
- "Agent's most recent response:\n{response}\n\n" +
64
89
  'Current time: {current_time}\n\n' +
90
+ "Agent's most recent response is delimited by <response> tags. " +
91
+ 'Text outside those tags is prompt metadata, not part of the response:\n' +
92
+ '<response>\n{response}\n</response>\n\n' +
65
93
  'Is the goal satisfied?';
66
94
  export const JUDGE_USER_PROMPT_WITH_SUBGOALS_TEMPLATE = 'Goal:\n{goal}\n\n' +
67
95
  'Additional criteria the user added mid-loop (all must also be ' +
68
96
  'satisfied for the goal to be DONE):\n{subgoals_block}\n\n' +
69
- "Agent's most recent response:\n{response}\n\n" +
70
97
  'Current time: {current_time}\n\n' +
98
+ "Agent's most recent response is delimited by <response> tags. " +
99
+ 'Text outside those tags is prompt metadata, not part of the response:\n' +
100
+ '<response>\n{response}\n</response>\n\n' +
71
101
  'Decision: For each numbered criterion above, find concrete ' +
72
102
  "evidence in the agent's response that the criterion is " +
73
103
  "satisfied. Do not accept generic phrases like 'all requirements " +
@@ -92,7 +122,13 @@ export function renderSubgoalsBlock(subgoals) {
92
122
  return '';
93
123
  return subgoals.map((text, i) => `- ${i + 1}. ${text}`).join('\n');
94
124
  }
125
+ export function getGoalJudgeCriteria(state) {
126
+ return [...goalPlanToCriteria(state.goalPlan), ...state.subgoals];
127
+ }
95
128
  export function createGoalState(goal, maxTurns = DEFAULT_MAX_TURNS) {
129
+ if (!Number.isSafeInteger(maxTurns) || maxTurns <= 0) {
130
+ throw new Error('maxTurns must be a positive integer');
131
+ }
96
132
  return {
97
133
  goal,
98
134
  status: 'active',
@@ -121,19 +157,28 @@ export function normalizeGoalState(raw) {
121
157
  const subgoals = Array.isArray(data.subgoals)
122
158
  ? data.subgoals.map(s => String(s).trim()).filter(Boolean)
123
159
  : [];
160
+ const goalPlan = normalizeGoalPlan(data.goalPlan);
124
161
  const verdict = ['done', 'continue', 'skipped'].includes(String(data.lastVerdict))
125
162
  ? data.lastVerdict
126
163
  : undefined;
127
164
  const state = {
128
165
  goal,
129
166
  status,
130
- turnsUsed: toInt(data.turnsUsed, 0),
131
- maxTurns: toInt(data.maxTurns, DEFAULT_MAX_TURNS) || DEFAULT_MAX_TURNS,
167
+ turnsUsed: toNonNegativeInt(data.turnsUsed, 0),
168
+ maxTurns: toPositiveInt(data.maxTurns, DEFAULT_MAX_TURNS),
132
169
  createdAt: toNumber(data.createdAt, 0),
133
170
  lastTurnAt: toNumber(data.lastTurnAt, 0),
134
- consecutiveParseFailures: toInt(data.consecutiveParseFailures, 0),
171
+ consecutiveParseFailures: toNonNegativeInt(data.consecutiveParseFailures, 0),
135
172
  subgoals,
136
173
  };
174
+ if (goalPlan)
175
+ state.goalPlan = goalPlan;
176
+ if (typeof data.goalPlanAttempted === 'boolean') {
177
+ state.goalPlanAttempted = data.goalPlanAttempted;
178
+ }
179
+ if (typeof data.goalPlanLastError === 'string') {
180
+ state.goalPlanLastError = data.goalPlanLastError;
181
+ }
137
182
  if (verdict)
138
183
  state.lastVerdict = verdict;
139
184
  if (typeof data.lastReason === 'string')
@@ -142,9 +187,23 @@ export function normalizeGoalState(raw) {
142
187
  state.pausedReason = data.pausedReason;
143
188
  return state;
144
189
  }
145
- function toInt(value, fallback) {
146
- const n = Number(value);
147
- return Number.isFinite(n) ? Math.trunc(n) : fallback;
190
+ function toPositiveInt(value, fallback) {
191
+ const n = parseSafeInteger(value, /^[1-9]\d*$/);
192
+ return Number.isSafeInteger(n) && n > 0 ? n : fallback;
193
+ }
194
+ function toNonNegativeInt(value, fallback) {
195
+ const n = parseSafeInteger(value, /^(?:0|[1-9]\d*)$/);
196
+ return Number.isSafeInteger(n) && n >= 0 ? n : fallback;
197
+ }
198
+ function parseSafeInteger(value, stringPattern) {
199
+ if (typeof value === 'number')
200
+ return value;
201
+ if (typeof value !== 'string')
202
+ return Number.NaN;
203
+ const trimmed = value.trim();
204
+ if (!stringPattern.test(trimmed))
205
+ return Number.NaN;
206
+ return Number(trimmed);
148
207
  }
149
208
  function toNumber(value, fallback) {
150
209
  const n = Number(value);
@@ -159,20 +218,31 @@ export function formatGoalStatusLine(state) {
159
218
  const sub = state.subgoals.length
160
219
  ? `, ${state.subgoals.length} subgoal${state.subgoals.length !== 1 ? 's' : ''}`
161
220
  : '';
221
+ const plan = state.goalPlan
222
+ ? `, plan ${state.goalPlan.tasks.length} task${state.goalPlan.tasks.length !== 1 ? 's' : ''}`
223
+ : '';
162
224
  if (state.status === 'active') {
163
- return `⊙ Goal (active, ${turns}${sub}): ${state.goal}`;
225
+ return `⊙ Goal (active, ${turns}${sub}${plan}): ${state.goal}`;
164
226
  }
165
227
  if (state.status === 'paused') {
166
228
  const extra = state.pausedReason ? ` — ${state.pausedReason}` : '';
167
- return `⏸ Goal (paused, ${turns}${sub}${extra}): ${state.goal}`;
229
+ return `⏸ Goal (paused, ${turns}${sub}${plan}${extra}): ${state.goal}`;
168
230
  }
169
231
  if (state.status === 'done') {
170
- return `✓ Goal done (${turns}${sub}): ${state.goal}`;
232
+ return `✓ Goal done (${turns}${sub}${plan}): ${state.goal}`;
171
233
  }
172
- return `Goal (${state.status}, ${turns}${sub}): ${state.goal}`;
234
+ return `Goal (${state.status}, ${turns}${sub}${plan}): ${state.goal}`;
173
235
  }
174
236
  /** The canonical user-role continuation message for an active goal. */
175
237
  export function buildContinuationPrompt(state) {
238
+ if (state.goalPlan) {
239
+ const manualSubgoalsBlock = state.subgoals.length
240
+ ? `Additional user criteria:\n${renderSubgoalsBlock(state.subgoals)}\n\n`
241
+ : '';
242
+ return CONTINUATION_PROMPT_WITH_PLAN_TEMPLATE.replace('{goal}', state.goal)
243
+ .replace('{goal_plan_block}', formatGoalPlan(state.goalPlan))
244
+ .replace('{manual_subgoals_block}', manualSubgoalsBlock);
245
+ }
176
246
  if (state.subgoals.length) {
177
247
  return CONTINUATION_PROMPT_WITH_SUBGOALS_TEMPLATE.replace('{goal}', state.goal).replace('{subgoals_block}', renderSubgoalsBlock(state.subgoals));
178
248
  }
@@ -22,4 +22,7 @@ export declare class GoalStore {
22
22
  save(key: string, state: GoalState): void;
23
23
  delete(key: string): void;
24
24
  private fileFor;
25
+ private fileCandidatesForRead;
26
+ private legacyFileFor;
27
+ private fileLabel;
25
28
  }
@@ -9,6 +9,7 @@
9
9
  * Cleared goals keep a tombstone (`status: 'cleared'`) for audit, mirroring
10
10
  * Hermes' SessionDB behavior.
11
11
  */
12
+ import { createHash } from 'crypto';
12
13
  import fs from 'fs';
13
14
  import path from 'path';
14
15
  import { getCodeBuddyPath } from '../utils/codebuddy-home.js';
@@ -25,17 +26,18 @@ export class GoalStore {
25
26
  load(key) {
26
27
  if (!key)
27
28
  return null;
28
- const file = this.fileFor(key);
29
- try {
30
- if (!fs.existsSync(file))
31
- return null;
32
- const raw = fs.readFileSync(file, 'utf-8');
33
- return normalizeGoalState(JSON.parse(raw));
34
- }
35
- catch (error) {
36
- logger.debug('GoalStore: failed to load goal state', { key, error: String(error) });
37
- return null;
29
+ for (const file of this.fileCandidatesForRead(key)) {
30
+ try {
31
+ if (!fs.existsSync(file))
32
+ continue;
33
+ const raw = fs.readFileSync(file, 'utf-8');
34
+ return normalizeGoalState(JSON.parse(raw));
35
+ }
36
+ catch (error) {
37
+ logger.debug('GoalStore: failed to load goal state', { key, file, error: String(error) });
38
+ }
38
39
  }
40
+ return null;
39
41
  }
40
42
  save(key, state) {
41
43
  if (!key)
@@ -54,18 +56,32 @@ export class GoalStore {
54
56
  delete(key) {
55
57
  if (!key)
56
58
  return;
57
- try {
58
- fs.rmSync(this.fileFor(key), { force: true });
59
- }
60
- catch (error) {
61
- logger.debug('GoalStore: failed to delete goal state', { key, error: String(error) });
59
+ for (const file of this.fileCandidatesForRead(key)) {
60
+ try {
61
+ fs.rmSync(file, { force: true });
62
+ }
63
+ catch (error) {
64
+ logger.debug('GoalStore: failed to delete goal state', { key, file, error: String(error) });
65
+ }
62
66
  }
63
67
  }
64
68
  fileFor(key) {
69
+ const label = this.fileLabel(key);
70
+ const digest = createHash('sha256').update(key).digest('hex').slice(0, 16);
71
+ return path.join(this.storeDir, `${label}-${digest}.json`);
72
+ }
73
+ fileCandidatesForRead(key) {
74
+ return Array.from(new Set([this.fileFor(key), this.legacyFileFor(key)]));
75
+ }
76
+ legacyFileFor(key) {
65
77
  // Session ids and dir-hash keys are already filesystem-safe; sanitize
66
78
  // anyway so a malformed key can't escape the store directory.
67
79
  const safe = key.replace(/[^a-zA-Z0-9._-]/g, '_');
68
80
  return path.join(this.storeDir, `${safe}.json`);
69
81
  }
82
+ fileLabel(key) {
83
+ const safe = key.replace(/[^a-zA-Z0-9._-]/g, '_').replace(/^\.+$/, '_').slice(0, 80);
84
+ return safe || 'goal';
85
+ }
70
86
  }
71
87
  //# sourceMappingURL=goal-store.js.map
@@ -188,6 +188,8 @@ export class UserHooksManager {
188
188
  const shellFlag = isWindows ? '/c' : '-c';
189
189
  const child = spawn(shell, [shellFlag, expandedCommand], {
190
190
  stdio: ['pipe', 'pipe', 'pipe'],
191
+ detached: !isWindows,
192
+ windowsHide: true,
191
193
  env: {
192
194
  ...process.env,
193
195
  TOOL_NAME: context.toolName ?? '',
@@ -202,12 +204,24 @@ export class UserHooksManager {
202
204
  let timedOut = false;
203
205
  const timer = setTimeout(() => {
204
206
  timedOut = true;
205
- child.kill('SIGTERM');
207
+ if (!isWindows && child.pid) {
208
+ try {
209
+ process.kill(-child.pid, 'SIGTERM');
210
+ }
211
+ catch {
212
+ child.kill('SIGTERM');
213
+ }
214
+ }
215
+ else {
216
+ child.kill('SIGTERM');
217
+ }
206
218
  }, timeout);
207
219
  // Write context JSON to stdin
220
+ child.stdin.on('error', (err) => {
221
+ logger.debug(`[user-hooks] command stdin closed: ${err.message}`);
222
+ });
208
223
  try {
209
- child.stdin.write(JSON.stringify({ event, ...context }));
210
- child.stdin.end();
224
+ child.stdin.end(JSON.stringify({ event, ...context }));
211
225
  }
212
226
  catch {
213
227
  // stdin may already be closed