@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
@@ -43,6 +43,8 @@ export class SmartSnapshotManager extends EventEmitter {
43
43
  snapshotCache = new Map();
44
44
  static MAX_CACHE_SIZE = 20;
45
45
  nextRef = 1;
46
+ /** Cached python interpreter that has the Atspi GI binding (`''` = none found, `null` = not yet probed). */
47
+ atspiPython = null;
46
48
  constructor(config = {}) {
47
49
  super();
48
50
  this.config = { ...DEFAULT_CONFIG, ...config };
@@ -678,17 +680,76 @@ if ($focused) {
678
680
  }
679
681
  return elements;
680
682
  }
683
+ /**
684
+ * Resolve a python3 interpreter that actually has the Atspi GObject-Introspection
685
+ * binding. `python3` on PATH is frequently a conda/pyenv interpreter that lacks the
686
+ * system `gi`/Atspi typelib (PyGObject is installed against the system python), so a
687
+ * bare `python3 -c "import gi"` silently fails and the snapshot falls back to mock
688
+ * elements. We probe a small candidate list once and cache the winner.
689
+ * Returns '' when no interpreter has the binding.
690
+ */
691
+ resolveAtspiPython() {
692
+ if (this.atspiPython !== null) {
693
+ return this.atspiPython;
694
+ }
695
+ const candidates = [
696
+ process.env.CODEBUDDY_ATSPI_PYTHON,
697
+ 'python3',
698
+ '/usr/bin/python3',
699
+ 'python',
700
+ ].filter((c) => typeof c === 'string' && c.length > 0);
701
+ // Probe the *exact* requirement — a python can have `gi` yet lack the Atspi typelib.
702
+ const probe = `import gi; gi.require_version('Atspi','2.0'); from gi.repository import Atspi; print('OK')`;
703
+ let transientFailure = false;
704
+ for (const candidate of candidates) {
705
+ try {
706
+ const out = execSync(`${candidate} -c "${probe}" 2>/dev/null`, {
707
+ encoding: 'utf-8',
708
+ timeout: 5000,
709
+ }).trim();
710
+ if (out.includes('OK')) {
711
+ this.atspiPython = candidate;
712
+ return candidate;
713
+ }
714
+ }
715
+ catch (err) {
716
+ // Distinguish a transient timeout/kill (machine under load) from a genuine
717
+ // missing interpreter/binding. A timeout must NOT poison the cache, or a
718
+ // single slow probe would silently downgrade every later snapshot to mock
719
+ // until the process restarts.
720
+ const e = err;
721
+ if (e?.signal === 'SIGTERM' || e?.code === 'ETIMEDOUT')
722
+ transientFailure = true;
723
+ }
724
+ }
725
+ if (transientFailure) {
726
+ // Leave the cache unset (null) so the next snapshot re-probes once load clears.
727
+ return '';
728
+ }
729
+ // Every candidate cleanly failed (absent/binding missing) — cache to avoid
730
+ // re-probing 3 interpreters on every snapshot.
731
+ this.atspiPython = '';
732
+ return '';
733
+ }
681
734
  /**
682
735
  * Detect elements on native Linux via AT-SPI
683
736
  */
684
737
  async detectLinuxATSPIElements(_options) {
685
738
  const elements = [];
739
+ const python = this.resolveAtspiPython();
740
+ if (!python) {
741
+ logger.warn('AT-SPI element enumeration unavailable: no python3 interpreter with the gi/Atspi binding found. ' +
742
+ 'Install python3-gi + gir1.2-atspi-2.0, or set CODEBUDDY_ATSPI_PYTHON to a python that has them. ' +
743
+ 'Falling back to mock UI elements (NOT a real snapshot).');
744
+ elements.push(...this.getMockElements());
745
+ return elements;
746
+ }
686
747
  try {
687
748
  // Use AT-SPI via python-atspi or accerciser
688
749
  // This requires libatspi and python3-atspi2 to be installed
689
750
  const script = `
690
751
  import gi
691
- gi.require_version('Atspi', '2.0')
752
+ gi.require_version("Atspi", "2.0")
692
753
  from gi.repository import Atspi
693
754
  import json
694
755
 
@@ -733,9 +794,14 @@ for i in range(desktop.get_child_count()):
733
794
 
734
795
  print(json.dumps(all_elements[:100]))
735
796
  `;
736
- // Try to run the Python script
797
+ // Try to run the Python script. Pass it base64-encoded and decode inside the
798
+ // interpreter — embedding a multi-line script (which itself contains quotes)
799
+ // directly in a `-c '...'` shell argument breaks quoting and silently yields a
800
+ // NameError, masking real AT-SPI output behind the mock fallback.
737
801
  try {
738
- const { stdout } = await execAsync(`python3 -c '${script}' 2>/dev/null`, {
802
+ const encoded = Buffer.from(script, 'utf-8').toString('base64');
803
+ const runner = `import base64; exec(base64.b64decode("${encoded}").decode("utf-8"))`;
804
+ const { stdout } = await execAsync(`${python} -c '${runner}' 2>/dev/null`, {
739
805
  timeout: 10000,
740
806
  });
741
807
  const parsed = JSON.parse(stdout);
@@ -758,12 +824,13 @@ print(json.dumps(all_elements[:100]))
758
824
  }
759
825
  }
760
826
  catch (_err) {
761
- // Intentionally ignored: AT-SPI parsing may fail, fallback to mock elements
827
+ // AT-SPI query/parse failed even though a gi-capable python was found.
828
+ logger.warn('AT-SPI enumeration failed at query time; falling back to mock UI elements (NOT a real snapshot)', { error: _err });
762
829
  elements.push(...this.getMockElements());
763
830
  }
764
831
  }
765
832
  catch (error) {
766
- logger.debug('Linux accessibility detection failed', { error });
833
+ logger.warn('Linux accessibility detection failed; falling back to mock UI elements (NOT a real snapshot)', { error });
767
834
  elements.push(...this.getMockElements());
768
835
  }
769
836
  return elements;
@@ -1,7 +1,8 @@
1
1
  import { execSync } from 'child_process';
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, statfsSync, unlinkSync, writeFileSync, } from 'fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync, } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { logger } from '../utils/logger.js';
5
+ import { getFreeSpaceInfo } from '../utils/disk-guard.js';
5
6
  function commandExists(cmd) {
6
7
  try {
7
8
  const lookupCommand = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
@@ -17,10 +18,34 @@ function getCommandAvailability(cmd) {
17
18
  }
18
19
  function checkNodeVersion() {
19
20
  const major = parseInt(process.version.slice(1), 10);
20
- if (major >= 18) {
21
- return { name: 'Node.js version', status: 'ok', message: `${process.version} (>= 18 required)` };
21
+ if (major < 18) {
22
+ return { name: 'Node.js version', status: 'error', message: `${process.version} — Node.js >= 18 is required` };
23
+ }
24
+ if (major < 22) {
25
+ return {
26
+ name: 'Node.js version',
27
+ status: 'warn',
28
+ message: `${process.version} — OK for the CLI (>= 18), but the Cowork desktop app needs >= 22`,
29
+ };
30
+ }
31
+ return { name: 'Node.js version', status: 'ok', message: `${process.version} (CLI >= 18 and Cowork >= 22 OK)` };
32
+ }
33
+ // The SQLite layer (memory/sessions/cache/analytics) is a native module. On a
34
+ // platform without a matching prebuilt binary, `npm install` may have failed to
35
+ // build it — surface that here clearly instead of crashing a DB feature later.
36
+ async function checkNativeSqlite() {
37
+ try {
38
+ await import('better-sqlite3');
39
+ return { name: 'SQLite (better-sqlite3)', status: 'ok', message: 'native module available' };
40
+ }
41
+ catch {
42
+ return {
43
+ name: 'SQLite (better-sqlite3)',
44
+ status: 'warn',
45
+ message: 'native module not built — DB-backed features (memory/sessions/cache) are unavailable. ' +
46
+ 'Install build tools and reinstall (Linux: `build-essential python3`; macOS: `xcode-select --install`).',
47
+ };
22
48
  }
23
- return { name: 'Node.js version', status: 'error', message: `${process.version} — Node.js >= 18 is required` };
24
49
  }
25
50
  function checkDependencies() {
26
51
  const checks = [];
@@ -170,33 +195,24 @@ function checkTtsProviders() {
170
195
  { cmd: 'espeak', label: 'espeak' },
171
196
  ];
172
197
  const found = providers.filter(p => commandExists(p.cmd));
173
- try {
174
- // Check if kokoro-js is installed in workspace node_modules
175
- import.meta.resolve?.('kokoro-js');
176
- found.push({ cmd: 'kokoro', label: 'kokoro (npm)' });
177
- }
178
- catch {
179
- // ignore
180
- }
181
198
  return [{
182
199
  name: 'TTS providers',
183
200
  status: found.length > 0 ? 'ok' : 'warn',
184
- message: found.length > 0 ? `available: ${found.map(p => p.label).join(', ')}` : 'none found (install edge-tts, espeak or use kokoro via npm)',
201
+ message: found.length > 0 ? `available: ${found.map(p => p.label).join(', ')}` : 'none found (install edge-tts, espeak)',
185
202
  }];
186
203
  }
187
204
  function checkDiskSpace(cwd) {
188
- try {
189
- const stats = statfsSync(cwd);
190
- const freeBytes = stats.bfree * stats.bsize;
191
- const freeGB = freeBytes / (1024 ** 3);
192
- if (freeGB < 1) {
193
- return { name: 'Disk space', status: 'warn', message: `${freeGB.toFixed(2)} GB free (< 1 GB)` };
194
- }
195
- return { name: 'Disk space', status: 'ok', message: `${freeGB.toFixed(1)} GB free` };
196
- }
197
- catch {
205
+ // Single source of truth (disk-guard). Uses bavail — free space actually
206
+ // available to a non-root process — rather than bfree.
207
+ const info = getFreeSpaceInfo(cwd);
208
+ if (info === null) {
198
209
  return { name: 'Disk space', status: 'warn', message: 'unable to check' };
199
210
  }
211
+ const freeGB = info.freeBytes / (1024 ** 3);
212
+ if (freeGB < 1) {
213
+ return { name: 'Disk space', status: 'warn', message: `${freeGB.toFixed(2)} GB free (< 1 GB)` };
214
+ }
215
+ return { name: 'Disk space', status: 'ok', message: `${freeGB.toFixed(1)} GB free` };
200
216
  }
201
217
  function checkGit(cwd) {
202
218
  if (!commandExists('git')) {
@@ -385,6 +401,7 @@ export async function runDoctorChecks(cwd) {
385
401
  const dir = cwd ?? process.cwd();
386
402
  return [
387
403
  checkNodeVersion(),
404
+ await checkNativeSqlite(),
388
405
  ...checkDependencies(),
389
406
  ...checkApiKeys(),
390
407
  await checkChatGptOAuth(),
@@ -629,6 +629,16 @@ export interface CostEvents extends Record<string, BaseEvent> {
629
629
  'cost:limit_reached': CostLimitReachedEvent;
630
630
  'cost:warning': CostWarningEvent;
631
631
  }
632
+ export interface FleetActivityEvent extends BaseEvent {
633
+ type: 'fleet:activity';
634
+ activityType: string;
635
+ title: string;
636
+ description: string;
637
+ metadata?: Record<string, unknown>;
638
+ }
639
+ export interface FleetEvents extends Record<string, BaseEvent> {
640
+ 'fleet:activity': FleetActivityEvent;
641
+ }
632
642
  /**
633
643
  * Complete map of all application events.
634
644
  * This is the master type for the unified event system.
@@ -723,6 +733,7 @@ export interface AllEvents extends Record<string, BaseEvent> {
723
733
  'sandbox:created': SandboxCreatedEvent;
724
734
  'sandbox:destroyed': SandboxDestroyedEvent;
725
735
  'sandbox:execution': SandboxExecutionEvent;
736
+ 'fleet:activity': FleetActivityEvent;
726
737
  'cost:updated': CostUpdatedEvent;
727
738
  'cost:limit_reached': CostLimitReachedEvent;
728
739
  'cost:warning': CostWarningEvent;
@@ -41,6 +41,17 @@ export function defaultFleetAgentId(cwd = process.cwd()) {
41
41
  const repo = path.basename(path.resolve(cwd)) || 'repo';
42
42
  return `${host}/${repo}`;
43
43
  }
44
+ function resolveGoalMaxTurns(raw) {
45
+ if (raw === undefined)
46
+ return undefined;
47
+ if (typeof raw !== 'number' || !Number.isSafeInteger(raw) || raw <= 0) {
48
+ throw new Error('goalMaxTurns must be a positive integer');
49
+ }
50
+ return raw;
51
+ }
52
+ function normalizeGoalTurnsUsed(raw) {
53
+ return typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0 ? raw : 0;
54
+ }
44
55
  export class FleetColabStore {
45
56
  dir;
46
57
  agentId;
@@ -216,7 +227,7 @@ export class FleetColabStore {
216
227
  const task = file.tasks.find((t) => t.id === taskId);
217
228
  if (!task)
218
229
  throw new Error(`Unknown fleet task '${taskId}'`);
219
- task.goalTurnsUsed = (task.goalTurnsUsed ?? 0) + 1;
230
+ task.goalTurnsUsed = normalizeGoalTurnsUsed(task.goalTurnsUsed) + 1;
220
231
  task.goalLastReason = reason;
221
232
  this.writeTasks(file);
222
233
  return { ...task };
@@ -235,6 +246,7 @@ export class FleetColabStore {
235
246
  }
236
247
  addTask(input) {
237
248
  const file = this.readTasks();
249
+ const goalMaxTurns = resolveGoalMaxTurns(input.goalMaxTurns);
238
250
  const task = {
239
251
  id: input.id ?? this.generateId('task'),
240
252
  title: input.title,
@@ -249,7 +261,7 @@ export class FleetColabStore {
249
261
  ...(input.verifyCommand ? { verifyCommand: input.verifyCommand } : {}),
250
262
  ...(input.dependsOn && input.dependsOn.length > 0 ? { dependsOn: [...new Set(input.dependsOn)] } : {}),
251
263
  ...(input.goalMode ? { goalMode: true } : {}),
252
- ...(input.goalMaxTurns && input.goalMaxTurns > 0 ? { goalMaxTurns: Math.trunc(input.goalMaxTurns) } : {}),
264
+ ...(goalMaxTurns !== undefined ? { goalMaxTurns } : {}),
253
265
  createdBy: input.createdBy ?? this.agentId,
254
266
  createdAt: this.isoNow(),
255
267
  };
@@ -32,9 +32,10 @@
32
32
  import { beginFleetWork } from './fleet-load.js';
33
33
  import { registerPeerMethod, unregisterPeerMethod } from '../server/websocket/peer-rpc.js';
34
34
  import { broadcastChatSessionEnd, broadcastChatSessionGoal, broadcastChatSessionStart, broadcastChatSessionTurn, } from '../server/websocket/fleet-bridge.js';
35
+ import { resolveGoalJudgeClientFailOpen } from '../goals/goal-judge-client.js';
35
36
  import { judgeGoal } from '../goals/goal-judge.js';
36
37
  import { resolveGoalsConfig } from '../goals/goal-manager.js';
37
- import { applyJudgeOutcome, createGoalState, formatGoalStatusLine, normalizeGoalState, renderSubgoalsBlock, } from '../goals/goal-state.js';
38
+ import { applyJudgeOutcome, createGoalState, formatGoalStatusLine, getGoalJudgeCriteria, normalizeGoalState, renderSubgoalsBlock, } from '../goals/goal-state.js';
38
39
  import { logger } from '../utils/logger.js';
39
40
  import { getPeerSessionStore, } from './peer-session-store.js';
40
41
  import { DEFAULT_DISPATCH_POLICY_PREVIEW_TOOLS, FLEET_DISPATCH_PROFILES, buildDispatchSystemPrompt, buildHermesToolsetDescriptor, getDispatchToolPolicy, isFleetDispatchProfile, mergeDispatchSystemPrompt, normalizeDispatchProfile, } from './dispatch-profile.js';
@@ -107,6 +108,14 @@ function newSessionId() {
107
108
  const rand = Math.floor(Math.random() * 1e9).toString(36);
108
109
  return `sess_${ts}_${rand}`;
109
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
+ }
110
119
  /**
111
120
  * Opportunistic GC — drop sessions whose `lastUsedAt` is older than
112
121
  * the configured idle window. Called at the top of each `start` and
@@ -162,16 +171,38 @@ async function evaluateSessionGoalAfterTurn(session, assistantText) {
162
171
  const goal = session.goal;
163
172
  if (!goal || goal.status !== 'active')
164
173
  return null;
165
- if (!assistantText.trim())
166
- return null; // empty turn: don't burn budget
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
+ }
167
194
  let report;
168
195
  try {
169
196
  const config = resolveGoalsConfig();
170
- const outcome = await judgeGoal(cachedGetter?.() ?? null, {
197
+ const baseClient = cachedGetter?.() ?? null;
198
+ const judgeClient = await resolveGoalJudgeClientFailOpen(baseClient, config.judgeModel);
199
+ const criteria = getGoalJudgeCriteria(goal);
200
+ const outcome = await judgeGoal(judgeClient, {
171
201
  goal: goal.goal,
172
202
  lastResponse: assistantText,
173
- ...(goal.subgoals.length ? { subgoals: goal.subgoals } : {}),
203
+ ...(criteria.length ? { subgoals: criteria } : {}),
174
204
  ...(config.judgeModel ? { model: config.judgeModel } : {}),
205
+ maxTokens: config.judgeMaxTokens,
175
206
  timeoutMs: config.judgeTimeoutMs,
176
207
  });
177
208
  const decision = applyJudgeOutcome(goal, outcome);
@@ -602,6 +633,7 @@ export async function wirePeerSessionBridge(getClient) {
602
633
  turnsUsed: session.goal.turnsUsed,
603
634
  maxTurns: session.goal.maxTurns,
604
635
  subgoals: [...session.goal.subgoals],
636
+ ...(session.goal.goalPlan ? { goalPlan: session.goal.goalPlan } : {}),
605
637
  ...(session.goal.lastVerdict ? { lastVerdict: session.goal.lastVerdict } : {}),
606
638
  ...(session.goal.lastReason ? { lastReason: session.goal.lastReason } : {}),
607
639
  ...(session.goal.pausedReason ? { pausedReason: session.goal.pausedReason } : {}),
@@ -618,9 +650,7 @@ export async function wirePeerSessionBridge(getClient) {
618
650
  throw new Error('GOAL_ACTIVE: a goal is already active on this session. ' +
619
651
  'Use action "status"/"pause"/"clear" mid-run; clear it before setting a new goal.');
620
652
  }
621
- const maxTurns = typeof params.maxTurns === 'number' && params.maxTurns > 0
622
- ? Math.trunc(params.maxTurns)
623
- : resolveGoalsConfig().maxTurns;
653
+ const maxTurns = resolvePeerGoalMaxTurns(params.maxTurns);
624
654
  session.goal = createGoalState(text, maxTurns);
625
655
  await persist();
626
656
  emitGoal('active');
@@ -629,8 +659,9 @@ export async function wirePeerSessionBridge(getClient) {
629
659
  case 'status':
630
660
  return { ...goalView(), traceId: ctx.traceId };
631
661
  case 'pause': {
632
- if (!session.goal)
662
+ if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
633
663
  return { ...goalView(), traceId: ctx.traceId };
664
+ }
634
665
  session.goal.status = 'paused';
635
666
  session.goal.pausedReason = 'user-paused';
636
667
  await persist();
@@ -638,10 +669,14 @@ export async function wirePeerSessionBridge(getClient) {
638
669
  return { ...goalView(), traceId: ctx.traceId };
639
670
  }
640
671
  case 'resume': {
641
- if (!session.goal)
672
+ if (!session.goal || session.goal.status !== 'paused') {
642
673
  return { ...goalView(), traceId: ctx.traceId };
674
+ }
643
675
  session.goal.status = 'active';
644
676
  delete session.goal.pausedReason;
677
+ delete session.goal.lastVerdict;
678
+ delete session.goal.lastReason;
679
+ session.goal.consecutiveParseFailures = 0;
645
680
  session.goal.turnsUsed = 0; // Hermes resume semantics: budget reset
646
681
  await persist();
647
682
  emitGoal('active');
@@ -674,11 +709,14 @@ export async function wirePeerSessionBridge(getClient) {
674
709
  traceId: ctx.traceId,
675
710
  };
676
711
  case 'subgoal-remove': {
677
- const index = typeof params.index === 'number' ? Math.trunc(params.index) : NaN;
712
+ const index = typeof params.index === 'number' ? params.index : NaN;
678
713
  if (!session.goal || !['active', 'paused'].includes(session.goal.status)) {
679
714
  throw new Error('NO_ACTIVE_GOAL: set a goal before removing subgoals');
680
715
  }
681
- if (!Number.isInteger(index) || index < 1 || index > session.goal.subgoals.length) {
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) {
682
720
  throw new Error(`peer.chat-session.goal: subgoal index out of range (1..${session.goal.subgoals.length})`);
683
721
  }
684
722
  const [removed] = session.goal.subgoals.splice(index - 1, 1);
@@ -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',
@@ -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;