@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
@@ -56,6 +56,13 @@ const DEFAULT_FEATURE_FLAGS = {
56
56
  category: 'ai',
57
57
  envOverride: 'USER_MODEL_DIALECTIC_ON_SESSION_END',
58
58
  },
59
+ SESSION_END_FLUSH: {
60
+ name: 'SESSION_END_FLUSH',
61
+ enabled: true,
62
+ description: 'At session end, write a short handoff (.codebuddy/HANDOFF.md) and propose review-gated lesson candidates from the transcript (WS3-T1)',
63
+ category: 'ai',
64
+ envOverride: 'CODEBUDDY_SESSION_END_FLUSH',
65
+ },
59
66
  VOICE_CONTROL: {
60
67
  name: 'VOICE_CONTROL',
61
68
  enabled: false,
@@ -405,13 +405,13 @@ const DEFAULT_MODEL_CONFIGS = [
405
405
  patchFormat: 'full_file',
406
406
  promptProfile: 'lite',
407
407
  },
408
- // Gemma 4 (Ollama, e2b/e4b) — local lightweight model. Same lite
409
- // profile as qwen/llama: minimal SP, no tool_calls advertised,
410
- // chat-only base prompt to avoid JSON hallucination on small models.
408
+ // Gemma 4 (Ollama, e2b/e4b) — local model with tool support. Keep the
409
+ // lite prompt/tool profile, but do not gate off tool calls: real `buddy goal`
410
+ // smoke with gemma4:12b only succeeds when the selected tools are preserved.
411
411
  {
412
412
  model: 'gemma4*',
413
413
  supportsReasoning: false,
414
- supportsToolCalls: false,
414
+ supportsToolCalls: true,
415
415
  supportsVision: false,
416
416
  contextWindow: 8192,
417
417
  maxOutputTokens: 2048,
@@ -122,6 +122,20 @@ export interface ModelPairsConfig {
122
122
  /** Model used for code edits and execution (the "doer") */
123
123
  editor?: string;
124
124
  }
125
+ /** Active-LLM registry: auto-failover across the user's live logins + "together". */
126
+ export interface LlmFailoverConfig {
127
+ /** Master gate. OFF by default — single-provider behavior is unchanged. */
128
+ enabled?: boolean;
129
+ /** Fallback ordering: 'resilience' (capable/subscription first, local last),
130
+ * 'free-first' (cheapest first), or 'manual' (explicit `manualOrder`). */
131
+ order?: 'resilience' | 'free-first' | 'manual';
132
+ /** Provider ids in preferred order when `order: 'manual'`. */
133
+ manualOrder?: string[];
134
+ /** Restrict the active set to local runtimes (privacy). */
135
+ local_only?: boolean;
136
+ /** Default strategy for `/llm ensemble` (Phase 2). */
137
+ together_strategy?: 'ensemble' | 'consensus' | 'fastest' | 'cascade';
138
+ }
125
139
  /**
126
140
  * External integrations configuration
127
141
  */
@@ -497,6 +511,8 @@ export interface CodeBuddyConfig {
497
511
  integrations: IntegrationsConfig;
498
512
  /** Model pairs for architect/editor split */
499
513
  model_pairs?: ModelPairsConfig;
514
+ /** Active-LLM registry: auto-failover across live logins + "together" */
515
+ llm?: LlmFailoverConfig;
500
516
  /** Agent defaults (model preferences) — Native Engine v2026.3.14 */
501
517
  agent_defaults?: AgentDefaultsConfig;
502
518
  /** Advisor tool settings (second opinion model) — V4.1 */
@@ -540,6 +540,9 @@ class ConfigManager {
540
540
  if (partial.model_pairs) {
541
541
  this.config.model_pairs = { ...this.config.model_pairs, ...partial.model_pairs };
542
542
  }
543
+ if (partial.llm) {
544
+ this.config.llm = { ...this.config.llm, ...partial.llm };
545
+ }
543
546
  }
544
547
  /**
545
548
  * Get current config
@@ -60,6 +60,23 @@ export interface ContextStats {
60
60
  isNearLimit: boolean;
61
61
  isCritical: boolean;
62
62
  }
63
+ /**
64
+ * Periodic memory snapshot (WS3-T2) — a compact, persisted view of the
65
+ * session so very long runs (12–15 h) survive crashes and aggressive
66
+ * compaction without losing the thread.
67
+ */
68
+ export interface ContextSnapshot {
69
+ sessionId: string;
70
+ takenAt: string;
71
+ stats: {
72
+ messageCount: number;
73
+ tokenCount: number;
74
+ compressionCount: number;
75
+ totalTokensSaved: number;
76
+ };
77
+ /** Extractive, privacy-linted summary of the conversation so far. */
78
+ summary: string;
79
+ }
63
80
  /**
64
81
  * Memory metrics for monitoring context manager health
65
82
  */
@@ -125,6 +142,10 @@ export declare class ContextManagerV2 {
125
142
  private _cachedStatsFingerprint;
126
143
  /** Last compression timestamp */
127
144
  private lastCompressionTime;
145
+ /** WS3-T2 — periodic snapshot timer (unref'd, never keeps the process alive) */
146
+ private snapshotTimer;
147
+ /** WS3-T2 — snapshots taken this session */
148
+ private snapshotCount;
128
149
  static readonly DEFAULT_CONFIG: ContextManagerConfig;
129
150
  constructor(config?: Partial<ContextManagerConfig>);
130
151
  /**
@@ -344,6 +365,24 @@ export declare class ContextManagerV2 {
344
365
  * Format compression statistics as human-readable string
345
366
  */
346
367
  formatCompressionStats(): string;
368
+ /**
369
+ * Take a compact snapshot of the session and persist it to
370
+ * `.codebuddy/context-snapshot.json` (latest wins). Returns null when the
371
+ * conversation is too small to be worth snapshotting.
372
+ *
373
+ * The summary is privacy-linted before it touches disk (WS3 guard-rail).
374
+ */
375
+ takeSnapshot(messages: CodeBuddyMessage[], workDir?: string): ContextSnapshot | null;
376
+ /**
377
+ * Start the periodic snapshot loop for long sessions (12–15 h).
378
+ *
379
+ * Interval resolution: explicit param → `CODEBUDDY_SNAPSHOT_INTERVAL_MIN`
380
+ * env (minutes) → 45 min default. `0` (or negative) disables. The timer
381
+ * is unref'd so it never keeps a finished process alive.
382
+ */
383
+ startPeriodicSnapshot(getMessages: () => CodeBuddyMessage[], intervalMs?: number, workDir?: string): void;
384
+ /** Stop the periodic snapshot loop (idempotent). */
385
+ stopPeriodicSnapshot(): void;
347
386
  }
348
387
  export type { KeyInformation, ContextArchive, CompressionMetrics, EnhancedCompressionResult, };
349
388
  /**
@@ -12,6 +12,9 @@
12
12
  * - Content-type-aware Compression
13
13
  * - Key Information Preservation
14
14
  */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { redactSecrets } from '../fleet/privacy-lint.js';
15
18
  import { createTokenCounter } from './token-counter.js';
16
19
  import { logger } from '../utils/logger.js';
17
20
  import { getModelToolConfig } from '../config/model-tools.js';
@@ -74,6 +77,10 @@ export class ContextManagerV2 {
74
77
  _cachedStatsFingerprint = '';
75
78
  /** Last compression timestamp */
76
79
  lastCompressionTime = null;
80
+ /** WS3-T2 — periodic snapshot timer (unref'd, never keeps the process alive) */
81
+ snapshotTimer = null;
82
+ /** WS3-T2 — snapshots taken this session */
83
+ snapshotCount = 0;
77
84
  // Default configuration based on research recommendations
78
85
  static DEFAULT_CONFIG = {
79
86
  maxContextTokens: 4096,
@@ -692,6 +699,7 @@ export class ContextManagerV2 {
692
699
  * Clean up resources
693
700
  */
694
701
  dispose() {
702
+ this.stopPeriodicSnapshot();
695
703
  this.tokenCounter.dispose();
696
704
  this.summaries = [];
697
705
  this.triggeredWarnings.clear();
@@ -921,6 +929,89 @@ export class ContextManagerV2 {
921
929
  }
922
930
  return lines.join('\n');
923
931
  }
932
+ // ==========================================================================
933
+ // WS3-T2 — Periodic memory snapshot
934
+ // ==========================================================================
935
+ /**
936
+ * Take a compact snapshot of the session and persist it to
937
+ * `.codebuddy/context-snapshot.json` (latest wins). Returns null when the
938
+ * conversation is too small to be worth snapshotting.
939
+ *
940
+ * The summary is privacy-linted before it touches disk (WS3 guard-rail).
941
+ */
942
+ takeSnapshot(messages, workDir = process.cwd()) {
943
+ if (!messages || messages.length < 4)
944
+ return null;
945
+ const snapshot = {
946
+ sessionId: this.sessionId,
947
+ takenAt: new Date().toISOString(),
948
+ stats: {
949
+ messageCount: messages.length,
950
+ tokenCount: this.countTokens(messages),
951
+ compressionCount: this.compressionCount,
952
+ totalTokensSaved: this.totalTokensSaved,
953
+ },
954
+ summary: redactSecrets(this.createSummary(messages)),
955
+ };
956
+ try {
957
+ const dir = path.join(workDir, '.codebuddy');
958
+ fs.mkdirSync(dir, { recursive: true });
959
+ fs.writeFileSync(path.join(dir, 'context-snapshot.json'), JSON.stringify(snapshot, null, 2), 'utf8');
960
+ }
961
+ catch (err) {
962
+ logger.debug('Context snapshot write failed', { error: String(err) });
963
+ return null;
964
+ }
965
+ this.snapshotCount++;
966
+ try {
967
+ const runStore = RunStore.getInstance();
968
+ if (runStore.getCurrentRunId()) {
969
+ runStore.appendEvent('context_snapshot', {
970
+ sessionId: snapshot.sessionId,
971
+ messageCount: snapshot.stats.messageCount,
972
+ tokenCount: snapshot.stats.tokenCount,
973
+ snapshotCount: this.snapshotCount,
974
+ });
975
+ }
976
+ }
977
+ catch {
978
+ // Observability must never break the snapshot path.
979
+ }
980
+ return snapshot;
981
+ }
982
+ /**
983
+ * Start the periodic snapshot loop for long sessions (12–15 h).
984
+ *
985
+ * Interval resolution: explicit param → `CODEBUDDY_SNAPSHOT_INTERVAL_MIN`
986
+ * env (minutes) → 45 min default. `0` (or negative) disables. The timer
987
+ * is unref'd so it never keeps a finished process alive.
988
+ */
989
+ startPeriodicSnapshot(getMessages, intervalMs, workDir = process.cwd()) {
990
+ this.stopPeriodicSnapshot();
991
+ let resolved = intervalMs;
992
+ if (resolved === undefined) {
993
+ const envMin = parseInt(process.env.CODEBUDDY_SNAPSHOT_INTERVAL_MIN || '45', 10);
994
+ resolved = (Number.isFinite(envMin) ? envMin : 45) * 60_000;
995
+ }
996
+ if (!resolved || resolved <= 0)
997
+ return;
998
+ this.snapshotTimer = setInterval(() => {
999
+ try {
1000
+ this.takeSnapshot(getMessages(), workDir);
1001
+ }
1002
+ catch (err) {
1003
+ logger.debug('Periodic context snapshot failed', { error: String(err) });
1004
+ }
1005
+ }, resolved);
1006
+ this.snapshotTimer.unref();
1007
+ }
1008
+ /** Stop the periodic snapshot loop (idempotent). */
1009
+ stopPeriodicSnapshot() {
1010
+ if (this.snapshotTimer) {
1011
+ clearInterval(this.snapshotTimer);
1012
+ this.snapshotTimer = null;
1013
+ }
1014
+ }
924
1015
  }
925
1016
  /**
926
1017
  * Create a context manager with auto-detection of model limits
@@ -34,6 +34,9 @@
34
34
  import * as fs from 'fs';
35
35
  import * as path from 'path';
36
36
  import { spawnSync } from 'child_process';
37
+ import { buildColabGoalContinuationPrompt } from './colab-goal.js';
38
+ /** Tail of agent stdout kept for the goal-mode judge (matches the judge's 4 KB cap). */
39
+ const OUTPUT_TAIL_CHARS = 4000;
37
40
  /** Resolve the buddy CLI entrypoint inside `repoRoot`. */
38
41
  function resolveEntrypoint(repoRoot) {
39
42
  const tsx = path.join(repoRoot, 'node_modules', '.bin', 'tsx');
@@ -82,7 +85,11 @@ export function createAgentTaskExecutor(opts = {}) {
82
85
  if (!entry) {
83
86
  return { ok: false, summary: 'no buddy entrypoint', error: `no src/index.ts or dist/index.js under ${repoRoot}` };
84
87
  }
85
- const prompt = `${task.title}\n\n${task.description ?? ''}`.trim();
88
+ // Goal-mode continuation: on later turns the worker gets the judge's
89
+ // nudge instead of the bare task text, so it targets the remaining gap.
90
+ const prompt = task.goalMode && (task.goalTurnsUsed ?? 0) > 0
91
+ ? buildColabGoalContinuationPrompt(task)
92
+ : `${task.title}\n\n${task.description ?? ''}`.trim();
86
93
  const env = buildAgentEnv(model);
87
94
  const started = Date.now();
88
95
  const res = doSpawn(entry.cmd, [...entry.baseArgs, '-p', prompt, '--permission-mode', permissionMode, '--output-format', 'text', ...extraArgs], { cwd: workspaceRoot, env, encoding: 'utf-8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 });
@@ -99,6 +106,8 @@ export function createAgentTaskExecutor(opts = {}) {
99
106
  error: reason,
100
107
  };
101
108
  }
109
+ // Tail of the agent's real output — what the goal-mode judge evaluates.
110
+ const output = (res.stdout ?? '').slice(-OUTPUT_TAIL_CHARS).trim();
102
111
  // Acceptance gate: if the task carries a verify command AND running task-
103
112
  // supplied shell is allowed (opt-in), the agent "finishing" isn't enough —
104
113
  // the gate must pass for the task to count as completed. When not allowed,
@@ -126,6 +135,7 @@ export function createAgentTaskExecutor(opts = {}) {
126
135
  ok: true,
127
136
  summary: `agent ran ${task.id} [${model.tier}/${model.model}] + gate \`${gate}\` passed (${elapsedSeconds}s)`,
128
137
  elapsedSeconds,
138
+ ...(output ? { output } : {}),
129
139
  };
130
140
  }
131
141
  const elapsedSeconds = Math.round((Date.now() - started) / 1000);
@@ -133,6 +143,7 @@ export function createAgentTaskExecutor(opts = {}) {
133
143
  ok: true,
134
144
  summary: `agent ran ${task.id} [${model.tier}/${model.model}] in ${workspaceRoot} (${elapsedSeconds}s)`,
135
145
  elapsedSeconds,
146
+ ...(output ? { output } : {}),
136
147
  };
137
148
  };
138
149
  }
@@ -89,7 +89,7 @@ export interface DefaultAutonomousLoopOptions {
89
89
  * local/network executor. The CLI uses this so the same config drives one-shot
90
90
  * and continuous runs.
91
91
  */
92
- export declare function createDefaultAutonomousLoop(opts?: DefaultAutonomousLoopOptions): FleetAutonomousLoop;
92
+ export declare function createDefaultAutonomousLoop(opts?: DefaultAutonomousLoopOptions): Promise<FleetAutonomousLoop>;
93
93
  /**
94
94
  * Event source for {@link AutonomousDaemonConfig.eventSourceFactory}: watches the
95
95
  * fleet queue file (`colab-tasks.json`) and calls `onChange` when it changes —
@@ -13,11 +13,12 @@
13
13
  * (see {@link createAgentTaskExecutor}).
14
14
  */
15
15
  import { FleetColabStore } from '../fleet/colab-store.js';
16
- import { resolveModelTierConfig } from '../agent/model-tier.js';
16
+ import { resolveLiveModelTierConfig, } from '../agent/model-tier.js';
17
17
  import { FileWatcherTrigger } from '../agent/file-watcher-trigger.js';
18
18
  import { FleetAutonomousLoop } from './autonomous-loop.js';
19
19
  import { createLocalModelTaskExecutor } from './ollama-task-executor.js';
20
20
  import { createAgentTaskExecutor } from './agent-task-executor.js';
21
+ import { createColabGoalJudge } from './colab-goal.js';
21
22
  const DEFAULT_INTERVAL_MS = 30_000;
22
23
  export class FleetAutonomousDaemon {
23
24
  loop;
@@ -115,12 +116,12 @@ export class FleetAutonomousDaemon {
115
116
  * local/network executor. The CLI uses this so the same config drives one-shot
116
117
  * and continuous runs.
117
118
  */
118
- export function createDefaultAutonomousLoop(opts = {}) {
119
+ export async function createDefaultAutonomousLoop(opts = {}) {
119
120
  const store = new FleetColabStore({
120
121
  ...(opts.dir ? { dir: opts.dir } : {}),
121
122
  ...(opts.agentId ? { agentId: opts.agentId } : {}),
122
123
  });
123
- const tierConfig = resolveModelTierConfig();
124
+ const tierConfig = await resolveLiveModelTierConfig();
124
125
  const executorMode = opts.executorMode ?? (process.env['CODEBUDDY_AUTONOMY_EXECUTOR'] === 'agent' ? 'agent' : 'artifact');
125
126
  const executor = executorMode === 'agent'
126
127
  ? createAgentTaskExecutor({ ...(opts.workspaceRoot ? { workspaceRoot: opts.workspaceRoot } : {}) })
@@ -132,6 +133,7 @@ export function createDefaultAutonomousLoop(opts = {}) {
132
133
  store,
133
134
  tierConfig,
134
135
  executor,
136
+ goalJudge: createColabGoalJudge(),
135
137
  ...(opts.policy ? { policy: opts.policy } : {}),
136
138
  ...(opts.enabled ? { enabled: opts.enabled } : {}),
137
139
  });
@@ -22,12 +22,19 @@
22
22
  */
23
23
  import { type AutonomousModelChoice, type ModelTierConfig, type ModelTierPolicy } from '../agent/model-tier.js';
24
24
  import type { ColabTask, ColabWorklogFileChange, FleetColabStore } from '../fleet/colab-store.js';
25
+ import { type ColabGoalJudge } from './colab-goal.js';
25
26
  export interface TaskExecutionResult {
26
27
  ok: boolean;
27
28
  summary: string;
28
29
  filesModified?: ColabWorklogFileChange[];
29
30
  elapsedSeconds?: number;
30
31
  error?: string;
32
+ /**
33
+ * Tail of the worker's actual output (capped). Goal-mode judges evaluate
34
+ * this; absent for executors that don't capture output (judge falls back
35
+ * to `summary`).
36
+ */
37
+ output?: string;
31
38
  }
32
39
  export type TaskExecutor = (task: ColabTask, model: AutonomousModelChoice) => Promise<TaskExecutionResult>;
33
40
  export interface AutonomousLoopConfig {
@@ -37,9 +44,14 @@ export interface AutonomousLoopConfig {
37
44
  policy?: ModelTierPolicy;
38
45
  /** Kill-switch — when it returns false the tick is a no-op. Default: always on. */
39
46
  enabled?: () => boolean;
47
+ /**
48
+ * Judge for `goalMode` tasks (Hermes kanban goal-mode parity). When absent,
49
+ * goal-mode tasks complete like plain tasks (no judge gate).
50
+ */
51
+ goalJudge?: ColabGoalJudge;
40
52
  }
41
53
  export interface TickResult {
42
- outcome: 'disabled' | 'idle' | 'completed' | 'failed' | 'saturated';
54
+ outcome: 'disabled' | 'idle' | 'completed' | 'failed' | 'saturated' | 'goal_continue' | 'goal_blocked';
43
55
  taskId?: string;
44
56
  taskTitle?: string;
45
57
  model?: AutonomousModelChoice;
@@ -58,7 +70,15 @@ export declare class FleetAutonomousLoop {
58
70
  * success. Resets across process restarts — escalation is a within-run feature.
59
71
  */
60
72
  private readonly failures;
73
+ private readonly goalJudge;
61
74
  constructor(config: AutonomousLoopConfig);
62
75
  /** Run a single autonomous tick. Never throws — failures are logged + reported. */
63
76
  tick(): Promise<TickResult>;
77
+ /**
78
+ * Goal-mode decision ladder. Returns a TickResult when the loop should NOT
79
+ * complete the task (judge says continue / budget exhausted), or null when
80
+ * the task may complete (judge done/skipped, or judge unreachable —
81
+ * fail-open like the interactive Ralph loop).
82
+ */
83
+ private evaluateGoalModeTask;
64
84
  }
@@ -22,6 +22,15 @@
22
22
  */
23
23
  import { chooseAutonomousModel, } from '../agent/model-tier.js';
24
24
  import { beginFleetWork, isFleetSaturated } from '../fleet/fleet-load.js';
25
+ import { DEFAULT_COLAB_GOAL_MAX_TURNS } from './colab-goal.js';
26
+ function resolveGoalMaxTurns(raw) {
27
+ return typeof raw === 'number' && Number.isSafeInteger(raw) && raw > 0
28
+ ? raw
29
+ : DEFAULT_COLAB_GOAL_MAX_TURNS;
30
+ }
31
+ function resolveGoalTurnsUsed(raw) {
32
+ return typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0 ? raw : 0;
33
+ }
25
34
  export class FleetAutonomousLoop {
26
35
  store;
27
36
  tierConfig;
@@ -35,12 +44,14 @@ export class FleetAutonomousLoop {
35
44
  * success. Resets across process restarts — escalation is a within-run feature.
36
45
  */
37
46
  failures = new Map();
47
+ goalJudge;
38
48
  constructor(config) {
39
49
  this.store = config.store;
40
50
  this.tierConfig = config.tierConfig;
41
51
  this.executor = config.executor;
42
52
  this.policy = config.policy ?? {};
43
53
  this.enabled = config.enabled ?? (() => true);
54
+ this.goalJudge = config.goalJudge;
44
55
  }
45
56
  /** Run a single autonomous tick. Never throws — failures are logged + reported. */
46
57
  async tick() {
@@ -88,6 +99,14 @@ export class FleetAutonomousLoop {
88
99
  doneLoad();
89
100
  }
90
101
  if (result.ok) {
102
+ // Goal-mode gate (Hermes kanban goal-mode): a successful attempt is not
103
+ // enough — the judge must confirm the task's criteria are satisfied.
104
+ if (task.goalMode && this.goalJudge) {
105
+ const goalOutcome = await this.evaluateGoalModeTask(task, result, model);
106
+ if (goalOutcome)
107
+ return goalOutcome;
108
+ // null → judge said done (or skipped): fall through to completion.
109
+ }
91
110
  this.failures.delete(task.id);
92
111
  this.store.completeTask(task.id, {
93
112
  summary: result.summary,
@@ -118,5 +137,55 @@ export class FleetAutonomousLoop {
118
137
  ...(result.error ? { detail: result.error } : {}),
119
138
  };
120
139
  }
140
+ /**
141
+ * Goal-mode decision ladder. Returns a TickResult when the loop should NOT
142
+ * complete the task (judge says continue / budget exhausted), or null when
143
+ * the task may complete (judge done/skipped, or judge unreachable —
144
+ * fail-open like the interactive Ralph loop).
145
+ */
146
+ async evaluateGoalModeTask(task, result, model) {
147
+ let verdict;
148
+ try {
149
+ verdict = await this.goalJudge(task, result, model);
150
+ }
151
+ catch {
152
+ return null; // fail-open: an unusable judge never blocks completion
153
+ }
154
+ if (verdict.verdict !== 'continue')
155
+ return null;
156
+ const maxTurns = resolveGoalMaxTurns(task.goalMaxTurns);
157
+ const turnsUsed = resolveGoalTurnsUsed(task.goalTurnsUsed) + 1;
158
+ if (turnsUsed >= maxTurns) {
159
+ // Hermes rule: block for human review instead of spinning forever.
160
+ const reason = `goal budget exhausted (${turnsUsed}/${maxTurns}) — judge: ${verdict.reason}`;
161
+ this.store.recordGoalTurn(task.id, verdict.reason);
162
+ this.store.blockTask(task.id, reason);
163
+ this.store.appendWorklog({
164
+ agent: this.store.agentId,
165
+ taskId: task.id,
166
+ summary: `Goal-mode blocked after ${turnsUsed}/${maxTurns} turns: ${verdict.reason}`,
167
+ filesModified: result.filesModified ?? [],
168
+ issues: [reason],
169
+ nextSteps: ['human review: unblock, split, or complete the task manually'],
170
+ });
171
+ this.store.updatePresence({ status: 'idle', currentTask: null });
172
+ return { outcome: 'goal_blocked', taskId: task.id, taskTitle: task.title, model, detail: verdict.reason };
173
+ }
174
+ // Under budget: persist the consumed turn + reason, release the task so a
175
+ // later tick continues it with the continuation nudge. A judge "continue"
176
+ // is NOT an executor failure — the model ladder must not escalate.
177
+ this.store.recordGoalTurn(task.id, verdict.reason);
178
+ this.store.appendWorklog({
179
+ agent: this.store.agentId,
180
+ taskId: task.id,
181
+ summary: `Goal-mode turn ${turnsUsed}/${maxTurns} — judge: continue: ${verdict.reason}`,
182
+ filesModified: result.filesModified ?? [],
183
+ issues: [],
184
+ nextSteps: ['continue on a later tick with the goal continuation nudge'],
185
+ });
186
+ this.store.releaseTask(task.id);
187
+ this.store.updatePresence({ status: 'idle', currentTask: null });
188
+ return { outcome: 'goal_continue', taskId: task.id, taskTitle: task.title, model, detail: verdict.reason };
189
+ }
121
190
  }
122
191
  //# sourceMappingURL=autonomous-loop.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Goal-mode for fleet colab tasks — port of Hermes Agent's kanban goal-mode.
3
+ *
4
+ * A task with `goalMode: true` is not completed on the worker's first
5
+ * successful attempt: an LLM judge checks the task's title/description (with
6
+ * `acceptanceCriteria` as strict numbered criteria) against the worker's
7
+ * output. "Continue" re-opens the task with a continuation nudge so the next
8
+ * tick keeps going; once `goalMaxTurns` is spent the task is BLOCKED for
9
+ * human review instead of spinning (Hermes' "block instead of loop" rule).
10
+ *
11
+ * The judge is fail-open (judgeGoal semantics): a broken judge yields
12
+ * "continue", and the turn budget is the backstop.
13
+ */
14
+ import type { ColabTask } from '../fleet/colab-store.js';
15
+ import { GoalJudgeResult } from '../goals/goal-judge.js';
16
+ import type { AutonomousModelChoice } from '../agent/model-tier.js';
17
+ import type { TaskExecutionResult } from './autonomous-loop.js';
18
+ /**
19
+ * Conservative default for unattended subprocess loops (Hermes' kanban
20
+ * example uses 7; interactive /goal uses 20).
21
+ */
22
+ export declare const DEFAULT_COLAB_GOAL_MAX_TURNS = 5;
23
+ export type ColabGoalJudge = (task: ColabTask, result: TaskExecutionResult, model: AutonomousModelChoice) => Promise<GoalJudgeResult>;
24
+ /** The goal text the judge evaluates: title + description. */
25
+ export declare function goalTextForTask(task: Pick<ColabTask, 'title' | 'description'>): string;
26
+ /**
27
+ * Continuation nudge fed to the worker on later goal-mode turns (port of
28
+ * Hermes' KANBAN_GOAL_CONTINUATION_TEMPLATE, adapted to the colab lifecycle:
29
+ * completion is decided by the loop's judge, so the worker is nudged to
30
+ * finish and state the outcome explicitly).
31
+ */
32
+ export declare function buildColabGoalContinuationPrompt(task: ColabTask): string;
33
+ /**
34
+ * Default judge: a one-shot call on the same tier model the worker ran on
35
+ * (local tiers stay free), overridable via `goals.judgeModel`. Task
36
+ * `acceptanceCriteria` become strict numbered criteria for the judge.
37
+ */
38
+ export declare function createColabGoalJudge(): ColabGoalJudge;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Goal-mode for fleet colab tasks — port of Hermes Agent's kanban goal-mode.
3
+ *
4
+ * A task with `goalMode: true` is not completed on the worker's first
5
+ * successful attempt: an LLM judge checks the task's title/description (with
6
+ * `acceptanceCriteria` as strict numbered criteria) against the worker's
7
+ * output. "Continue" re-opens the task with a continuation nudge so the next
8
+ * tick keeps going; once `goalMaxTurns` is spent the task is BLOCKED for
9
+ * human review instead of spinning (Hermes' "block instead of loop" rule).
10
+ *
11
+ * The judge is fail-open (judgeGoal semantics): a broken judge yields
12
+ * "continue", and the turn budget is the backstop.
13
+ */
14
+ import { CodeBuddyClient } from '../codebuddy/client.js';
15
+ import { resolveGoalJudgeClient } from '../goals/goal-judge-client.js';
16
+ import { judgeGoal } from '../goals/goal-judge.js';
17
+ import { resolveGoalsConfig } from '../goals/goal-manager.js';
18
+ import { logger } from '../utils/logger.js';
19
+ /**
20
+ * Conservative default for unattended subprocess loops (Hermes' kanban
21
+ * example uses 7; interactive /goal uses 20).
22
+ */
23
+ export const DEFAULT_COLAB_GOAL_MAX_TURNS = 5;
24
+ /** The goal text the judge evaluates: title + description. */
25
+ export function goalTextForTask(task) {
26
+ return `${task.title}\n\n${task.description ?? ''}`.trim();
27
+ }
28
+ /**
29
+ * Continuation nudge fed to the worker on later goal-mode turns (port of
30
+ * Hermes' KANBAN_GOAL_CONTINUATION_TEMPLATE, adapted to the colab lifecycle:
31
+ * completion is decided by the loop's judge, so the worker is nudged to
32
+ * finish and state the outcome explicitly).
33
+ */
34
+ export function buildColabGoalContinuationPrompt(task) {
35
+ const criteria = task.acceptanceCriteria?.length
36
+ ? `\nAcceptance criteria (ALL must be satisfied):\n${task.acceptanceCriteria
37
+ .map((text, i) => `- ${i + 1}. ${text}`)
38
+ .join('\n')}\n`
39
+ : '';
40
+ const lastReason = task.goalLastReason ? `\nJudge's last verdict: ${task.goalLastReason}\n` : '';
41
+ return ('[Continuing toward this fleet task — the judge says it is not done yet]\n' +
42
+ `Task: ${goalTextForTask(task)}\n` +
43
+ criteria +
44
+ lastReason +
45
+ '\nFinish the remaining work. When everything is done, state completion ' +
46
+ 'explicitly with evidence (file contents, command output). If you are ' +
47
+ 'blocked, state the blocker clearly.');
48
+ }
49
+ /**
50
+ * Default judge: a one-shot call on the same tier model the worker ran on
51
+ * (local tiers stay free), overridable via `goals.judgeModel`. Task
52
+ * `acceptanceCriteria` become strict numbered criteria for the judge.
53
+ */
54
+ export function createColabGoalJudge() {
55
+ return async (task, result, model) => {
56
+ try {
57
+ const config = resolveGoalsConfig();
58
+ const apiKey = process.env.GROK_API_KEY || process.env.OPENAI_API_KEY || 'local';
59
+ const baseClient = new CodeBuddyClient(apiKey, model.model, model.baseUrl);
60
+ const client = await resolveGoalJudgeClient(baseClient, config.judgeModel, {
61
+ apiKey,
62
+ ...(model.baseUrl ? { baseURL: model.baseUrl } : {}),
63
+ providerLabel: model.baseUrl?.includes(':11434') ? 'ollama' : model.tier,
64
+ });
65
+ return await judgeGoal(client, {
66
+ goal: goalTextForTask(task),
67
+ lastResponse: result.output || result.summary,
68
+ ...(task.acceptanceCriteria?.length ? { subgoals: task.acceptanceCriteria } : {}),
69
+ ...(config.judgeModel ? { model: config.judgeModel } : {}),
70
+ maxTokens: config.judgeMaxTokens,
71
+ timeoutMs: config.judgeTimeoutMs,
72
+ });
73
+ }
74
+ catch (error) {
75
+ // Fail-open, like judgeGoal itself: never wedge the loop on judge setup.
76
+ logger.debug('colab goal judge: setup failed — continue', { error: String(error) });
77
+ return { verdict: 'continue', reason: `judge setup error: ${String(error)}`, parseFailed: false };
78
+ }
79
+ };
80
+ }
81
+ //# sourceMappingURL=colab-goal.js.map
@@ -40,6 +40,12 @@ export interface JobExecutionResult {
40
40
  skipReason?: string;
41
41
  /** For watchdog jobs: false when any check produced an alert/error. */
42
42
  watchdogOk?: boolean;
43
+ /**
44
+ * Structured output data for cross-job data passing. When this job is part
45
+ * of a chain (`then`), its outputData is forwarded as the next job's
46
+ * inputData.
47
+ */
48
+ outputData?: string;
43
49
  }
44
50
  export declare class CronAgentBridge extends EventEmitter {
45
51
  private config;
@@ -48,11 +54,11 @@ export declare class CronAgentBridge extends EventEmitter {
48
54
  /**
49
55
  * Create a task executor function for the CronScheduler
50
56
  */
51
- createTaskExecutor(): (job: CronJob) => Promise<unknown>;
57
+ createTaskExecutor(): (job: CronJob, inputData?: string) => Promise<unknown>;
52
58
  /**
53
59
  * Execute a cron job by creating an agent instance
54
60
  */
55
- executeJob(job: CronJob): Promise<JobExecutionResult>;
61
+ executeJob(job: CronJob, inputData?: string): Promise<JobExecutionResult>;
56
62
  /**
57
63
  * Start a durable run record for a job execution, when an observability store
58
64
  * is configured. Returns the run id, or undefined when recording is disabled
@@ -86,8 +92,11 @@ export declare class CronAgentBridge extends EventEmitter {
86
92
  * WITHOUT instantiating a CodeBuddyAgent or calling any model provider.
87
93
  * A non-zero exit (or timeout) throws so the run is recorded as failed and
88
94
  * any chained `then` job does not fire.
95
+ *
96
+ * Returns both the combined output (for logging/delivery) and the separate
97
+ * stdout (for cross-job data passing, capped at 64KB by the script runner).
89
98
  */
90
- private executeScriptTask;
99
+ private executeScriptTaskFull;
91
100
  /**
92
101
  * Execute a skill-type task — resolves a named skill from the SkillRegistry
93
102
  * and runs it via the SkillExecutor. Without a configured tool executor the