@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
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Camoufox Runner — Playwright-server protocol (NOT Chrome CDP)
3
+ *
4
+ * Camoufox is an anti-detect **Firefox**. It does NOT speak the Chrome
5
+ * DevTools Protocol and exposes no `/json/version` endpoint. The upstream
6
+ * supported automation surface is a **Playwright server**: the Python
7
+ * `camoufox` package launches Playwright's `firefox` `launchServer`, which
8
+ * prints a Playwright-server WebSocket endpoint (`ws://<host>:<port>/<guid>`).
9
+ * The caller connects to it with `playwright.firefox.connect(wsEndpoint)` —
10
+ * NOT `chromium.connectOverCDP()` (that is Chromium-only and invalid here).
11
+ *
12
+ * This runner spawns the server, parses the printed wsEndpoint, and returns
13
+ * it. The caller owns the `firefox.connect()` step (see
14
+ * `src/agent/hermes-browser-backends.ts → runCamofoxSmoke`).
15
+ *
16
+ * ── Live-validated on this host (camoufox 0.4.11, 2026-06-14) ──────────────
17
+ * The repo's Node Playwright 1.58.2 `firefox.connect()` SUCCESSFULLY connects
18
+ * to a server launched by the venv's Playwright 1.58.0 driver — same-minor
19
+ * patch skew passes Playwright's version guard, so a real end-to-end page
20
+ * load (title + heading round-trip) works. If a future ecosystem bump widens
21
+ * the gap to a different minor (e.g. server 1.49 vs client 1.58), `connect`
22
+ * throws a version-guard error; the caller surfaces that honestly rather than
23
+ * faking success.
24
+ *
25
+ * ── 0.4.11 CLI bug worked around here ──────────────────────────────────────
26
+ * `python -m camoufox server` (and `camoufox.server.launch_server()` with no
27
+ * args) is BROKEN in 0.4.11: `launch_options()` emits `proxy: null`, which the
28
+ * bundled `launchServer.js` rejects with
29
+ * `proxy: expected object, got null → Failed to launch browser`. We drive the
30
+ * same public `launch_server` plumbing from a tiny inline Python launcher that
31
+ * strips null-valued options before piping them to `launchServer.js`, which is
32
+ * the supported path minus the upstream null bug.
33
+ *
34
+ * Falls back gracefully when Camoufox is not installed — every public function
35
+ * returns a typed result rather than throwing.
36
+ */
37
+ export interface CamofoxRunnerOptions {
38
+ /** Run without a visible window (default: true). */
39
+ headless?: boolean;
40
+ /**
41
+ * Milliseconds to wait for the server to print its wsEndpoint
42
+ * (default: 30 000 — the first launch downloads/initialises the profile).
43
+ */
44
+ timeout?: number;
45
+ /**
46
+ * Path to a Python interpreter that has the `camoufox` package installed.
47
+ * Defaults to `$CODEBUDDY_CAMOFOX_PYTHON`, then `python3`.
48
+ * This is the supported entry point — Camoufox's server is Python-driven.
49
+ */
50
+ pythonPath?: string;
51
+ /**
52
+ * Optional explicit path to the Camoufox **browser binary** to hand to the
53
+ * server (`executable_path`). Defaults to `$CODEBUDDY_CAMOFOX_BINARY`, else
54
+ * the package's own auto-resolved install. Rarely needed.
55
+ */
56
+ binaryPath?: string;
57
+ }
58
+ export interface CamofoxRunnerResult {
59
+ ok: boolean;
60
+ /**
61
+ * Playwright-server WebSocket endpoint, e.g.
62
+ * `ws://localhost:44477/51422436867b21da7464b5b253fd2fdd`.
63
+ * Connect with `playwright.firefox.connect(wsEndpoint)` — NOT connectOverCDP.
64
+ */
65
+ wsEndpoint?: string;
66
+ error?: string;
67
+ /**
68
+ * PID of the launched Python process group leader. Pass to `closeCamofox`
69
+ * to reap the whole python → node → firefox tree.
70
+ */
71
+ pid?: number;
72
+ }
73
+ /**
74
+ * Extract the Playwright-server WS endpoint from accumulated server stdout.
75
+ * Upstream prints: `Websocket endpoint:\x1b[93m ws://host:port/guid \x1b[0m`.
76
+ *
77
+ * Only returns an endpoint once the line is **complete** — i.e. the buffer
78
+ * contains a delimiter *after* the `ws://…` token (the trailing ANSI reset is
79
+ * stripped, so we look for whitespace / newline / end-of-input following it).
80
+ * This avoids resolving a truncated URL when the pipe splits mid-line.
81
+ */
82
+ export declare function parseWsEndpoint(text: string): string | null;
83
+ /**
84
+ * Launch a Camoufox Playwright server and return its wsEndpoint.
85
+ *
86
+ * ```ts
87
+ * const result = await launchCamofox({ headless: true });
88
+ * if (result.ok) {
89
+ * const playwright = await import('playwright');
90
+ * // Firefox-based — use firefox.connect(), NOT chromium.connectOverCDP().
91
+ * const browser = await playwright.firefox.connect(result.wsEndpoint!);
92
+ * // ... drive the browser ...
93
+ * await browser.close();
94
+ * await closeCamofox(result.pid!);
95
+ * }
96
+ * ```
97
+ */
98
+ export declare function launchCamofox(options?: CamofoxRunnerOptions): Promise<CamofoxRunnerResult>;
99
+ /**
100
+ * Gracefully terminate a previously launched Camoufox server tree.
101
+ *
102
+ * The launcher is spawned `detached`, so its PID leads a process group that
103
+ * contains the node Playwright server and every Firefox process. We signal the
104
+ * whole group (`-pid`) — signalling only the python leader would orphan the
105
+ * node server and the browser.
106
+ */
107
+ export declare function closeCamofox(pid: number): Promise<void>;
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Camoufox Runner — Playwright-server protocol (NOT Chrome CDP)
3
+ *
4
+ * Camoufox is an anti-detect **Firefox**. It does NOT speak the Chrome
5
+ * DevTools Protocol and exposes no `/json/version` endpoint. The upstream
6
+ * supported automation surface is a **Playwright server**: the Python
7
+ * `camoufox` package launches Playwright's `firefox` `launchServer`, which
8
+ * prints a Playwright-server WebSocket endpoint (`ws://<host>:<port>/<guid>`).
9
+ * The caller connects to it with `playwright.firefox.connect(wsEndpoint)` —
10
+ * NOT `chromium.connectOverCDP()` (that is Chromium-only and invalid here).
11
+ *
12
+ * This runner spawns the server, parses the printed wsEndpoint, and returns
13
+ * it. The caller owns the `firefox.connect()` step (see
14
+ * `src/agent/hermes-browser-backends.ts → runCamofoxSmoke`).
15
+ *
16
+ * ── Live-validated on this host (camoufox 0.4.11, 2026-06-14) ──────────────
17
+ * The repo's Node Playwright 1.58.2 `firefox.connect()` SUCCESSFULLY connects
18
+ * to a server launched by the venv's Playwright 1.58.0 driver — same-minor
19
+ * patch skew passes Playwright's version guard, so a real end-to-end page
20
+ * load (title + heading round-trip) works. If a future ecosystem bump widens
21
+ * the gap to a different minor (e.g. server 1.49 vs client 1.58), `connect`
22
+ * throws a version-guard error; the caller surfaces that honestly rather than
23
+ * faking success.
24
+ *
25
+ * ── 0.4.11 CLI bug worked around here ──────────────────────────────────────
26
+ * `python -m camoufox server` (and `camoufox.server.launch_server()` with no
27
+ * args) is BROKEN in 0.4.11: `launch_options()` emits `proxy: null`, which the
28
+ * bundled `launchServer.js` rejects with
29
+ * `proxy: expected object, got null → Failed to launch browser`. We drive the
30
+ * same public `launch_server` plumbing from a tiny inline Python launcher that
31
+ * strips null-valued options before piping them to `launchServer.js`, which is
32
+ * the supported path minus the upstream null bug.
33
+ *
34
+ * Falls back gracefully when Camoufox is not installed — every public function
35
+ * returns a typed result rather than throwing.
36
+ */
37
+ import { spawn } from 'child_process';
38
+ import { logger } from '../utils/logger.js';
39
+ // ---------------------------------------------------------------------------
40
+ // Internals
41
+ // ---------------------------------------------------------------------------
42
+ const DEFAULT_TIMEOUT_MS = 30_000;
43
+ /**
44
+ * Inline Python launcher. Reproduces `camoufox.server.launch_server()` but
45
+ * strips null-valued options (the 0.4.11 `proxy: null` CLI bug) before piping
46
+ * the config to the bundled `launchServer.js`, which prints the wsEndpoint.
47
+ *
48
+ * `headless` is templated in as a literal `True`/`False`.
49
+ * `executable_path` is passed only when a binary override is supplied.
50
+ */
51
+ function buildLauncherScript(headless, binaryPath) {
52
+ const launchKwargs = binaryPath
53
+ ? `headless=${headless ? 'True' : 'False'}, executable_path=${JSON.stringify(binaryPath)}`
54
+ : `headless=${headless ? 'True' : 'False'}`;
55
+ return [
56
+ 'import base64, subprocess, sys',
57
+ 'try:',
58
+ ' import orjson',
59
+ ' def _dumps(o): return orjson.dumps(o)',
60
+ 'except Exception:',
61
+ ' import json',
62
+ ' def _dumps(o): return json.dumps(o).encode()',
63
+ 'from pathlib import Path',
64
+ 'from camoufox.utils import launch_options',
65
+ 'from camoufox.server import LAUNCH_SCRIPT, get_nodejs, to_camel_case_dict',
66
+ `config = launch_options(${launchKwargs})`,
67
+ '# Work around camoufox 0.4.11: launch_options emits proxy=None, which',
68
+ "# launchServer.js rejects with 'proxy: expected object, got null'.",
69
+ 'config = {k: v for k, v in config.items() if v is not None}',
70
+ 'nodejs = get_nodejs()',
71
+ 'data = _dumps(to_camel_case_dict(config))',
72
+ 'proc = subprocess.Popen([nodejs, str(LAUNCH_SCRIPT)],',
73
+ ' cwd=Path(nodejs).parent / "package",',
74
+ ' stdin=subprocess.PIPE, text=True)',
75
+ 'proc.stdin.write(base64.b64encode(data).decode())',
76
+ 'proc.stdin.close()',
77
+ 'proc.wait()',
78
+ ].join('\n');
79
+ }
80
+ // Strip ANSI escape sequences (the endpoint is printed wrapped in colour codes).
81
+ // eslint-disable-next-line no-control-regex
82
+ const ANSI_PATTERN = /\[[0-9;]*m/g;
83
+ /**
84
+ * Extract the Playwright-server WS endpoint from accumulated server stdout.
85
+ * Upstream prints: `Websocket endpoint:\x1b[93m ws://host:port/guid \x1b[0m`.
86
+ *
87
+ * Only returns an endpoint once the line is **complete** — i.e. the buffer
88
+ * contains a delimiter *after* the `ws://…` token (the trailing ANSI reset is
89
+ * stripped, so we look for whitespace / newline / end-of-input following it).
90
+ * This avoids resolving a truncated URL when the pipe splits mid-line.
91
+ */
92
+ export function parseWsEndpoint(text) {
93
+ const cleaned = text.replace(ANSI_PATTERN, '');
94
+ // Require the ws token to be followed by whitespace or end-of-string, which
95
+ // only happens once the full line (incl. trailing reset) has arrived.
96
+ const match = cleaned.match(/(ws:\/\/\S+?)(?:\s|$)/i);
97
+ const candidate = match?.[1];
98
+ if (!candidate)
99
+ return null;
100
+ // Guard against an end-of-buffer partial: only accept when something
101
+ // (whitespace/newline) actually terminates the token, not bare EOF on the
102
+ // very last char with no trailing delimiter seen yet.
103
+ const terminated = new RegExp(`${escapeRegExp(candidate)}\\s`).test(cleaned);
104
+ return terminated ? candidate : null;
105
+ }
106
+ function escapeRegExp(value) {
107
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Public API
111
+ // ---------------------------------------------------------------------------
112
+ /**
113
+ * Launch a Camoufox Playwright server and return its wsEndpoint.
114
+ *
115
+ * ```ts
116
+ * const result = await launchCamofox({ headless: true });
117
+ * if (result.ok) {
118
+ * const playwright = await import('playwright');
119
+ * // Firefox-based — use firefox.connect(), NOT chromium.connectOverCDP().
120
+ * const browser = await playwright.firefox.connect(result.wsEndpoint!);
121
+ * // ... drive the browser ...
122
+ * await browser.close();
123
+ * await closeCamofox(result.pid!);
124
+ * }
125
+ * ```
126
+ */
127
+ export async function launchCamofox(options = {}) {
128
+ const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
129
+ const headless = options.headless ?? true;
130
+ const python = options.pythonPath ?? (process.env.CODEBUDDY_CAMOFOX_PYTHON?.trim() || 'python3');
131
+ const binaryPath = options.binaryPath ?? (process.env.CODEBUDDY_CAMOFOX_BINARY?.trim() || undefined);
132
+ const script = buildLauncherScript(headless, binaryPath);
133
+ // Spawn the Python launcher detached so it leads its own process group; the
134
+ // server tree is python → node(launchServer.js) → firefox. Killing only the
135
+ // python PID would orphan the node server + Firefox, so we reap the whole
136
+ // group in closeCamofox via `process.kill(-pid)`.
137
+ let proc;
138
+ try {
139
+ proc = spawn(python, ['-c', script], {
140
+ stdio: ['ignore', 'pipe', 'pipe'],
141
+ windowsHide: true,
142
+ detached: true,
143
+ });
144
+ }
145
+ catch (err) {
146
+ const message = err instanceof Error ? err.message : String(err);
147
+ logger.warn(`[camofox-runner] Failed to spawn ${python}: ${message}`);
148
+ return { ok: false, error: `Failed to spawn ${python}: ${message}` };
149
+ }
150
+ // Don't let the launcher keep our event loop alive once we've handed back.
151
+ proc.unref();
152
+ return await new Promise((resolve) => {
153
+ let settled = false;
154
+ let stdoutBuf = '';
155
+ let stderrBuf = '';
156
+ const finish = (result) => {
157
+ if (settled)
158
+ return;
159
+ settled = true;
160
+ clearTimeout(timer);
161
+ resolve(result);
162
+ };
163
+ const timer = setTimeout(() => {
164
+ // No endpoint in time — kill the (partial) tree and report.
165
+ const pid = proc.pid;
166
+ if (pid !== undefined) {
167
+ try {
168
+ process.kill(-pid, 'SIGKILL');
169
+ }
170
+ catch { /* best-effort */ }
171
+ }
172
+ const detail = classifyServerError(stderrBuf || stdoutBuf);
173
+ logger.warn(`[camofox-runner] No wsEndpoint within ${timeoutMs}ms. ${detail}`);
174
+ finish({
175
+ ok: false,
176
+ error: `Camoufox server did not print a wsEndpoint within ${timeoutMs}ms. ${detail}`.trim(),
177
+ pid,
178
+ });
179
+ }, timeoutMs);
180
+ proc.stdout?.on('data', (chunk) => {
181
+ stdoutBuf += chunk.toString();
182
+ const endpoint = parseWsEndpoint(stdoutBuf);
183
+ if (endpoint) {
184
+ logger.debug(`[camofox-runner] Playwright server ready at ${endpoint} (pid ${proc.pid})`);
185
+ finish({ ok: true, wsEndpoint: endpoint, pid: proc.pid });
186
+ }
187
+ });
188
+ proc.stderr?.on('data', (chunk) => {
189
+ stderrBuf += chunk.toString();
190
+ });
191
+ proc.on('error', (err) => {
192
+ const message = err.message;
193
+ const hint = /ENOENT/.test(message)
194
+ ? ` Python interpreter '${python}' not found, or the camoufox package is not installed. ` +
195
+ 'Install with: python3 -m venv <venv> && <venv>/bin/pip install "camoufox[geoip]" && set options.pythonPath.'
196
+ : '';
197
+ logger.warn(`[camofox-runner] Spawn error: ${message}${hint}`);
198
+ finish({ ok: false, error: `Spawn error: ${message}${hint}`, pid: proc.pid });
199
+ });
200
+ proc.on('close', (code) => {
201
+ if (settled)
202
+ return;
203
+ const detail = classifyServerError(stderrBuf || stdoutBuf);
204
+ finish({
205
+ ok: false,
206
+ error: `Camoufox server exited (code ${code ?? 'null'}) before printing a wsEndpoint. ${detail}`.trim(),
207
+ pid: proc.pid,
208
+ });
209
+ });
210
+ });
211
+ }
212
+ /**
213
+ * Map common server failures to an actionable message. Surfaces the
214
+ * Playwright version-guard case explicitly (HONEST handling of the
215
+ * version-lock the task calls out) rather than masking it.
216
+ */
217
+ function classifyServerError(output) {
218
+ const text = output.trim();
219
+ if (!text)
220
+ return '';
221
+ // Playwright server/client version guard. The server is launched by the
222
+ // Python camoufox package's Playwright driver; the caller connects with the
223
+ // repo's Node Playwright. A minor-version mismatch trips this guard.
224
+ if (/version|incompatible|handshake|\b428\b/i.test(text) && /playwright/i.test(text)) {
225
+ return ('Camoufox server requires a Playwright version compatible with the ' +
226
+ "installed camoufox package's driver; the repo is pinned to Playwright " +
227
+ '1.58.x. If connect() later fails with a version-guard error, align the ' +
228
+ 'venv camoufox Playwright minor with the repo pin (same minor; patch ' +
229
+ `skew is tolerated). Server output: ${firstNonEmptyLine(text)}`);
230
+ }
231
+ if (/No module named ['"]?camoufox|ModuleNotFoundError/i.test(text)) {
232
+ return ('The camoufox Python package is not importable by the chosen interpreter. ' +
233
+ 'Install with: python3 -m venv <venv> && <venv>/bin/pip install ' +
234
+ '"camoufox[geoip]" and pass options.pythonPath=<venv>/bin/python.');
235
+ }
236
+ if (/proxy: expected object, got null/i.test(text)) {
237
+ // Should not happen — we strip nulls — but report clearly if upstream changes.
238
+ return 'Camoufox launch options were rejected (proxy: null). The installed camoufox version may differ from the one this runner targets.';
239
+ }
240
+ return `Server output: ${firstNonEmptyLine(text)}`;
241
+ }
242
+ function firstNonEmptyLine(text) {
243
+ return text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? text.slice(0, 200);
244
+ }
245
+ /**
246
+ * Gracefully terminate a previously launched Camoufox server tree.
247
+ *
248
+ * The launcher is spawned `detached`, so its PID leads a process group that
249
+ * contains the node Playwright server and every Firefox process. We signal the
250
+ * whole group (`-pid`) — signalling only the python leader would orphan the
251
+ * node server and the browser.
252
+ */
253
+ export async function closeCamofox(pid) {
254
+ const killGroup = (signal) => {
255
+ try {
256
+ process.kill(-pid, signal);
257
+ return true;
258
+ }
259
+ catch {
260
+ // Group already gone, or never became a group leader; fall back to the
261
+ // single PID so we still make a best-effort kill.
262
+ try {
263
+ process.kill(pid, signal);
264
+ return true;
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ }
270
+ };
271
+ if (!killGroup('SIGTERM'))
272
+ return; // already gone
273
+ // Give the tree a moment to exit before escalating.
274
+ await new Promise((resolve) => setTimeout(resolve, 500));
275
+ // Is the leader still alive? (signal 0 = existence probe)
276
+ let alive = true;
277
+ try {
278
+ process.kill(pid, 0);
279
+ }
280
+ catch {
281
+ alive = false;
282
+ }
283
+ if (alive) {
284
+ killGroup('SIGKILL');
285
+ }
286
+ }
287
+ //# sourceMappingURL=camofox-runner.js.map
@@ -108,6 +108,9 @@ export class DingTalkChannel extends BaseChannel {
108
108
  isAtAll: config.isAtAll,
109
109
  }, options);
110
110
  }
111
+ // REST/webhook adapter — outbound messages are one-shot HTTPS POSTs to the
112
+ // DingTalk robot webhook; there is no persistent connection, so reconnection
113
+ // (ReconnectionManager) is N/A.
111
114
  async connect() {
112
115
  await this.adapter.start();
113
116
  this.status.connected = true;
@@ -143,6 +143,15 @@ export declare class DiscordChannel extends BaseChannel {
143
143
  * Build action row from buttons
144
144
  */
145
145
  private buildActionRow;
146
+ /**
147
+ * Return the slash command names registered with this adapter.
148
+ *
149
+ * Implements {@link SlashCommandProvider} so the slash-parity checker can
150
+ * report real parity instead of falling back to an empty list. Names come
151
+ * from the configured `commands` (the same set registered with Discord via
152
+ * {@link registerCommands}), normalized without a leading slash.
153
+ */
154
+ getRegisteredCommands(): string[];
146
155
  /**
147
156
  * Send typing indicator
148
157
  */
@@ -712,6 +712,18 @@ export class DiscordChannel extends BaseChannel {
712
712
  components,
713
713
  };
714
714
  }
715
+ /**
716
+ * Return the slash command names registered with this adapter.
717
+ *
718
+ * Implements {@link SlashCommandProvider} so the slash-parity checker can
719
+ * report real parity instead of falling back to an empty list. Names come
720
+ * from the configured `commands` (the same set registered with Discord via
721
+ * {@link registerCommands}), normalized without a leading slash.
722
+ */
723
+ getRegisteredCommands() {
724
+ const commands = this.discordConfig.commands ?? [];
725
+ return commands.map((cmd) => cmd.name.replace(/^\//, ''));
726
+ }
715
727
  /**
716
728
  * Send typing indicator
717
729
  */
@@ -8,7 +8,7 @@
8
8
  * Native Engine v2026.3.11 alignment: approval cards, reasoning streams,
9
9
  * identity-aware headers, full thread context.
10
10
  */
11
- import { BaseChannel, ChannelConfig, DeliveryResult, OutboundMessage } from '../core.js';
11
+ import { BaseChannel, ChannelConfig, DeliveryResult, InboundMessage, OutboundMessage } from '../core.js';
12
12
  export interface FeishuConfig {
13
13
  appId: string;
14
14
  appSecret: string;
@@ -54,6 +54,67 @@ export interface FeishuMessage {
54
54
  messageType: 'text' | 'post' | 'image' | 'interactive' | 'file';
55
55
  createTime: string;
56
56
  }
57
+ /**
58
+ * Shape of the `im.message.receive_v1` event body. Mirrors the Lark Open
59
+ * Platform schema. The official `EventDispatcher` hands the handler the
60
+ * UNWRAPPED `event` body (`{ sender, message }`); the full
61
+ * `{ schema, header, event }` envelope only appears on the raw webhook. We
62
+ * accept both (see {@link parseFeishuMessageEvent}).
63
+ */
64
+ export interface FeishuReceiveEventBody {
65
+ sender?: {
66
+ sender_id?: {
67
+ open_id?: string;
68
+ union_id?: string;
69
+ user_id?: string;
70
+ };
71
+ sender_type?: string;
72
+ tenant_key?: string;
73
+ };
74
+ message?: {
75
+ message_id?: string;
76
+ root_id?: string;
77
+ parent_id?: string;
78
+ create_time?: string;
79
+ chat_id?: string;
80
+ thread_id?: string;
81
+ chat_type?: 'p2p' | 'group' | string;
82
+ message_type?: string;
83
+ /** JSON-encoded string; structure varies by message_type. */
84
+ content?: string;
85
+ mentions?: Array<Record<string, unknown>>;
86
+ };
87
+ }
88
+ /** The raw event as it may arrive: either the body, or the full envelope. */
89
+ export interface FeishuReceiveEventEnvelope {
90
+ schema?: string;
91
+ header?: Record<string, unknown>;
92
+ event?: FeishuReceiveEventBody;
93
+ }
94
+ /**
95
+ * Parse a Lark `im.message.receive_v1` event into a Code Buddy
96
+ * {@link InboundMessage}.
97
+ *
98
+ * Envelope-tolerant: accepts either the unwrapped event body (what the SDK's
99
+ * `EventDispatcher` hands the handler) or the full `{ schema, header, event }`
100
+ * webhook envelope.
101
+ *
102
+ * Returns `null` when the event carries no usable message (e.g. a non-message
103
+ * event slipped through, or no chat/text could be resolved).
104
+ */
105
+ export declare function parseFeishuMessageEvent(raw: FeishuReceiveEventBody | FeishuReceiveEventEnvelope | undefined | null): InboundMessage | null;
106
+ /**
107
+ * Legacy in-process adapter.
108
+ *
109
+ * The real outbound transport now lives in {@link FeishuChannel}, which mints a
110
+ * genuine `tenant_access_token` and POSTs through the REST `im/v1/messages`
111
+ * API. This class is retained for its still-useful, network-free helpers — the
112
+ * interactive-card builders, reasoning-stream hooks, and `getThreadMessages`
113
+ * (exercised by `tests/channels/feishu-cards.test.ts`). Its `sendText` /
114
+ * `sendCard` / `sendImage` / `replyMessage` methods, however, perform NO
115
+ * network I/O and return synthetic ids; they are kept only for backward-compat
116
+ * and are not on the real send path. Prefer {@link FeishuChannel.send}.
117
+ */
57
118
  export declare class FeishuAdapter {
58
119
  private config;
59
120
  private running;
@@ -62,18 +123,22 @@ export declare class FeishuAdapter {
62
123
  start(): Promise<void>;
63
124
  stop(): Promise<void>;
64
125
  isRunning(): boolean;
126
+ /** @deprecated No network I/O — returns a synthetic id. Use {@link FeishuChannel.send}. */
65
127
  sendText(chatId: string, text: string): Promise<{
66
128
  success: boolean;
67
129
  messageId: string;
68
130
  }>;
131
+ /** @deprecated No network I/O — returns a synthetic id. Use {@link FeishuChannel.send}. */
69
132
  sendCard(chatId: string, card: Record<string, unknown>): Promise<{
70
133
  success: boolean;
71
134
  messageId: string;
72
135
  }>;
136
+ /** @deprecated No network I/O — returns a synthetic id. Use {@link FeishuChannel.send}. */
73
137
  sendImage(chatId: string, imageKey: string): Promise<{
74
138
  success: boolean;
75
139
  messageId: string;
76
140
  }>;
141
+ /** @deprecated No network I/O. Use {@link FeishuChannel.send} with `replyTo` instead. */
77
142
  replyMessage(messageId: string, text: string): Promise<{
78
143
  success: boolean;
79
144
  }>;
@@ -113,14 +178,115 @@ export declare class FeishuAdapter {
113
178
  */
114
179
  getThreadMessages(chatId: string): Promise<FeishuMessage[]>;
115
180
  }
181
+ /**
182
+ * Structured result of attempting to bring up the inbound receive channel.
183
+ *
184
+ * Feishu/Lark's real-time push is delivered over a proprietary persistent
185
+ * "long-connection" (WebSocket) whose wire framing is NOT publicly specified:
186
+ * frames are Protobuf (`pbbp2` Frame messages with a CONTROL/DATA `method`
187
+ * discriminator, internal header keys, and gzip-compressed, chunk-reassembled
188
+ * JSON payloads that each require a server ACK frame). That schema, the control
189
+ * opcodes, and the ACK handshake ship only inside the official Lark SDK
190
+ * (`@larksuiteoapi/node-sdk`). We deliberately do NOT reimplement it from a
191
+ * guess: a hand-rolled framing would not be the real protocol, and any "mock"
192
+ * exercising it would only validate our own invention rather than the wire
193
+ * contract. Instead, when the official SDK IS installed we drive its
194
+ * {@link https://github.com/larksuite/node-sdk WSClient} (see
195
+ * {@link FeishuChannel.connect}); when it is NOT installed we keep the honest
196
+ * send-only state below. The outbound REST path is always fully implemented.
197
+ */
198
+ export interface FeishuReceiveStatus {
199
+ /**
200
+ * `true` only when the official Lark `WSClient` long-connection is live;
201
+ * `false` when the SDK is absent (or no app credentials are configured).
202
+ */
203
+ connected: boolean;
204
+ /**
205
+ * Machine-readable state code:
206
+ * - `'lark-sdk-required'` — the optional `@larksuiteoapi/node-sdk` is not
207
+ * installed, so no inbound socket exists (outbound REST still works).
208
+ * - `'lark-ws'` — the official SDK's long-connection is live.
209
+ */
210
+ reason: 'lark-sdk-required' | 'lark-ws';
211
+ /** Human-readable explanation. */
212
+ detail: string;
213
+ }
116
214
  export declare class FeishuChannel extends BaseChannel {
117
215
  private adapter;
216
+ private readonly baseUrl;
217
+ /** Cached tenant_access_token (re-minted on demand if the API rejects it). */
218
+ private tenantToken;
219
+ private receiveStatus;
220
+ /** Live official-SDK long-connection client, when the SDK is installed. */
221
+ private wsClient;
118
222
  constructor(config: FeishuChannelConfig);
223
+ /**
224
+ * Bring the channel up.
225
+ *
226
+ * Outbound (`send()`) is fully functional: it lazily mints a
227
+ * `tenant_access_token` and POSTs to `/open-apis/im/v1/messages`.
228
+ *
229
+ * Inbound (receiving user messages) requires Feishu's proprietary
230
+ * long-connection — see {@link FeishuReceiveStatus}. We do NOT fake it. We
231
+ * attempt to bring up the REAL inbound socket using the official Lark SDK
232
+ * (`@larksuiteoapi/node-sdk`), imported OPTIONALLY at runtime — it is not a
233
+ * declared dependency, so most installs won't have it. When it IS present
234
+ * (and app credentials are configured) we start its `WSClient` and register
235
+ * the `im.message.receive_v1` handler, which parses each message into an
236
+ * {@link InboundMessage} and re-emits it via `this.emit('message' | 'command')`.
237
+ * The SDK owns the long-connection + reconnect internally.
238
+ *
239
+ * When the SDK is absent (the default) we keep the honest "send-only" state:
240
+ * `status.connected = false`, the receive reason surfaced in
241
+ * `status.error` / `status.info`. No throw — outbound still works.
242
+ */
119
243
  connect(): Promise<void>;
244
+ /**
245
+ * Attempt to bring up the REAL inbound long-connection via the optional
246
+ * official Lark SDK. Mutates `this.receiveStatus` / `this.status` to the live
247
+ * `'lark-ws'` state on success; otherwise leaves the honest send-only state
248
+ * untouched. Never throws — a missing SDK or a start() failure degrades
249
+ * gracefully to outbound-only.
250
+ */
251
+ private tryStartInbound;
252
+ /**
253
+ * Parse a raw `im.message.receive_v1` event and re-emit it as a Code Buddy
254
+ * `message` (and `command`) event. This is the seam the live SDK handler
255
+ * delegates to; it is also unit-testable directly without a live tenant.
256
+ *
257
+ * @returns the parsed {@link InboundMessage}, or `null` if the event carried
258
+ * no usable message.
259
+ */
260
+ dispatchInboundEvent(event: FeishuReceiveEventBody | FeishuReceiveEventEnvelope): InboundMessage | null;
120
261
  disconnect(): Promise<void>;
262
+ /**
263
+ * Send an outbound message through the real Feishu REST API.
264
+ *
265
+ * Cards are sent with `msg_type: 'interactive'`; everything else as
266
+ * `msg_type: 'text'`. The chat id (`message.channelId`) becomes the
267
+ * `receive_id` with `receive_id_type=chat_id`.
268
+ */
121
269
  send(message: OutboundMessage): Promise<DeliveryResult>;
270
+ /**
271
+ * Honest, structured account of the inbound receive channel. Returns `null`
272
+ * before `connect()` has run; otherwise a {@link FeishuReceiveStatus}
273
+ * explaining why no live socket exists.
274
+ */
275
+ getReceiveStatus(): FeishuReceiveStatus | null;
122
276
  /**
123
277
  * Get the underlying adapter (for direct card/reasoning API access).
124
278
  */
125
279
  getAdapter(): FeishuAdapter | null;
280
+ /**
281
+ * POST a message to `/open-apis/im/v1/messages`, minting (and caching) a
282
+ * tenant_access_token first. On an auth-class failure the token is dropped
283
+ * and the call retried once with a fresh token.
284
+ */
285
+ private postMessage;
286
+ /**
287
+ * Return a cached tenant_access_token, minting one via
288
+ * `/open-apis/auth/v3/tenant_access_token/internal` if needed. This is the
289
+ * same internal-app credential exchange used by `src/tools/feishu-tool.ts`.
290
+ */
291
+ private ensureTenantToken;
126
292
  }