@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
@@ -9,6 +9,10 @@
9
9
  */
10
10
  import { logger } from '../utils/logger.js';
11
11
  import { createHash } from 'node:crypto';
12
+ import { maybeContinueGoalAfterTurn } from '../goals/goal-loop.js';
13
+ import { getGoalManager } from '../goals/goal-manager.js';
14
+ const COWORK_GOAL_SESSION_PREFIX = 'cowork:';
15
+ const GOAL_LOOP_HARD_BACKSTOP = 100;
12
16
  /**
13
17
  * Concrete implementation of EngineAdapter that wraps CodeBuddyAgent.
14
18
  *
@@ -40,6 +44,12 @@ export class CodeBuddyEngineAdapter {
40
44
  * effect on the next turn (Phase 8).
41
45
  */
42
46
  agentIdentities = new Map();
47
+ /**
48
+ * Configured reasoning/thinking level (`off | minimal | … | xhigh`), set via
49
+ * {@link setThinkingLevel}. Re-applied to each freshly-created agent so a
50
+ * model swap / new session keeps the user's chosen level.
51
+ */
52
+ thinkingLevel;
43
53
  /**
44
54
  * Hard cap on cached agents — matches the pi-runner's
45
55
  * `MAX_CACHED_SESSIONS` (50) so memory pressure is comparable
@@ -102,6 +112,13 @@ export class CodeBuddyEngineAdapter {
102
112
  if (typeof agent.setVisionGroundingModel === 'function') {
103
113
  agent.setVisionGroundingModel(this.config.visionGroundingModel);
104
114
  }
115
+ // Re-apply the configured thinking level so a freshly-created agent
116
+ // (new session or post model-swap) keeps the user's chosen level on the
117
+ // Gemini-native path. The OpenAI-compat / Grok / Ollama path reads the
118
+ // global extended-thinking budget per turn, so it needs no per-agent step.
119
+ if (this.thinkingLevel) {
120
+ this.applyGeminiThinkingLevel(agent, this.thinkingLevel);
121
+ }
105
122
  // Phase 9 — enforce LRU before insertion so we never exceed
106
123
  // the cap. Evict the least-recently-used (head of the
107
124
  // insertion-ordered Map) until there's room.
@@ -165,92 +182,166 @@ export class CodeBuddyEngineAdapter {
165
182
  toolCallCount,
166
183
  };
167
184
  }
168
- // Stream the response
169
- const stream = agent.processUserMessageStream(lastMessage.content);
170
- for await (const chunk of stream) {
171
- // Check for abort
172
- if (abortController.signal.aborted) {
173
- break;
174
- }
175
- switch (chunk.type) {
176
- case 'content':
177
- if (chunk.content) {
178
- fullContent += chunk.content;
179
- onEvent({ type: 'content', content: chunk.content });
180
- }
181
- break;
182
- case 'reasoning':
183
- if (chunk.reasoning) {
184
- onEvent({ type: 'thinking', thinking: chunk.reasoning });
185
- }
186
- break;
187
- case 'tool_calls':
188
- if (chunk.toolCalls) {
189
- for (const tc of chunk.toolCalls) {
190
- toolCallCount++;
185
+ const streamingAgent = agent;
186
+ const goalSessionKey = buildCoworkGoalSessionKey(sessionId);
187
+ const runPromptTurn = async (prompt) => {
188
+ let turnContent = '';
189
+ const toolEvidence = [];
190
+ // Tracks FAILED tool actions this turn so the goal judge can't be fooled
191
+ // into a premature "done" by an assistant that narrates success after a
192
+ // write/patch/command actually failed (Hermes-style mutation verifier).
193
+ const toolFailures = [];
194
+ const stream = streamingAgent.processUserMessageStream(prompt);
195
+ for await (const chunk of stream) {
196
+ // Check for abort
197
+ if (abortController.signal.aborted) {
198
+ return { interrupted: true, judgeResponse: buildGoalJudgeResponse(turnContent, toolEvidence, toolFailures) };
199
+ }
200
+ switch (chunk.type) {
201
+ case 'content':
202
+ if (chunk.content) {
203
+ turnContent += chunk.content;
204
+ fullContent += chunk.content;
205
+ onEvent({ type: 'content', content: chunk.content });
206
+ }
207
+ break;
208
+ case 'reasoning':
209
+ if (chunk.reasoning) {
210
+ onEvent({ type: 'thinking', thinking: chunk.reasoning });
211
+ }
212
+ break;
213
+ case 'tool_calls':
214
+ if (chunk.toolCalls) {
215
+ for (const tc of chunk.toolCalls) {
216
+ toolCallCount++;
217
+ onEvent({
218
+ type: 'tool_start',
219
+ tool: {
220
+ id: tc.id,
221
+ name: tc.function.name,
222
+ input: tc.function.arguments,
223
+ },
224
+ });
225
+ }
226
+ }
227
+ break;
228
+ case 'tool_result':
229
+ if (chunk.toolCall && chunk.toolResult) {
230
+ const finalOutput = chunk.toolResult.output || chunk.toolResult.error;
231
+ const toolStatus = chunk.toolResult.success ? 'success' : 'error';
232
+ if (finalOutput) {
233
+ toolEvidence.push(`[tool:${chunk.toolCall.function.name} ${toolStatus}]\n${String(finalOutput)}`);
234
+ }
235
+ // Record failures INDEPENDENTLY of finalOutput — a tool that
236
+ // fails with no output/error would otherwise be invisible to the
237
+ // judge (the silent-failure hole).
238
+ if (!chunk.toolResult.success) {
239
+ const detail = finalOutput ? `: ${truncateFailureDetail(String(finalOutput))}` : '';
240
+ toolFailures.push(`${chunk.toolCall.function.name}${detail}`);
241
+ }
191
242
  onEvent({
192
- type: 'tool_start',
243
+ type: 'tool_end',
193
244
  tool: {
194
- id: tc.id,
195
- name: tc.function.name,
196
- input: tc.function.arguments,
245
+ id: chunk.toolCall.id,
246
+ name: chunk.toolCall.function.name,
247
+ input: chunk.toolCall.function.arguments,
248
+ output: finalOutput,
249
+ isError: !chunk.toolResult.success,
250
+ data: chunk.toolResult.data,
197
251
  },
198
252
  });
199
253
  }
200
- }
201
- break;
202
- case 'tool_result':
203
- if (chunk.toolCall && chunk.toolResult) {
204
- const finalOutput = chunk.toolResult.output || chunk.toolResult.error;
205
- onEvent({
206
- type: 'tool_end',
207
- tool: {
208
- id: chunk.toolCall.id,
209
- name: chunk.toolCall.function.name,
210
- output: finalOutput,
211
- isError: !chunk.toolResult.success,
212
- data: chunk.toolResult.data,
213
- },
214
- });
215
- }
216
- break;
217
- case 'tool_stream':
218
- if (chunk.toolStreamData) {
219
- onEvent({
220
- type: 'tool_stream',
221
- tool: {
222
- id: chunk.toolStreamData.toolCallId,
223
- name: chunk.toolStreamData.toolName,
224
- delta: chunk.toolStreamData.delta,
225
- },
226
- });
227
- }
228
- break;
229
- case 'token_count':
230
- if (chunk.tokenCount !== undefined) {
231
- totalTokens = chunk.tokenCount;
232
- onEvent({ type: 'token_count', tokenCount: chunk.tokenCount });
233
- }
234
- break;
235
- case 'ask_user':
236
- if (chunk.askUser) {
237
- onEvent({ type: 'ask_user', askUser: chunk.askUser });
238
- }
239
- break;
240
- case 'plan_progress':
241
- if (chunk.planProgress) {
242
- onEvent({ type: 'plan_progress', planProgress: chunk.planProgress });
243
- }
244
- break;
245
- case 'diff_preview':
246
- if (chunk.diffPreview) {
247
- onEvent({ type: 'diff_preview', diffPreview: chunk.diffPreview });
248
- }
249
- break;
250
- case 'done':
251
- onEvent({ type: 'done' });
252
- break;
254
+ break;
255
+ case 'tool_stream':
256
+ if (chunk.toolStreamData) {
257
+ onEvent({
258
+ type: 'tool_stream',
259
+ tool: {
260
+ id: chunk.toolStreamData.toolCallId,
261
+ name: chunk.toolStreamData.toolName,
262
+ delta: chunk.toolStreamData.delta,
263
+ },
264
+ });
265
+ }
266
+ break;
267
+ case 'token_count':
268
+ if (chunk.tokenCount !== undefined) {
269
+ totalTokens = chunk.tokenCount;
270
+ onEvent({ type: 'token_count', tokenCount: chunk.tokenCount });
271
+ }
272
+ break;
273
+ case 'ask_user':
274
+ if (chunk.askUser) {
275
+ onEvent({ type: 'ask_user', askUser: chunk.askUser });
276
+ }
277
+ break;
278
+ case 'plan_progress':
279
+ if (chunk.planProgress) {
280
+ onEvent({ type: 'plan_progress', planProgress: chunk.planProgress });
281
+ }
282
+ break;
283
+ case 'steer':
284
+ if (chunk.steer) {
285
+ onEvent({ type: 'steer', steer: chunk.steer });
286
+ }
287
+ break;
288
+ case 'diff_preview':
289
+ if (chunk.diffPreview) {
290
+ onEvent({ type: 'diff_preview', diffPreview: chunk.diffPreview });
291
+ }
292
+ break;
293
+ case 'done':
294
+ onEvent({ type: 'done' });
295
+ break;
296
+ }
297
+ }
298
+ return { interrupted: false, judgeResponse: buildGoalJudgeResponse(turnContent, toolEvidence, toolFailures) };
299
+ };
300
+ const emitGoalStatus = (message) => {
301
+ const content = `${fullContent ? '\n\n' : ''}${message}\n\n`;
302
+ fullContent += content;
303
+ onEvent({ type: 'content', content });
304
+ };
305
+ // Structured goal-status event for host UIs (Cowork goal banner). Emits the
306
+ // current GoalState snapshot so the renderer can show turn progress without
307
+ // re-reading goal storage. Safe no-op when no goal is set.
308
+ const emitGoalSnapshot = () => {
309
+ const s = getGoalManager(goalSessionKey).state;
310
+ if (!s || s.status === 'cleared')
311
+ return;
312
+ onEvent({
313
+ type: 'goal_status',
314
+ goalStatus: {
315
+ goal: s.goal,
316
+ status: s.status,
317
+ turnsUsed: s.turnsUsed,
318
+ maxTurns: s.maxTurns,
319
+ ...(s.lastVerdict ? { lastVerdict: s.lastVerdict } : {}),
320
+ ...(s.lastReason ? { lastReason: s.lastReason } : {}),
321
+ },
322
+ });
323
+ };
324
+ // Emit once up-front so the banner appears the instant a goal turn starts
325
+ // (before the first judge verdict), then again after each judged turn.
326
+ emitGoalSnapshot();
327
+ let turn = await runPromptTurn(lastMessage.content);
328
+ for (let i = 0; i < GOAL_LOOP_HARD_BACKSTOP; i++) {
329
+ const outcome = await maybeContinueGoalAfterTurn({
330
+ client: streamingAgent.getClient?.() ?? null,
331
+ lastResponse: turn.judgeResponse,
332
+ interrupted: turn.interrupted,
333
+ sessionKey: goalSessionKey,
334
+ });
335
+ if (outcome?.message) {
336
+ emitGoalStatus(outcome.message);
253
337
  }
338
+ if (outcome) {
339
+ emitGoalSnapshot();
340
+ }
341
+ if (turn.interrupted || !outcome?.continuationPrompt) {
342
+ break;
343
+ }
344
+ turn = await runPromptTurn(outcome.continuationPrompt);
254
345
  }
255
346
  return {
256
347
  content: fullContent,
@@ -278,6 +369,18 @@ export class CodeBuddyEngineAdapter {
278
369
  logger.info('[CodeBuddyEngineAdapter] cancelled session', { sessionId });
279
370
  }
280
371
  }
372
+ steer(sessionId, prompt) {
373
+ const controller = this.abortControllers.get(sessionId);
374
+ const agent = this.agents.get(sessionId);
375
+ const queue = agent?.getMessageQueue?.();
376
+ if (!controller || controller.signal.aborted || !queue) {
377
+ return false;
378
+ }
379
+ queue.setMode('steer');
380
+ queue.enqueue({ content: prompt, source: 'cowork', timestamp: new Date() });
381
+ logger.info('[CodeBuddyEngineAdapter] steer delivered', { sessionId });
382
+ return true;
383
+ }
281
384
  clearSession(sessionId) {
282
385
  const agent = this.agents.get(sessionId);
283
386
  if (agent && typeof agent.dispose === 'function') {
@@ -352,6 +455,51 @@ export class CodeBuddyEngineAdapter {
352
455
  logger.warn('[CodeBuddyEngineAdapter] reloadSkills failed', { err });
353
456
  }
354
457
  }
458
+ /**
459
+ * Hot-swap the reasoning/thinking level for live sessions. Updates the global
460
+ * extended-thinking budget (read per-turn by the OpenAI-compat / Grok / Ollama
461
+ * providers → effective next turn, no rebuild) and the Gemini-native default on
462
+ * every cached agent. The level is remembered so future agents inherit it.
463
+ */
464
+ async setThinkingLevel(level) {
465
+ this.thinkingLevel = level;
466
+ try {
467
+ const { getExtendedThinking } = await import('../agent/extended-thinking.js');
468
+ getExtendedThinking().applyThinkingLevel(level);
469
+ }
470
+ catch (err) {
471
+ logger.warn('[CodeBuddyEngineAdapter] applyThinkingLevel failed', { err });
472
+ }
473
+ for (const agent of this.agents.values()) {
474
+ this.applyGeminiThinkingLevel(agent, level);
475
+ }
476
+ logger.info('[CodeBuddyEngineAdapter] thinkingLevel set', { level });
477
+ }
478
+ /**
479
+ * Map a UI level to the Gemini-native default thinking level on a single
480
+ * agent's client. `GeminiThinkingLevel` is `minimal|low|medium|high`, so
481
+ * `xhigh` clamps to `high` and `off`/unknown leaves the Gemini default
482
+ * untouched (the global extended-thinking switch already disables it for the
483
+ * OpenAI-compat path). Best-effort; never throws.
484
+ */
485
+ applyGeminiThinkingLevel(agent, level) {
486
+ const geminiLevel = level === 'minimal' || level === 'low' || level === 'medium'
487
+ ? level
488
+ : level === 'high' || level === 'xhigh'
489
+ ? 'high'
490
+ : null;
491
+ if (!geminiLevel)
492
+ return;
493
+ try {
494
+ const getClient = agent.getClient;
495
+ if (typeof getClient === 'function') {
496
+ getClient.call(agent)?.setDefaultThinkingLevel(geminiLevel);
497
+ }
498
+ }
499
+ catch {
500
+ /* best effort — Gemini path only */
501
+ }
502
+ }
355
503
  /**
356
504
  * Synchronise the core MCPManager singleton with the host's view of
357
505
  * the MCP servers. Called by Cowork at boot and after any
@@ -481,4 +629,33 @@ export class CodeBuddyEngineAdapter {
481
629
  });
482
630
  }
483
631
  }
632
+ function buildCoworkGoalSessionKey(sessionId) {
633
+ return `${COWORK_GOAL_SESSION_PREFIX}${sessionId}`;
634
+ }
635
+ /** Cap a single failure detail so the footer stays compact for the judge. */
636
+ export function truncateFailureDetail(detail, max = 160) {
637
+ const flat = detail.replace(/\s+/g, ' ').trim();
638
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
639
+ }
640
+ /**
641
+ * Build a prominent, NON-bracketed footer listing failed tool actions. The goal
642
+ * judge prompt (goal-state.ts) treats `[tool:…]` bracketed lines as ignorable
643
+ * metadata, so failures must be surfaced as a plain instruction line or the
644
+ * judge discounts them. Returns '' when there are no failures.
645
+ */
646
+ export function buildToolFailureFooter(failures) {
647
+ if (!failures.length)
648
+ return '';
649
+ return (`⚠️ ${failures.length} tool action(s) failed this turn: ${failures.join('; ')}. ` +
650
+ `Do NOT treat the goal as done if these failures block it — fix or work around them first.`);
651
+ }
652
+ export function buildGoalJudgeResponse(content, toolEvidence, toolFailures = []) {
653
+ const footer = buildToolFailureFooter(toolFailures);
654
+ const parts = [
655
+ content.trim(),
656
+ ...toolEvidence.map((part) => part.trim()),
657
+ footer, // last → most prominent for the LLM judge
658
+ ].filter(Boolean);
659
+ return parts.join('\n\n');
660
+ }
484
661
  //# sourceMappingURL=codebuddy-engine-adapter.js.map
@@ -40,6 +40,11 @@ export interface EngineAdapter {
40
40
  * Cancel a running session.
41
41
  */
42
42
  cancel(sessionId: string): void;
43
+ /**
44
+ * Deliver user guidance into an active run when the underlying agent
45
+ * supports steer mode. Returns false when there is no active run/agent.
46
+ */
47
+ steer?(sessionId: string, prompt: string): boolean | Promise<boolean>;
43
48
  /**
44
49
  * Clear internal state for a session (free memory, close resources).
45
50
  */
@@ -80,6 +85,15 @@ export interface EngineAdapter {
80
85
  * Optional — adapters without a skills system can omit this.
81
86
  */
82
87
  reloadSkills?(): Promise<void>;
88
+ /**
89
+ * Hot-swap the reasoning/thinking level for live sessions —
90
+ * `off | minimal | low | medium | high | xhigh`. Updates the global
91
+ * extended-thinking budget (read per-turn by the OpenAI-compat / Grok /
92
+ * Ollama providers) and the Gemini-native default on cached agents, so the
93
+ * Cowork ReasoningLevelPicker takes effect on the next turn without a session
94
+ * restart. Optional — adapters without a thinking system can omit this.
95
+ */
96
+ setThinkingLevel?(level: string): Promise<void>;
83
97
  /**
84
98
  * Set the default visual grounding fallback configuration.
85
99
  */
@@ -320,6 +320,13 @@ export class DesktopAutomationManager extends EventEmitter {
320
320
  const nativeProvider = await this.createNativeProvider();
321
321
  if (nativeProvider) {
322
322
  this.registerProvider(nativeProvider);
323
+ // The created provider registers under its own name (e.g. 'nutjs' on
324
+ // Linux when xdotool/xclip are unavailable). Also expose it under the
325
+ // requested 'native' key so the preferred-provider lookup resolves it
326
+ // instead of silently falling through to the mock provider.
327
+ if (!this.providers.has('native')) {
328
+ this.providers.set('native', nativeProvider);
329
+ }
323
330
  }
324
331
  }
325
332
  // Try preferred provider first
@@ -689,6 +696,15 @@ export class DesktopAutomationManager extends EventEmitter {
689
696
  return new WindowsNativeProvider({ wsl: false });
690
697
  }
691
698
  case 'linux': {
699
+ // Prefer nut-js (libnut): it provides mouse/keyboard/screen control
700
+ // without xdotool/xclip/wmctrl, which are frequently absent on minimal
701
+ // or remote Linux desktops. Fall back to the xdotool-based provider
702
+ // (which also does window management) only when libnut is unavailable.
703
+ const { NutJsProvider } = await import('./nutjs-provider.js');
704
+ const nut = new NutJsProvider();
705
+ if (await nut.isAvailable()) {
706
+ return nut;
707
+ }
692
708
  const { LinuxNativeProvider } = await import('./linux-native-provider.js');
693
709
  return new LinuxNativeProvider();
694
710
  }
@@ -0,0 +1,63 @@
1
+ export interface OmniParserElement {
2
+ /** Index in the server's parsed_content_list — this is the number drawn on the annotated (SOM) image. */
3
+ id: number;
4
+ /** 'text' | 'icon' (whatever the server reports). */
5
+ type: string;
6
+ /** OCR text or icon caption (the server's `content`). */
7
+ content: string;
8
+ /** Whether OmniParser flagged the element as interactive/clickable (`interactivity`). */
9
+ interactable: boolean;
10
+ /** Bounding box [x1, y1, x2, y2]. Pixels when `normalized` is false, else 0-1 ratios. */
11
+ bbox: [number, number, number, number];
12
+ /** Box center [x, y] — pixels when `normalized` is false. Handy for a follow-up click. */
13
+ center: [number, number];
14
+ /** True when bbox/center are 0-1 ratios (image dimensions were unknown). */
15
+ normalized: boolean;
16
+ }
17
+ export interface OmniParserResult {
18
+ elements: OmniParserElement[];
19
+ /** Base64 of the Set-of-Marks (numbered) image returned by the server, or the original on failure. */
20
+ annotatedImageBase64: string;
21
+ }
22
+ export interface ParseScreenOptions {
23
+ /** Screenshot pixel width — when provided, normalized boxes are scaled to pixels. */
24
+ width?: number;
25
+ /** Screenshot pixel height. */
26
+ height?: number;
27
+ }
28
+ /**
29
+ * Runner for OmniParser (Microsoft's screen-parsing model — github.com/microsoft/OmniParser).
30
+ *
31
+ * Talks to the OmniParser v2 `omniparserserver` FastAPI service (omnitool/omniparserserver):
32
+ * - POST {base}/parse/ body {"base64_image": "<b64>"}
33
+ * -> {"som_image_base64": "<b64>", "parsed_content_list": [{type, content, interactivity, bbox}], "latency"}
34
+ * - GET {base}/probe/ -> health check
35
+ *
36
+ * `bbox` from the server is normalized to 0-1; we scale it to pixels when the
37
+ * screenshot dimensions are known so the coordinates are directly clickable.
38
+ *
39
+ * The model is GPU/Python and must be self-hosted; configure the base URL via
40
+ * OMNIPARSER_API_URL (default http://localhost:8000). Off-server, every call
41
+ * degrades gracefully to empty elements + the original screenshot.
42
+ */
43
+ export declare class OmniParserRunner {
44
+ private baseUrl;
45
+ private apiKey;
46
+ constructor();
47
+ private get parseUrl();
48
+ private get probeUrl();
49
+ /**
50
+ * Send a base64-encoded screenshot to the OmniParser server for analysis.
51
+ *
52
+ * @param imageBase64 The raw screenshot in base64 (no data: prefix).
53
+ * @param opts Optional screenshot pixel dimensions used to scale normalized boxes.
54
+ * @returns Parsed elements and the annotated (Set-of-Marks) image.
55
+ */
56
+ parseScreen(imageBase64: string, opts?: ParseScreenOptions): Promise<OmniParserResult>;
57
+ /** Map OmniParser's parsed_content_list into our element shape, scaling boxes to pixels when possible. */
58
+ private mapElements;
59
+ /**
60
+ * Check if the OmniParser server is reachable via its /probe/ health endpoint.
61
+ */
62
+ isAvailable(): Promise<boolean>;
63
+ }
@@ -0,0 +1,115 @@
1
+ import { logger } from '../utils/logger.js';
2
+ /**
3
+ * Runner for OmniParser (Microsoft's screen-parsing model — github.com/microsoft/OmniParser).
4
+ *
5
+ * Talks to the OmniParser v2 `omniparserserver` FastAPI service (omnitool/omniparserserver):
6
+ * - POST {base}/parse/ body {"base64_image": "<b64>"}
7
+ * -> {"som_image_base64": "<b64>", "parsed_content_list": [{type, content, interactivity, bbox}], "latency"}
8
+ * - GET {base}/probe/ -> health check
9
+ *
10
+ * `bbox` from the server is normalized to 0-1; we scale it to pixels when the
11
+ * screenshot dimensions are known so the coordinates are directly clickable.
12
+ *
13
+ * The model is GPU/Python and must be self-hosted; configure the base URL via
14
+ * OMNIPARSER_API_URL (default http://localhost:8000). Off-server, every call
15
+ * degrades gracefully to empty elements + the original screenshot.
16
+ */
17
+ export class OmniParserRunner {
18
+ baseUrl;
19
+ apiKey;
20
+ constructor() {
21
+ // OMNIPARSER_API_URL is the server base (scheme://host:port); endpoints are derived.
22
+ const raw = process.env.OMNIPARSER_API_URL || 'http://localhost:8000';
23
+ this.baseUrl = raw.replace(/\/+$/, '');
24
+ this.apiKey = process.env.OMNIPARSER_API_KEY;
25
+ }
26
+ get parseUrl() {
27
+ return `${this.baseUrl}/parse/`;
28
+ }
29
+ get probeUrl() {
30
+ return `${this.baseUrl}/probe/`;
31
+ }
32
+ /**
33
+ * Send a base64-encoded screenshot to the OmniParser server for analysis.
34
+ *
35
+ * @param imageBase64 The raw screenshot in base64 (no data: prefix).
36
+ * @param opts Optional screenshot pixel dimensions used to scale normalized boxes.
37
+ * @returns Parsed elements and the annotated (Set-of-Marks) image.
38
+ */
39
+ async parseScreen(imageBase64, opts = {}) {
40
+ try {
41
+ logger.debug('Sending screenshot to OmniParser API', { url: this.parseUrl });
42
+ const headers = {
43
+ 'Content-Type': 'application/json',
44
+ };
45
+ if (this.apiKey) {
46
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
47
+ }
48
+ const response = await fetch(this.parseUrl, {
49
+ method: 'POST',
50
+ headers,
51
+ // OmniParser v2 ParseRequest expects the field `base64_image`.
52
+ body: JSON.stringify({ base64_image: imageBase64 }),
53
+ });
54
+ if (!response.ok) {
55
+ throw new Error(`OmniParser API returned status: ${response.status} ${response.statusText}`);
56
+ }
57
+ const data = (await response.json());
58
+ const elements = this.mapElements(data.parsed_content_list ?? [], opts);
59
+ return {
60
+ elements,
61
+ annotatedImageBase64: data.som_image_base64 || imageBase64,
62
+ };
63
+ }
64
+ catch (error) {
65
+ const msg = error instanceof Error ? error.message : String(error);
66
+ logger.error('OmniParser API call failed', { error: msg, url: this.parseUrl });
67
+ // Graceful fallback if the server is down/misconfigured: original image, no elements.
68
+ return {
69
+ elements: [],
70
+ annotatedImageBase64: imageBase64,
71
+ };
72
+ }
73
+ }
74
+ /** Map OmniParser's parsed_content_list into our element shape, scaling boxes to pixels when possible. */
75
+ mapElements(raw, opts) {
76
+ const { width, height } = opts;
77
+ const scalable = typeof width === 'number' && width > 0 && typeof height === 'number' && height > 0;
78
+ return raw.map((item, index) => {
79
+ const b = Array.isArray(item.bbox) && item.bbox.length === 4 ? item.bbox : [0, 0, 0, 0];
80
+ let [x1, y1, x2, y2] = [Number(b[0]) || 0, Number(b[1]) || 0, Number(b[2]) || 0, Number(b[3]) || 0];
81
+ if (scalable) {
82
+ x1 = Math.round(x1 * width);
83
+ y1 = Math.round(y1 * height);
84
+ x2 = Math.round(x2 * width);
85
+ y2 = Math.round(y2 * height);
86
+ }
87
+ return {
88
+ id: index,
89
+ type: item.type ?? 'unknown',
90
+ content: item.content ?? '',
91
+ interactable: item.interactivity ?? false,
92
+ bbox: [x1, y1, x2, y2],
93
+ center: [Math.round((x1 + x2) / 2), Math.round((y1 + y2) / 2)],
94
+ normalized: !scalable,
95
+ };
96
+ });
97
+ }
98
+ /**
99
+ * Check if the OmniParser server is reachable via its /probe/ health endpoint.
100
+ */
101
+ async isAvailable() {
102
+ try {
103
+ const headers = {};
104
+ if (this.apiKey) {
105
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
106
+ }
107
+ const response = await fetch(this.probeUrl, { method: 'GET', headers });
108
+ return response.ok;
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=omniparser-runner.js.map
@@ -132,6 +132,8 @@ export declare class SmartSnapshotManager extends EventEmitter {
132
132
  private snapshotCache;
133
133
  private static readonly MAX_CACHE_SIZE;
134
134
  private nextRef;
135
+ /** Cached python interpreter that has the Atspi GI binding (`''` = none found, `null` = not yet probed). */
136
+ private atspiPython;
135
137
  constructor(config?: Partial<SmartSnapshotConfig>);
136
138
  private runPowerShellEncoded;
137
139
  /**
@@ -206,6 +208,15 @@ export declare class SmartSnapshotManager extends EventEmitter {
206
208
  * Detect elements on WSL2 via PowerShell UIAutomation (same script as detectWindowsElements)
207
209
  */
208
210
  private detectWSLElements;
211
+ /**
212
+ * Resolve a python3 interpreter that actually has the Atspi GObject-Introspection
213
+ * binding. `python3` on PATH is frequently a conda/pyenv interpreter that lacks the
214
+ * system `gi`/Atspi typelib (PyGObject is installed against the system python), so a
215
+ * bare `python3 -c "import gi"` silently fails and the snapshot falls back to mock
216
+ * elements. We probe a small candidate list once and cache the winner.
217
+ * Returns '' when no interpreter has the binding.
218
+ */
219
+ private resolveAtspiPython;
209
220
  /**
210
221
  * Detect elements on native Linux via AT-SPI
211
222
  */