@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
@@ -31,7 +31,11 @@
31
31
  */
32
32
  import { beginFleetWork } from './fleet-load.js';
33
33
  import { registerPeerMethod, unregisterPeerMethod } from '../server/websocket/peer-rpc.js';
34
- import { broadcastChatSessionEnd, broadcastChatSessionStart, broadcastChatSessionTurn, } from '../server/websocket/fleet-bridge.js';
34
+ import { broadcastChatSessionEnd, broadcastChatSessionGoal, broadcastChatSessionStart, broadcastChatSessionTurn, } from '../server/websocket/fleet-bridge.js';
35
+ import { resolveGoalJudgeClientFailOpen } from '../goals/goal-judge-client.js';
36
+ import { judgeGoal } from '../goals/goal-judge.js';
37
+ import { resolveGoalsConfig } from '../goals/goal-manager.js';
38
+ import { applyJudgeOutcome, createGoalState, formatGoalStatusLine, getGoalJudgeCriteria, normalizeGoalState, renderSubgoalsBlock, } from '../goals/goal-state.js';
35
39
  import { logger } from '../utils/logger.js';
36
40
  import { getPeerSessionStore, } from './peer-session-store.js';
37
41
  import { DEFAULT_DISPATCH_POLICY_PREVIEW_TOOLS, FLEET_DISPATCH_PROFILES, buildDispatchSystemPrompt, buildHermesToolsetDescriptor, getDispatchToolPolicy, isFleetDispatchProfile, mergeDispatchSystemPrompt, normalizeDispatchProfile, } from './dispatch-profile.js';
@@ -104,6 +108,14 @@ function newSessionId() {
104
108
  const rand = Math.floor(Math.random() * 1e9).toString(36);
105
109
  return `sess_${ts}_${rand}`;
106
110
  }
111
+ function resolvePeerGoalMaxTurns(raw) {
112
+ if (raw === undefined)
113
+ return resolveGoalsConfig().maxTurns;
114
+ if (typeof raw !== 'number' || !Number.isSafeInteger(raw) || raw <= 0) {
115
+ throw new Error('peer.chat-session.goal: maxTurns must be a positive integer');
116
+ }
117
+ return raw;
118
+ }
107
119
  /**
108
120
  * Opportunistic GC — drop sessions whose `lastUsedAt` is older than
109
121
  * the configured idle window. Called at the top of each `start` and
@@ -144,10 +156,82 @@ function snapshot(session) {
144
156
  toolDecisions: session.toolDecisions,
145
157
  toolset: session.toolset,
146
158
  messages: [...session.messages],
159
+ ...(session.goal ? { goal: { ...session.goal, subgoals: [...session.goal.subgoals] } } : {}),
147
160
  createdAt: session.createdAt,
148
161
  lastUsedAt: session.lastUsedAt,
149
162
  };
150
163
  }
164
+ /**
165
+ * Post-turn goal hook shared by `continue` and `continue-stream`. Judges the
166
+ * assistant's response against the session goal, applies the Hermes decision
167
+ * ladder, and returns the report embedded in the RPC response. The judge is
168
+ * fail-open; this never throws. The caller persists the session afterwards.
169
+ */
170
+ async function evaluateSessionGoalAfterTurn(session, assistantText) {
171
+ const goal = session.goal;
172
+ if (!goal || goal.status !== 'active')
173
+ return null;
174
+ if (!assistantText.trim()) {
175
+ goal.status = 'paused';
176
+ goal.pausedReason = 'empty response (nothing to evaluate)';
177
+ const report = {
178
+ status: 'paused',
179
+ verdict: 'skipped',
180
+ reason: goal.pausedReason,
181
+ turnsUsed: goal.turnsUsed,
182
+ maxTurns: goal.maxTurns,
183
+ message: '⏸ Goal paused — the peer produced no judgeable response.',
184
+ };
185
+ broadcastChatSessionGoal({
186
+ sessionId: session.sessionId,
187
+ status: report.status,
188
+ verdict: report.verdict,
189
+ turnsUsed: report.turnsUsed,
190
+ maxTurns: report.maxTurns,
191
+ });
192
+ return report;
193
+ }
194
+ let report;
195
+ try {
196
+ const config = resolveGoalsConfig();
197
+ const baseClient = cachedGetter?.() ?? null;
198
+ const judgeClient = await resolveGoalJudgeClientFailOpen(baseClient, config.judgeModel);
199
+ const criteria = getGoalJudgeCriteria(goal);
200
+ const outcome = await judgeGoal(judgeClient, {
201
+ goal: goal.goal,
202
+ lastResponse: assistantText,
203
+ ...(criteria.length ? { subgoals: criteria } : {}),
204
+ ...(config.judgeModel ? { model: config.judgeModel } : {}),
205
+ maxTokens: config.judgeMaxTokens,
206
+ timeoutMs: config.judgeTimeoutMs,
207
+ });
208
+ const decision = applyJudgeOutcome(goal, outcome);
209
+ report = {
210
+ status: decision.status,
211
+ verdict: decision.verdict,
212
+ reason: decision.reason,
213
+ turnsUsed: goal.turnsUsed,
214
+ maxTurns: goal.maxTurns,
215
+ message: decision.message,
216
+ ...(decision.continuationPrompt ? { continuationPrompt: decision.continuationPrompt } : {}),
217
+ };
218
+ }
219
+ catch (err) {
220
+ logger.warn('[peer-session-bridge] goal evaluation failed — skipping this turn', {
221
+ sessionId: session.sessionId,
222
+ error: err instanceof Error ? err.message : String(err),
223
+ });
224
+ return null;
225
+ }
226
+ broadcastChatSessionGoal({
227
+ sessionId: session.sessionId,
228
+ status: report.status,
229
+ verdict: report.verdict,
230
+ turnsUsed: report.turnsUsed,
231
+ maxTurns: report.maxTurns,
232
+ });
233
+ return report;
234
+ }
151
235
  /**
152
236
  * Register the `peer.chat-session.*` methods. The `getClient` closure
153
237
  * is captured and called fresh on each invocation so the caller can
@@ -174,6 +258,7 @@ export async function wirePeerSessionBridge(getClient) {
174
258
  for (const p of persisted) {
175
259
  if (now - p.lastUsedAt > idleMs)
176
260
  continue; // double-check vs the boundary
261
+ const goal = p.goal ? normalizeGoalState(p.goal) : null;
177
262
  sessions.set(p.sessionId, {
178
263
  sessionId: p.sessionId,
179
264
  systemPrompt: p.systemPrompt,
@@ -183,6 +268,7 @@ export async function wirePeerSessionBridge(getClient) {
183
268
  toolDecisions: p.toolDecisions,
184
269
  toolset: p.toolset,
185
270
  messages: [...p.messages],
271
+ ...(goal && goal.status !== 'cleared' ? { goal } : {}),
186
272
  createdAt: p.createdAt,
187
273
  lastUsedAt: p.lastUsedAt,
188
274
  pending: Promise.resolve(),
@@ -312,6 +398,11 @@ export async function wirePeerSessionBridge(getClient) {
312
398
  const text = response?.choices?.[0]?.message?.content ?? '';
313
399
  session.messages.push({ role: 'assistant', content: text });
314
400
  session.lastUsedAt = Date.now();
401
+ // Goal Ralph-loop (Hermes gateway parity): judge the turn server-side,
402
+ // mutate the session goal state, and report the verdict to the caller
403
+ // (who drives the continuation). Runs BEFORE the disk flush so the
404
+ // snapshot below persists the updated goal counters.
405
+ const goalReport = await evaluateSessionGoalAfterTurn(session, text);
315
406
  // V1.2-saga — flush the new turn to disk before returning so a
316
407
  // crash mid-conversation can be replayed on next boot. Failure
317
408
  // logs but doesn't fail the turn: the caller already got an
@@ -340,6 +431,7 @@ export async function wirePeerSessionBridge(getClient) {
340
431
  finishReason: response?.choices?.[0]?.finish_reason,
341
432
  usage: response?.usage,
342
433
  traceId: ctx.traceId,
434
+ ...(goalReport ? { goal: goalReport } : {}),
343
435
  ...sessionPolicyMetadata(session),
344
436
  };
345
437
  };
@@ -439,6 +531,8 @@ export async function wirePeerSessionBridge(getClient) {
439
531
  }
440
532
  session.messages.push({ role: 'assistant', content: aggregate });
441
533
  session.lastUsedAt = Date.now();
534
+ // Goal Ralph-loop — same server-side judge as the non-streaming path.
535
+ const goalReport = await evaluateSessionGoalAfterTurn(session, aggregate);
442
536
  try {
443
537
  await getPeerSessionStore().save(snapshot(session));
444
538
  }
@@ -460,6 +554,7 @@ export async function wirePeerSessionBridge(getClient) {
460
554
  finishReason,
461
555
  usage,
462
556
  traceId: ctx.traceId,
557
+ ...(goalReport ? { goal: goalReport } : {}),
463
558
  ...sessionPolicyMetadata(session),
464
559
  };
465
560
  };
@@ -497,6 +592,150 @@ export async function wirePeerSessionBridge(getClient) {
497
592
  traceId: ctx.traceId,
498
593
  };
499
594
  });
595
+ // Goal Ralph-loop controls (Hermes gateway parity). One method, action-
596
+ // dispatched: set | status | pause | resume | clear | subgoal-add |
597
+ // subgoal-list | subgoal-remove | subgoal-clear. Mirrors the /goal +
598
+ // /subgoal slash surface. Setting a NEW goal while one is active is
599
+ // rejected (Hermes mid-run rule) — pause/clear first. status/pause/
600
+ // resume/clear are safe mid-turn: they only touch goal metadata.
601
+ registerPeerMethod('peer.chat-session.goal', async (params, ctx) => {
602
+ const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
603
+ const action = typeof params.action === 'string' ? params.action : 'status';
604
+ if (!sessionId) {
605
+ throw new Error('peer.chat-session.goal: sessionId is required (string)');
606
+ }
607
+ const session = sessions.get(sessionId);
608
+ if (!session) {
609
+ throw new Error(`SESSION_NOT_FOUND: no session with id "${sessionId}"`);
610
+ }
611
+ const persist = async () => {
612
+ try {
613
+ await getPeerSessionStore().save(snapshot(session));
614
+ }
615
+ catch (err) {
616
+ logger.warn('[peer-session-bridge] save on goal mutation failed', {
617
+ sessionId,
618
+ error: err instanceof Error ? err.message : String(err),
619
+ });
620
+ }
621
+ };
622
+ const emitGoal = (status) => {
623
+ broadcastChatSessionGoal({
624
+ sessionId,
625
+ status,
626
+ ...(session.goal ? { turnsUsed: session.goal.turnsUsed, maxTurns: session.goal.maxTurns } : {}),
627
+ });
628
+ };
629
+ const goalView = () => session.goal
630
+ ? {
631
+ goal: session.goal.goal,
632
+ status: session.goal.status,
633
+ turnsUsed: session.goal.turnsUsed,
634
+ maxTurns: session.goal.maxTurns,
635
+ subgoals: [...session.goal.subgoals],
636
+ ...(session.goal.goalPlan ? { goalPlan: session.goal.goalPlan } : {}),
637
+ ...(session.goal.lastVerdict ? { lastVerdict: session.goal.lastVerdict } : {}),
638
+ ...(session.goal.lastReason ? { lastReason: session.goal.lastReason } : {}),
639
+ ...(session.goal.pausedReason ? { pausedReason: session.goal.pausedReason } : {}),
640
+ statusLine: formatGoalStatusLine(session.goal),
641
+ }
642
+ : { status: 'none', statusLine: formatGoalStatusLine(null) };
643
+ switch (action) {
644
+ case 'set': {
645
+ const text = typeof params.goal === 'string' ? params.goal.trim() : '';
646
+ if (!text) {
647
+ throw new Error('peer.chat-session.goal: action "set" requires goal text (string)');
648
+ }
649
+ if (session.goal?.status === 'active') {
650
+ throw new Error('GOAL_ACTIVE: a goal is already active on this session. ' +
651
+ 'Use action "status"/"pause"/"clear" mid-run; clear it before setting a new goal.');
652
+ }
653
+ const maxTurns = resolvePeerGoalMaxTurns(params.maxTurns);
654
+ session.goal = createGoalState(text, maxTurns);
655
+ await persist();
656
+ emitGoal('active');
657
+ return { ...goalView(), traceId: ctx.traceId };
658
+ }
659
+ case 'status':
660
+ return { ...goalView(), traceId: ctx.traceId };
661
+ case 'pause': {
662
+ if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
663
+ return { ...goalView(), traceId: ctx.traceId };
664
+ }
665
+ session.goal.status = 'paused';
666
+ session.goal.pausedReason = 'user-paused';
667
+ await persist();
668
+ emitGoal('paused');
669
+ return { ...goalView(), traceId: ctx.traceId };
670
+ }
671
+ case 'resume': {
672
+ if (!session.goal || session.goal.status !== 'paused') {
673
+ return { ...goalView(), traceId: ctx.traceId };
674
+ }
675
+ session.goal.status = 'active';
676
+ delete session.goal.pausedReason;
677
+ delete session.goal.lastVerdict;
678
+ delete session.goal.lastReason;
679
+ session.goal.consecutiveParseFailures = 0;
680
+ session.goal.turnsUsed = 0; // Hermes resume semantics: budget reset
681
+ await persist();
682
+ emitGoal('active');
683
+ return { ...goalView(), traceId: ctx.traceId };
684
+ }
685
+ case 'clear': {
686
+ const had = Boolean(session.goal);
687
+ delete session.goal;
688
+ await persist();
689
+ if (had)
690
+ emitGoal('cleared');
691
+ return { cleared: had, ...goalView(), traceId: ctx.traceId };
692
+ }
693
+ case 'subgoal-add': {
694
+ const text = typeof params.text === 'string' ? params.text.trim() : '';
695
+ if (!text) {
696
+ throw new Error('peer.chat-session.goal: action "subgoal-add" requires text (string)');
697
+ }
698
+ if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
699
+ throw new Error('NO_ACTIVE_GOAL: set a goal before adding subgoals');
700
+ }
701
+ session.goal.subgoals.push(text);
702
+ await persist();
703
+ return { ...goalView(), traceId: ctx.traceId };
704
+ }
705
+ case 'subgoal-list':
706
+ return {
707
+ ...goalView(),
708
+ rendered: session.goal ? renderSubgoalsBlock(session.goal.subgoals) : '',
709
+ traceId: ctx.traceId,
710
+ };
711
+ case 'subgoal-remove': {
712
+ const index = typeof params.index === 'number' ? params.index : NaN;
713
+ if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
714
+ throw new Error('NO_ACTIVE_GOAL: set a goal before removing subgoals');
715
+ }
716
+ if (!Number.isSafeInteger(index) || index < 1) {
717
+ throw new Error('peer.chat-session.goal: subgoal index must be a positive integer');
718
+ }
719
+ if (index > session.goal.subgoals.length) {
720
+ throw new Error(`peer.chat-session.goal: subgoal index out of range (1..${session.goal.subgoals.length})`);
721
+ }
722
+ const [removed] = session.goal.subgoals.splice(index - 1, 1);
723
+ await persist();
724
+ return { removed, ...goalView(), traceId: ctx.traceId };
725
+ }
726
+ case 'subgoal-clear': {
727
+ if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
728
+ throw new Error('NO_ACTIVE_GOAL: set a goal before clearing subgoals');
729
+ }
730
+ const previous = session.goal.subgoals.length;
731
+ session.goal.subgoals = [];
732
+ await persist();
733
+ return { cleared: previous, ...goalView(), traceId: ctx.traceId };
734
+ }
735
+ default:
736
+ throw new Error(`peer.chat-session.goal: unknown action "${action}" (expected set | status | pause | resume | clear | subgoal-add | subgoal-list | subgoal-remove | subgoal-clear)`);
737
+ }
738
+ });
500
739
  registerPeerMethod('peer.chat-session.end', async (params, ctx) => {
501
740
  const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
502
741
  if (!sessionId) {
@@ -520,13 +759,14 @@ export async function wirePeerSessionBridge(getClient) {
520
759
  wired = true;
521
760
  logger.debug('[peer-session-bridge] wired');
522
761
  }
523
- /** Detach all three methods. Idempotent. Does NOT clear in-memory sessions. */
762
+ /** Detach all registered methods. Idempotent. Does NOT clear in-memory sessions. */
524
763
  export function unwirePeerSessionBridge() {
525
764
  if (!wired)
526
765
  return;
527
766
  unregisterPeerMethod('peer.chat-session.start');
528
767
  unregisterPeerMethod('peer.chat-session.continue');
529
768
  unregisterPeerMethod('peer.chat-session.continue-stream');
769
+ unregisterPeerMethod('peer.chat-session.goal');
530
770
  unregisterPeerMethod('peer.chat-session.end');
531
771
  cachedGetter = null;
532
772
  wired = false;
@@ -546,6 +786,7 @@ export function _unwireForTests() {
546
786
  unregisterPeerMethod('peer.chat-session.start');
547
787
  unregisterPeerMethod('peer.chat-session.continue');
548
788
  unregisterPeerMethod('peer.chat-session.continue-stream');
789
+ unregisterPeerMethod('peer.chat-session.goal');
549
790
  unregisterPeerMethod('peer.chat-session.list');
550
791
  unregisterPeerMethod('peer.chat-session.end');
551
792
  }
@@ -27,6 +27,7 @@
27
27
  *
28
28
  * @module fleet/peer-session-store
29
29
  */
30
+ import type { GoalState } from '../goals/goal-state.js';
30
31
  import type { FleetDispatchProfile, FleetHermesToolsetDescriptor, FleetDispatchToolDecision, FleetDispatchToolPolicy } from './dispatch-profile.js';
31
32
  export interface PersistedChatMessage {
32
33
  role: 'system' | 'user' | 'assistant';
@@ -42,6 +43,8 @@ export interface PersistedChatSession {
42
43
  toolset?: FleetHermesToolsetDescriptor;
43
44
  /** User/assistant turns (system prompt is held separately). */
44
45
  messages: PersistedChatMessage[];
46
+ /** Standing goal attached via `peer.chat-session.goal` (Hermes gateway parity). */
47
+ goal?: GoalState;
45
48
  createdAt: number;
46
49
  lastUsedAt: number;
47
50
  }
@@ -34,6 +34,7 @@ import { PolicyEngine } from '../security/policy-engine.js';
34
34
  import { ConfirmationService } from '../utils/confirmation-service.js';
35
35
  import { auditLogger } from '../security/audit-logger.js';
36
36
  import { assertPeerToolInvokeAllowed } from './permissions.js';
37
+ import { getGlobalEventBus } from '../events/event-bus.js';
37
38
  // ──────────────────────────────────────────────────────────────────
38
39
  // Allowlist & gates
39
40
  // ──────────────────────────────────────────────────────────────────
@@ -397,6 +398,19 @@ async function runInvocation(params, ctx, stream) {
397
398
  }
398
399
  }
399
400
  function logAudit(input) {
401
+ try {
402
+ getGlobalEventBus().emit('fleet:activity', {
403
+ activityType: 'fleet.tool.invoke',
404
+ title: 'Peer Tool Invoked',
405
+ description: input.error
406
+ ? `Peer ${input.ctx.connectionId} failed to invoke ${input.tool}: ${input.error}`
407
+ : `Peer ${input.ctx.connectionId} invoked ${input.tool}`,
408
+ metadata: { peerId: input.ctx.connectionId, tool: input.tool, ok: input.ok, error: input.error }
409
+ });
410
+ }
411
+ catch (err) {
412
+ // ignore
413
+ }
400
414
  auditLogger.log({
401
415
  action: 'tool_execution',
402
416
  decision: input.ok ? 'allow' : 'block',
@@ -35,3 +35,11 @@ export interface PrivacyLintResult {
35
35
  * Scan a prompt for secrets. Returns matches with previews.
36
36
  */
37
37
  export declare function scanForSecrets(prompt: string): PrivacyLintResult;
38
+ /**
39
+ * Replace every secret/PII match with a `[REDACTED:<kind>]` marker.
40
+ *
41
+ * Run this on FULL text before any truncation — cutting a PEM block (or
42
+ * any multi-line secret) in half can hide it from the patterns above.
43
+ * Used by every memory-persistence path (WS3 guard-rail).
44
+ */
45
+ export declare function redactSecrets(text: string): string;
@@ -164,4 +164,26 @@ function redactPreview(text, start, end) {
164
164
  : matched.slice(0, 4) + '…[redacted]…' + matched.slice(-4);
165
165
  return `${before}${redacted}${after}`.replace(/\s+/g, ' ').trim();
166
166
  }
167
+ /**
168
+ * Replace every secret/PII match with a `[REDACTED:<kind>]` marker.
169
+ *
170
+ * Run this on FULL text before any truncation — cutting a PEM block (or
171
+ * any multi-line secret) in half can hide it from the patterns above.
172
+ * Used by every memory-persistence path (WS3 guard-rail).
173
+ */
174
+ export function redactSecrets(text) {
175
+ const lint = scanForSecrets(text);
176
+ if (!lint.hasSecrets)
177
+ return text;
178
+ let out = '';
179
+ let cursor = 0;
180
+ for (const match of [...lint.matches].sort((a, b) => a.start - b.start)) {
181
+ if (match.start < cursor)
182
+ continue; // overlapping match already covered
183
+ out += text.slice(cursor, match.start) + `[REDACTED:${match.kind}]`;
184
+ cursor = match.end;
185
+ }
186
+ out += text.slice(cursor);
187
+ return out;
188
+ }
167
189
  //# sourceMappingURL=privacy-lint.js.map
@@ -0,0 +1,27 @@
1
+ import type { CodeBuddyClient } from '../codebuddy/client.js';
2
+ export interface GoalPlanSubtask {
3
+ id: string;
4
+ title: string;
5
+ description?: string;
6
+ acceptanceCriteria: string[];
7
+ }
8
+ export interface GoalPlanTask extends GoalPlanSubtask {
9
+ dependsOn: string[];
10
+ subtasks: GoalPlanSubtask[];
11
+ }
12
+ export interface GoalPlan {
13
+ summary: string;
14
+ tasks: GoalPlanTask[];
15
+ notes?: string[];
16
+ }
17
+ export interface DecomposeGoalOptions {
18
+ model?: string;
19
+ maxTokens?: number;
20
+ timeoutMs?: number;
21
+ }
22
+ export declare function shouldAutoDecomposeGoal(goal: string): boolean;
23
+ export declare function decomposeGoal(goal: string, client: CodeBuddyClient, options?: DecomposeGoalOptions): Promise<GoalPlan | null>;
24
+ export declare function parseGoalPlan(raw: string, originalGoal?: string): GoalPlan | null;
25
+ export declare function normalizeGoalPlan(raw: unknown, originalGoal?: string): GoalPlan | null;
26
+ export declare function goalPlanToCriteria(plan: GoalPlan | undefined, maxCriteria?: number): string[];
27
+ export declare function formatGoalPlan(plan: GoalPlan): string;