@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
@@ -140,6 +140,10 @@ export interface JobRun {
140
140
  result?: unknown;
141
141
  error?: string;
142
142
  duration?: number;
143
+ /** Structured output from this job run, passed as inputData to chained jobs. */
144
+ outputData?: string;
145
+ /** Input data received from a parent job in a chain. */
146
+ inputData?: string;
143
147
  }
144
148
  export interface CronSchedulerConfig {
145
149
  /** Jobs persist path */
@@ -174,7 +178,7 @@ export declare class CronScheduler extends EventEmitter {
174
178
  /**
175
179
  * Set the task executor (for CronAgentBridge integration)
176
180
  */
177
- setTaskExecutor(executor: (job: CronJob) => Promise<unknown>): void;
181
+ setTaskExecutor(executor: (job: CronJob, inputData?: string) => Promise<unknown>): void;
178
182
  /**
179
183
  * Load persisted jobs into memory without starting the tick loop. Useful for
180
184
  * one-shot CLI commands (`buddy cron list/add/remove`) that must see the
@@ -182,7 +186,7 @@ export declare class CronScheduler extends EventEmitter {
182
186
  * overwrite the file with only the in-memory jobs.
183
187
  */
184
188
  loadFromDisk(): Promise<void>;
185
- start(taskExecutor?: (job: CronJob) => Promise<unknown>): Promise<void>;
189
+ start(taskExecutor?: (job: CronJob, inputData?: string) => Promise<unknown>): Promise<void>;
186
190
  stop(): Promise<void>;
187
191
  /**
188
192
  * Add a new job
@@ -232,11 +236,19 @@ export declare class CronScheduler extends EventEmitter {
232
236
  /**
233
237
  * Run a job immediately
234
238
  */
235
- runJobNow(jobId: string): Promise<JobRun | null>;
239
+ runJobNow(jobId: string, inputData?: string): Promise<JobRun | null>;
236
240
  private scheduleJob;
237
241
  private cancelJobTimer;
238
242
  private calculateNextRun;
239
243
  private executeJob;
244
+ /** Max bytes for outputData passed between chained jobs. */
245
+ private static readonly MAX_OUTPUT_DATA_BYTES;
246
+ /**
247
+ * Extract a string `outputData` from the executor result. Looks for an
248
+ * explicit `outputData` field first, then falls back to `output`, then
249
+ * stringifies the whole result. Always capped at 64KB.
250
+ */
251
+ static extractOutputData(result: unknown): string | undefined;
240
252
  /**
241
253
  * Resolve and execute a chained (`then`) job after its parent succeeds.
242
254
  *
@@ -304,11 +304,11 @@ export class CronScheduler extends EventEmitter {
304
304
  /**
305
305
  * Run a job immediately
306
306
  */
307
- async runJobNow(jobId) {
307
+ async runJobNow(jobId, inputData) {
308
308
  const job = this.jobs.get(jobId);
309
309
  if (!job)
310
310
  return null;
311
- return await this.executeJob(job);
311
+ return await this.executeJob(job, 0, inputData);
312
312
  }
313
313
  // ==========================================================================
314
314
  // Scheduling
@@ -379,19 +379,20 @@ export class CronScheduler extends EventEmitter {
379
379
  // ==========================================================================
380
380
  // Execution
381
381
  // ==========================================================================
382
- async executeJob(job, chainDepth = 0) {
382
+ async executeJob(job, chainDepth = 0, inputData) {
383
383
  const run = {
384
384
  id: crypto.randomUUID(),
385
385
  jobId: job.id,
386
386
  startedAt: new Date(),
387
387
  status: 'running',
388
+ ...(inputData !== undefined ? { inputData } : {}),
388
389
  };
389
390
  this.emit('job:run:start', run);
390
391
  try {
391
392
  // Execute task
392
393
  let result;
393
394
  if (this.taskExecutor) {
394
- result = await this.taskExecutor(job);
395
+ result = await this.taskExecutor(job, inputData);
395
396
  }
396
397
  else {
397
398
  // Default execution (just log)
@@ -401,6 +402,10 @@ export class CronScheduler extends EventEmitter {
401
402
  run.status = 'success';
402
403
  run.result = result;
403
404
  run.duration = run.completedAt.getTime() - run.startedAt.getTime();
405
+ // Extract outputData from the executor result for cross-job data passing.
406
+ // The executor may return an object with an `output` or `outputData`
407
+ // field; we cap at 64KB to keep run history manageable.
408
+ run.outputData = CronScheduler.extractOutputData(result);
404
409
  job.lastRunAt = run.startedAt;
405
410
  job.runCount++;
406
411
  // Reset backoff on success
@@ -444,10 +449,50 @@ export class CronScheduler extends EventEmitter {
444
449
  // successful run, so the chain runs inside a guarded helper that swallows
445
450
  // (and logs) any error from the chained job.
446
451
  if (run.status === 'success' && job.then) {
447
- await this.runChainedJob(job, chainDepth);
452
+ await this.runChainedJob(job, chainDepth, run.outputData);
448
453
  }
449
454
  return run;
450
455
  }
456
+ /** Max bytes for outputData passed between chained jobs. */
457
+ static MAX_OUTPUT_DATA_BYTES = 65_536; // 64 KiB
458
+ /**
459
+ * Extract a string `outputData` from the executor result. Looks for an
460
+ * explicit `outputData` field first, then falls back to `output`, then
461
+ * stringifies the whole result. Always capped at 64KB.
462
+ */
463
+ static extractOutputData(result) {
464
+ if (result == null)
465
+ return undefined;
466
+ let raw;
467
+ if (typeof result === 'string') {
468
+ raw = result;
469
+ }
470
+ else if (typeof result === 'object') {
471
+ const obj = result;
472
+ if (typeof obj.outputData === 'string') {
473
+ raw = obj.outputData;
474
+ }
475
+ else if (typeof obj.output === 'string') {
476
+ raw = obj.output;
477
+ }
478
+ else {
479
+ try {
480
+ raw = JSON.stringify(result);
481
+ }
482
+ catch {
483
+ return undefined;
484
+ }
485
+ }
486
+ }
487
+ else {
488
+ raw = String(result);
489
+ }
490
+ if (!raw || raw.length === 0)
491
+ return undefined;
492
+ return raw.length > CronScheduler.MAX_OUTPUT_DATA_BYTES
493
+ ? raw.slice(0, CronScheduler.MAX_OUTPUT_DATA_BYTES)
494
+ : raw;
495
+ }
451
496
  /**
452
497
  * Resolve and execute a chained (`then`) job after its parent succeeds.
453
498
  *
@@ -455,7 +500,7 @@ export class CronScheduler extends EventEmitter {
455
500
  * authored out of order. A missing/disabled target, a self-reference, or a
456
501
  * depth-cap breach is logged and stops the chain — it never throws.
457
502
  */
458
- async runChainedJob(parent, chainDepth) {
503
+ async runChainedJob(parent, chainDepth, outputData) {
459
504
  const target = parent.then;
460
505
  if (!target)
461
506
  return;
@@ -493,7 +538,8 @@ export class CronScheduler extends EventEmitter {
493
538
  return;
494
539
  }
495
540
  try {
496
- await this.executeJob(next, chainDepth + 1);
541
+ // Pass the parent job's outputData as the chained job's inputData.
542
+ await this.executeJob(next, chainDepth + 1, outputData);
497
543
  }
498
544
  catch (err) {
499
545
  // A chained failure is captured in the chained job's own run record; it
@@ -26,6 +26,8 @@ export interface CronScriptResult {
26
26
  exitCode: number | null;
27
27
  /** Combined stdout + stderr, capped. */
28
28
  output: string;
29
+ /** Stdout only, capped at 64KB. Used for cross-job data passing. */
30
+ stdout: string;
29
31
  /** True when the timeout fired and the process was killed. */
30
32
  timedOut: boolean;
31
33
  }
@@ -16,6 +16,7 @@ import { spawn } from 'child_process';
16
16
  import path from 'path';
17
17
  const DEFAULT_SCRIPT_TIMEOUT_MS = 600_000;
18
18
  const MAX_OUTPUT_BYTES = 1_048_576; // 1 MiB cap on captured output
19
+ const MAX_STDOUT_BYTES = 65_536; // 64 KiB cap on stdout for data passing
19
20
  const DEFAULT_ALLOWED_EXECUTABLES = new Set([
20
21
  'bash',
21
22
  'cargo',
@@ -68,27 +69,41 @@ function spawnScript(command, timeoutMs) {
68
69
  windowsHide: true,
69
70
  });
70
71
  let output = '';
72
+ let stdoutOnly = '';
71
73
  let timedOut = false;
72
- const append = (chunk) => {
74
+ const appendOutput = (chunk) => {
73
75
  if (output.length < MAX_OUTPUT_BYTES) {
74
76
  output += chunk;
75
77
  }
76
78
  };
79
+ const appendStdout = (chunk) => {
80
+ if (stdoutOnly.length < MAX_STDOUT_BYTES) {
81
+ stdoutOnly += chunk;
82
+ }
83
+ };
77
84
  const timer = setTimeout(() => {
78
85
  timedOut = true;
79
86
  child.kill('SIGTERM');
80
87
  }, timeoutMs);
81
88
  child.stdout.setEncoding('utf8');
82
- child.stdout.on('data', append);
89
+ child.stdout.on('data', (chunk) => {
90
+ appendOutput(chunk);
91
+ appendStdout(chunk);
92
+ });
83
93
  child.stderr.setEncoding('utf8');
84
- child.stderr.on('data', append);
94
+ child.stderr.on('data', appendOutput);
85
95
  child.on('error', (error) => {
86
96
  clearTimeout(timer);
87
97
  reject(error);
88
98
  });
89
99
  child.on('close', (exitCode) => {
90
100
  clearTimeout(timer);
91
- resolve({ exitCode, output: output.trimEnd(), timedOut });
101
+ resolve({
102
+ exitCode,
103
+ output: output.trimEnd(),
104
+ stdout: stdoutOnly.trimEnd(),
105
+ timedOut,
106
+ });
92
107
  });
93
108
  });
94
109
  }
@@ -16,8 +16,8 @@
16
16
  * The aggregate `ok` is true only when there are no alerts and no errors.
17
17
  */
18
18
  import { spawn } from 'child_process';
19
- import fs from 'fs';
20
19
  import path from 'path';
20
+ import { getFreeSpaceInfo } from '../utils/disk-guard.js';
21
21
  const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
22
22
  const DEFAULT_BUILD_ALLOWED_EXECUTABLES = new Set([
23
23
  'bash',
@@ -115,32 +115,19 @@ async function runSingleCheck(check, allowedExecutables) {
115
115
  }
116
116
  function checkDisk(check, name) {
117
117
  const target = check.path || process.cwd();
118
- const statfs = fs.statfsSync;
119
- if (typeof statfs !== 'function') {
118
+ // Single source of truth for free space (uses bavail); null when statfs is
119
+ // unavailable or the path can't be stat'd.
120
+ const info = getFreeSpaceInfo(target);
121
+ if (info === null) {
120
122
  return {
121
123
  type: 'disk',
122
124
  name,
123
125
  status: 'error',
124
- summary: 'fs.statfsSync is not available in this Node runtime',
126
+ summary: `cannot determine free space at ${target} (fs.statfsSync unavailable or path unreadable)`,
125
127
  details: { path: target },
126
128
  };
127
129
  }
128
- let stats;
129
- try {
130
- stats = statfs(target);
131
- }
132
- catch (err) {
133
- return {
134
- type: 'disk',
135
- name,
136
- status: 'error',
137
- summary: `cannot stat filesystem at ${target}: ${errMsg(err)}`,
138
- details: { path: target },
139
- };
140
- }
141
- const totalBytes = stats.blocks * stats.bsize;
142
- const freeBytes = stats.bavail * stats.bsize;
143
- const freePercent = totalBytes > 0 ? (freeBytes / totalBytes) * 100 : 0;
130
+ const { freeBytes, totalBytes, freePercent } = info;
144
131
  const breaches = [];
145
132
  if (typeof check.minFreeBytes === 'number' && freeBytes < check.minFreeBytes) {
146
133
  breaches.push(`free ${formatBytes(freeBytes)} < min ${formatBytes(check.minFreeBytes)}`);
@@ -89,8 +89,13 @@ export class USearchVectorIndex extends EventEmitter {
89
89
  */
90
90
  async loadUSearch() {
91
91
  try {
92
- // Dynamic import for optional dependency
93
- const usearch = (await import('usearch'));
92
+ // Dynamic import for an OPTIONAL native dependency (see optionalDependencies).
93
+ // Use a non-literal specifier so `tsc` doesn't require 'usearch' to be
94
+ // resolvable on every platform — it isn't installed on some CI runners
95
+ // (e.g. windows-latest), which otherwise fails type-check with TS2307.
96
+ // Types come from the local USearchModule interface, not the package.
97
+ const usearchSpecifier = 'usearch';
98
+ const usearch = (await import(usearchSpecifier));
94
99
  return usearch;
95
100
  }
96
101
  catch (_err) {
@@ -42,6 +42,8 @@ export const TOOL_GROUPS = {
42
42
  // Runtime - Process
43
43
  spawn_process: ['group:runtime', 'group:runtime:process'],
44
44
  kill_process: ['group:runtime', 'group:runtime:process', 'group:dangerous'],
45
+ // Runtime - Office macros (arbitrary VBA/PowerShell via COM, Windows only)
46
+ office_macro_execute: ['group:runtime', 'group:runtime:shell', 'group:dangerous'],
45
47
  // Web - Fetch
46
48
  web_fetch: ['group:web', 'group:web:fetch'],
47
49
  http_request: ['group:web', 'group:web:fetch'],
@@ -14,6 +14,8 @@ import { createRequire } from 'module';
14
14
  import express from 'express';
15
15
  import cors from 'cors';
16
16
  import { createServer } from 'http';
17
+ import { createServer as createHttpsServer, Server as HttpsServer } from 'https';
18
+ import { resolveServerTlsOptions } from './tls-config.js';
17
19
  const _require = createRequire(import.meta.url);
18
20
  let SERVER_VERSION = '0.0.0';
19
21
  try {
@@ -77,7 +79,10 @@ export function getServerBaseUrl(server, config) {
77
79
  const port = typeof address === 'object' && address ? address.port : config.port;
78
80
  const publicHost = config.host === '0.0.0.0' || config.host === '::' ? '127.0.0.1' : config.host;
79
81
  const host = publicHost.includes(':') && !publicHost.startsWith('[') ? `[${publicHost}]` : publicHost;
80
- return `http://${host}:${port}`;
82
+ // Report https:// when the server was constructed with TLS so the logged URL
83
+ // isn't a lie. Default (plain http.Server) stays http://.
84
+ const scheme = server instanceof HttpsServer ? 'https' : 'http';
85
+ return `${scheme}://${host}:${port}`;
81
86
  }
82
87
  // Default configuration
83
88
  const DEFAULT_CONFIG = {
@@ -878,7 +883,17 @@ export async function startServer(userConfig = {}) {
878
883
  /* prometheus exporter optional */
879
884
  }
880
885
  const app = createApp(config);
881
- const server = createServer(app);
886
+ // Optional TLS: serve the API (including /api/mobile) over HTTPS when
887
+ // CODEBUDDY_HTTPS / CODEBUDDY_MOBILE_TLS is set so the mobile-supervision
888
+ // endpoint can be exposed off-device securely. Default (no env) is plain HTTP,
889
+ // byte-for-byte unchanged. resolveServerTlsOptions() throws (never silently
890
+ // downgrades) if TLS is requested but cannot be satisfied. The https.Server
891
+ // surface (listen/address/on('upgrade')/close) matches http.Server at runtime,
892
+ // so we keep the `HttpServer` type to avoid widening downstream signatures.
893
+ const tlsOptions = resolveServerTlsOptions();
894
+ const server = tlsOptions
895
+ ? createHttpsServer(tlsOptions, app)
896
+ : createServer(app);
882
897
  const startChannelBridge = async (baseUrl) => {
883
898
  try {
884
899
  const { getChannelManager } = await import('../channels/index.js');
@@ -1,4 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
+ import { EventEmitter } from 'events';
2
3
  export declare const mobileRouter: import("express-serve-static-core").Router;
3
4
  export declare let activePairingCode: string;
4
5
  export declare let activePairingCodeExpiresAt: number;
@@ -10,11 +11,11 @@ export declare const activeTokens: Map<string, {
10
11
  * A follow-up prompt review draft. The mobile device (or a draft-only call) can
11
12
  * *propose* a prompt, but it never executes: it lands here as
12
13
  * `needs_local_operator` and only a local operator (loopback) can `approve` or
13
- * `cancel` it. Approval is a review-gate marker — it records who/when but never
14
- * dispatches work, matching the gateway contract's `autoDispatch:false` /
15
- * `remoteExecutionDisabled:true` invariants.
14
+ * `cancel` it. When `CODEBUDDY_MOBILE_ALLOW_DISPATCH=true`, an approved draft
15
+ * can optionally be dispatched to the agent for execution. When the env var is
16
+ * OFF (default), approval remains a review-gate marker only.
16
17
  */
17
- export type FollowupDraftStatus = 'needs_local_operator' | 'approved' | 'cancelled';
18
+ export type FollowupDraftStatus = 'needs_local_operator' | 'approved' | 'cancelled' | 'dispatched' | 'completed' | 'failed';
18
19
  export interface FollowupDraft {
19
20
  id: string;
20
21
  prompt: string;
@@ -24,9 +25,16 @@ export interface FollowupDraft {
24
25
  approvedBy?: string;
25
26
  approvedAt?: number;
26
27
  cancelledAt?: number;
28
+ dispatchedAt?: number;
29
+ completedAt?: number;
30
+ failedAt?: number;
31
+ failureReason?: string;
27
32
  [key: string]: unknown;
28
33
  }
29
34
  export declare const followupDrafts: FollowupDraft[];
35
+ export declare const mobileEventBus: EventEmitter<[never]>;
36
+ /** Check whether dispatch is enabled via environment variable. */
37
+ export declare function isDispatchEnabled(): boolean;
30
38
  /**
31
39
  * Decide whether a request originates from the loopback interface. We read the
32
40
  * raw socket address (`req.socket.remoteAddress`) on purpose: `req.ip` and the
@@ -1,5 +1,6 @@
1
1
  import { Router } from 'express';
2
2
  import crypto from 'crypto';
3
+ import { EventEmitter } from 'events';
3
4
  import path from 'path';
4
5
  import fs from 'fs';
5
6
  import { logger } from '../../utils/logger.js';
@@ -36,6 +37,11 @@ function rotatePairingCode() {
36
37
  }
37
38
  activePairingCode = nextCode;
38
39
  activePairingCodeExpiresAt = Date.now() + getPairingCodeTtlMs();
40
+ mobileEventBus.emit('event', {
41
+ type: 'pairing_rotated',
42
+ pairingCodeExpiresAt: activePairingCodeExpiresAt,
43
+ timestamp: Date.now(),
44
+ });
39
45
  return activePairingCode;
40
46
  }
41
47
  function ensureActivePairingCodeFresh() {
@@ -44,6 +50,15 @@ function ensureActivePairingCodeFresh() {
44
50
  }
45
51
  }
46
52
  export const followupDrafts = [];
53
+ // ──────────────────────────────────────────────────────────────────────────
54
+ // Mobile event bus — emits SSE-pushed events for mobile clients.
55
+ // ──────────────────────────────────────────────────────────────────────────
56
+ export const mobileEventBus = new EventEmitter();
57
+ mobileEventBus.setMaxListeners(50);
58
+ /** Check whether dispatch is enabled via environment variable. */
59
+ export function isDispatchEnabled() {
60
+ return process.env.CODEBUDDY_MOBILE_ALLOW_DISPATCH === 'true';
61
+ }
47
62
  function pruneExpiredTokens(now = Date.now()) {
48
63
  for (const [token, tokenData] of activeTokens) {
49
64
  if (now > tokenData.expiresAt) {
@@ -158,7 +173,8 @@ mobileRouter.get('/followup-drafts', loopbackOnlyMiddleware, (req, res) => {
158
173
  res.json({ ok: true, drafts: followupDrafts });
159
174
  });
160
175
  // POST /api/mobile/followup-draft/:id/approve: local operator approves a draft.
161
- // Approval is a review-gate marker only — it NEVER dispatches or executes work.
176
+ // When CODEBUDDY_MOBILE_ALLOW_DISPATCH is OFF (default), approval is a
177
+ // review-gate marker only — it never dispatches or executes work.
162
178
  mobileRouter.post('/followup-draft/:id/approve', loopbackOnlyMiddleware, (req, res) => {
163
179
  const draft = followupDrafts.find((d) => d.id === req.params.id);
164
180
  if (!draft) {
@@ -175,6 +191,12 @@ mobileRouter.post('/followup-draft/:id/approve', loopbackOnlyMiddleware, (req, r
175
191
  draft.status = 'approved';
176
192
  draft.approvedBy = reviewer;
177
193
  draft.approvedAt = Date.now();
194
+ mobileEventBus.emit('event', {
195
+ type: 'draft_status_changed',
196
+ draftId: draft.id,
197
+ status: draft.status,
198
+ timestamp: Date.now(),
199
+ });
178
200
  res.json({
179
201
  ok: true,
180
202
  message: 'Draft approved by local operator. No work is dispatched automatically — execution stays local and explicit.',
@@ -194,8 +216,52 @@ mobileRouter.post('/followup-draft/:id/cancel', loopbackOnlyMiddleware, (req, re
194
216
  }
195
217
  draft.status = 'cancelled';
196
218
  draft.cancelledAt = Date.now();
219
+ mobileEventBus.emit('event', {
220
+ type: 'draft_status_changed',
221
+ draftId: draft.id,
222
+ status: draft.status,
223
+ timestamp: Date.now(),
224
+ });
197
225
  res.json({ ok: true, message: 'Draft cancelled by local operator.', draft });
198
226
  });
227
+ // POST /api/mobile/followup-draft/:id/dispatch: local operator dispatches an
228
+ // approved draft to the agent. Gated behind CODEBUDDY_MOBILE_ALLOW_DISPATCH.
229
+ mobileRouter.post('/followup-draft/:id/dispatch', loopbackOnlyMiddleware, (req, res) => {
230
+ if (!isDispatchEnabled()) {
231
+ res.status(403).json({
232
+ ok: false,
233
+ error: 'Dispatch is disabled. Set CODEBUDDY_MOBILE_ALLOW_DISPATCH=true to enable.',
234
+ });
235
+ return;
236
+ }
237
+ const draft = followupDrafts.find((d) => d.id === req.params.id);
238
+ if (!draft) {
239
+ res.status(404).json({ ok: false, error: 'Draft not found' });
240
+ return;
241
+ }
242
+ if (draft.status !== 'approved') {
243
+ res.status(409).json({
244
+ ok: false,
245
+ error: `Draft must be approved before dispatch (status: ${draft.status})`,
246
+ });
247
+ return;
248
+ }
249
+ draft.status = 'dispatched';
250
+ draft.dispatchedAt = Date.now();
251
+ mobileEventBus.emit('event', {
252
+ type: 'draft_status_changed',
253
+ draftId: draft.id,
254
+ status: draft.status,
255
+ timestamp: Date.now(),
256
+ });
257
+ // Emit a dispatch queue entry for the agent to pick up.
258
+ mobileEventBus.emit('dispatch', { draftId: draft.id, prompt: draft.prompt });
259
+ res.json({
260
+ ok: true,
261
+ message: 'Draft dispatched to agent for execution.',
262
+ draft,
263
+ });
264
+ });
199
265
  // 1. POST /api/mobile/pair: Validate pairing request and issue short-lived token
200
266
  mobileRouter.post('/pair', (req, res) => {
201
267
  const code = typeof req.body?.code === 'string' ? req.body.code.trim() : '';
@@ -221,6 +287,49 @@ mobileRouter.post('/pair', (req, res) => {
221
287
  expiresAt,
222
288
  });
223
289
  });
290
+ // ──────────────────────────────────────────────────────────────────────────
291
+ // SSE push endpoint — authenticated via mobile bearer token (query param
292
+ // fallback for EventSource API which doesn't support Authorization header).
293
+ // Pushes: draft_status_changed, snapshot_available, pairing_rotated.
294
+ // ──────────────────────────────────────────────────────────────────────────
295
+ const HEARTBEAT_INTERVAL_MS = 30_000;
296
+ mobileRouter.get('/events', (req, res) => {
297
+ // Auth: prefer Authorization header, fall back to ?token= query param.
298
+ const authHeader = req.headers.authorization;
299
+ let token;
300
+ if (authHeader && authHeader.startsWith('Bearer ')) {
301
+ token = authHeader.substring(7).trim();
302
+ }
303
+ else if (typeof req.query.token === 'string') {
304
+ token = req.query.token.trim();
305
+ }
306
+ if (!token || !isValidToken(token)) {
307
+ res.status(401).json({ ok: false, error: 'Unauthorized: Invalid or expired pairing token' });
308
+ return;
309
+ }
310
+ // SSE headers.
311
+ res.setHeader('Content-Type', 'text/event-stream');
312
+ res.setHeader('Cache-Control', 'no-cache');
313
+ res.setHeader('Connection', 'keep-alive');
314
+ res.flushHeaders();
315
+ const sendSSE = (payload) => {
316
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
317
+ };
318
+ // Heartbeat to keep the connection alive.
319
+ const heartbeat = setInterval(() => {
320
+ sendSSE({ type: 'heartbeat', timestamp: Date.now() });
321
+ }, HEARTBEAT_INTERVAL_MS);
322
+ // Forward bus events to this SSE client.
323
+ const onEvent = (payload) => {
324
+ sendSSE(payload);
325
+ };
326
+ mobileEventBus.on('event', onEvent);
327
+ // Cleanup on disconnect.
328
+ req.on('close', () => {
329
+ clearInterval(heartbeat);
330
+ mobileEventBus.off('event', onEvent);
331
+ });
332
+ });
224
333
  // Apply mobile auth middleware to all remaining routes
225
334
  mobileRouter.use(mobileAuthMiddleware);
226
335
  // 2. GET /api/mobile/snapshot: Return active run snapshot summary
@@ -331,6 +440,12 @@ async function enqueuePendingFollowupDraft(prompt, query, source) {
331
440
  createdAt: Date.now(),
332
441
  };
333
442
  followupDrafts.push(savedDraft);
443
+ mobileEventBus.emit('event', {
444
+ type: 'draft_status_changed',
445
+ draftId: savedDraft.id,
446
+ status: savedDraft.status,
447
+ timestamp: Date.now(),
448
+ });
334
449
  return savedDraft;
335
450
  }
336
451
  // 5. POST /api/mobile/followup-draft: Save a local operator followup review draft
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Optional TLS for the API server.
3
+ *
4
+ * The mobile-supervision endpoint (`/api/mobile`) rides the main HTTP server.
5
+ * To expose it off-device securely it can be served over HTTPS. This is purely
6
+ * transport security — it does not change any product gate (the dispatch gate
7
+ * `CODEBUDDY_MOBILE_ALLOW_DISPATCH` is untouched).
8
+ *
9
+ * Behaviour, gated by env (HTTP stays the default — no env, no TLS):
10
+ * - `CODEBUDDY_HTTPS=1` (or `CODEBUDDY_MOBILE_TLS=1`) enables HTTPS.
11
+ * - `CODEBUDDY_TLS_CERT` / `CODEBUDDY_TLS_KEY` = paths to a PEM cert/key. This
12
+ * is the standard production approach (Let's Encrypt / reverse proxy / the
13
+ * operator provisions the cert).
14
+ * - If TLS is enabled but no cert is provided, a dev self-signed cert is
15
+ * generated via `openssl` (if on PATH) into `~/.codebuddy/tls/` and reused.
16
+ * - If TLS is enabled but it cannot be satisfied (cert unreadable, or openssl
17
+ * absent), this **throws** with a clear message. It never silently falls back
18
+ * to plain HTTP — an operator who asked for TLS must not be downgraded.
19
+ *
20
+ * Uses only Node built-ins (`https`, `fs`, `child_process`) — no new dependency.
21
+ */
22
+ export interface ServerTlsOptions {
23
+ cert: string | Buffer;
24
+ key: string | Buffer;
25
+ }
26
+ /** Whether HTTPS is requested for the API server. */
27
+ export declare function isTlsEnabled(env?: NodeJS.ProcessEnv): boolean;
28
+ /**
29
+ * Resolve TLS options for the server, or `null` when TLS is not requested.
30
+ *
31
+ * Returns `null` (HTTP, unchanged default) when neither `CODEBUDDY_HTTPS` nor
32
+ * `CODEBUDDY_MOBILE_TLS` is set. Throws a clear error if TLS is requested but
33
+ * cannot be satisfied — it never downgrades to HTTP behind the operator's back.
34
+ */
35
+ export declare function resolveServerTlsOptions(env?: NodeJS.ProcessEnv): ServerTlsOptions | null;