@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,468 @@
1
+ /**
2
+ * Hibernate/wake lifecycle semantics for Hermes runtime backends.
3
+ *
4
+ * Each backend gets a strategy that maps to concrete CLI invocations:
5
+ * - local/wsl/os-sandbox/singularity: lifecycle unsupported (always running or job-based)
6
+ * - docker: pause / unpause / inspect
7
+ * - ssh: connection close / reconnect
8
+ * - modal: app stop / run
9
+ * - daytona: workspace stop / start
10
+ * - vercel-sandbox: unsupported (no lifecycle API)
11
+ */
12
+ import os from 'os';
13
+ import { spawnSync } from 'child_process';
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+ const UNSUPPORTED_BACKEND_IDS = new Set([
18
+ 'local',
19
+ 'wsl',
20
+ 'os-sandbox',
21
+ 'singularity',
22
+ 'vercel-sandbox',
23
+ ]);
24
+ const DEFAULT_TIMEOUT_MS = 30_000;
25
+ const BACKEND_LABELS = {
26
+ 'local': 'Local process',
27
+ 'docker': 'Docker sandbox',
28
+ 'wsl': 'WSL',
29
+ 'os-sandbox': 'Native OS sandbox',
30
+ 'ssh': 'SSH remote shell',
31
+ 'singularity': 'Singularity/Apptainer',
32
+ 'modal': 'Modal',
33
+ 'daytona': 'Daytona',
34
+ 'vercel-sandbox': 'Vercel Sandbox',
35
+ };
36
+ const ALL_BACKEND_IDS = Object.keys(BACKEND_LABELS);
37
+ function runSpawn(command, args, env, timeoutMs) {
38
+ try {
39
+ const result = spawnSync(command, args, {
40
+ env,
41
+ stdio: ['ignore', 'pipe', 'pipe'],
42
+ timeout: timeoutMs,
43
+ windowsHide: true,
44
+ });
45
+ const stdout = decodeBuffer(result.stdout).trim();
46
+ const stderr = decodeBuffer(result.stderr).trim();
47
+ return {
48
+ exitCode: result.status,
49
+ ok: !result.error && result.status === 0,
50
+ stdout,
51
+ stderr,
52
+ };
53
+ }
54
+ catch {
55
+ return { exitCode: null, ok: false, stdout: '', stderr: '' };
56
+ }
57
+ }
58
+ function decodeBuffer(value) {
59
+ if (!value)
60
+ return '';
61
+ if (typeof value === 'string')
62
+ return value;
63
+ return value.toString('utf8');
64
+ }
65
+ function unsupportedAction(backendId, action, durationMs) {
66
+ return {
67
+ backendId,
68
+ action,
69
+ result: 'unsupported',
70
+ state: 'unsupported',
71
+ detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
72
+ durationMs,
73
+ };
74
+ }
75
+ function failedAction(backendId, action, detail, durationMs) {
76
+ return {
77
+ backendId,
78
+ action,
79
+ result: 'failed',
80
+ state: 'unknown',
81
+ detail,
82
+ durationMs,
83
+ };
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Docker lifecycle
87
+ // ---------------------------------------------------------------------------
88
+ function dockerHibernate(containerId, env, timeoutMs) {
89
+ const start = Date.now();
90
+ if (!containerId) {
91
+ return failedAction('docker', 'hibernate', 'containerId is required for docker hibernate.', 0);
92
+ }
93
+ const result = runSpawn('docker', ['pause', containerId], env, timeoutMs);
94
+ const durationMs = Date.now() - start;
95
+ if (result.ok) {
96
+ return {
97
+ backendId: 'docker',
98
+ action: 'hibernate',
99
+ result: 'success',
100
+ state: 'hibernated',
101
+ detail: `Container ${containerId} paused.`,
102
+ durationMs,
103
+ };
104
+ }
105
+ return failedAction('docker', 'hibernate', result.stderr || `docker pause failed (exit ${result.exitCode}).`, durationMs);
106
+ }
107
+ function dockerWake(containerId, env, timeoutMs) {
108
+ const start = Date.now();
109
+ if (!containerId) {
110
+ return failedAction('docker', 'wake', 'containerId is required for docker wake.', 0);
111
+ }
112
+ const result = runSpawn('docker', ['unpause', containerId], env, timeoutMs);
113
+ const durationMs = Date.now() - start;
114
+ if (result.ok) {
115
+ return {
116
+ backendId: 'docker',
117
+ action: 'wake',
118
+ result: 'success',
119
+ state: 'running',
120
+ detail: `Container ${containerId} unpaused.`,
121
+ durationMs,
122
+ };
123
+ }
124
+ return failedAction('docker', 'wake', result.stderr || `docker unpause failed (exit ${result.exitCode}).`, durationMs);
125
+ }
126
+ function dockerStatus(containerId, env, timeoutMs) {
127
+ const start = Date.now();
128
+ if (!containerId) {
129
+ return failedAction('docker', 'status', 'containerId is required for docker status.', 0);
130
+ }
131
+ const result = runSpawn('docker', ['inspect', '--format', '{{.State.Status}}', containerId], env, timeoutMs);
132
+ const durationMs = Date.now() - start;
133
+ if (!result.ok) {
134
+ return failedAction('docker', 'status', result.stderr || `docker inspect failed (exit ${result.exitCode}).`, durationMs);
135
+ }
136
+ const raw = result.stdout.trim().toLowerCase();
137
+ const state = dockerStatusToLifecycleState(raw);
138
+ return {
139
+ backendId: 'docker',
140
+ action: 'status',
141
+ result: 'success',
142
+ state,
143
+ detail: `Container ${containerId} status: ${raw}.`,
144
+ durationMs,
145
+ };
146
+ }
147
+ function dockerStatusToLifecycleState(dockerState) {
148
+ switch (dockerState) {
149
+ case 'running':
150
+ return 'running';
151
+ case 'paused':
152
+ return 'hibernated';
153
+ case 'restarting':
154
+ return 'starting';
155
+ case 'removing':
156
+ case 'exited':
157
+ case 'dead':
158
+ return 'stopping';
159
+ default:
160
+ return 'unknown';
161
+ }
162
+ }
163
+ // ---------------------------------------------------------------------------
164
+ // SSH lifecycle
165
+ // ---------------------------------------------------------------------------
166
+ function sshHibernate(_sshHost, _env, _timeoutMs) {
167
+ // SSH "hibernate" = drop connection. We don't manage persistent connections,
168
+ // so we just report success — the SSH session will close naturally.
169
+ return {
170
+ backendId: 'ssh',
171
+ action: 'hibernate',
172
+ result: 'success',
173
+ state: 'hibernated',
174
+ detail: 'SSH connection released; keepalive stopped.',
175
+ durationMs: 0,
176
+ };
177
+ }
178
+ function sshWake(sshHost, env, timeoutMs) {
179
+ const start = Date.now();
180
+ if (!sshHost) {
181
+ return failedAction('ssh', 'wake', 'sshHost is required for SSH wake.', 0);
182
+ }
183
+ const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=10', sshHost, 'true'], env, timeoutMs);
184
+ const durationMs = Date.now() - start;
185
+ if (result.ok) {
186
+ return {
187
+ backendId: 'ssh',
188
+ action: 'wake',
189
+ result: 'success',
190
+ state: 'running',
191
+ detail: `SSH connection to ${sshHost} verified.`,
192
+ durationMs,
193
+ };
194
+ }
195
+ return failedAction('ssh', 'wake', result.stderr || `SSH connection to ${sshHost} failed (exit ${result.exitCode}).`, durationMs);
196
+ }
197
+ function sshStatus(sshHost, env, timeoutMs) {
198
+ const start = Date.now();
199
+ if (!sshHost) {
200
+ return failedAction('ssh', 'status', 'sshHost is required for SSH status.', 0);
201
+ }
202
+ const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=5', sshHost, 'true'], env, timeoutMs);
203
+ const durationMs = Date.now() - start;
204
+ if (result.ok) {
205
+ return {
206
+ backendId: 'ssh',
207
+ action: 'status',
208
+ result: 'success',
209
+ state: 'running',
210
+ detail: `SSH host ${sshHost} is reachable.`,
211
+ durationMs,
212
+ };
213
+ }
214
+ return {
215
+ backendId: 'ssh',
216
+ action: 'status',
217
+ result: 'success',
218
+ state: 'hibernated',
219
+ detail: `SSH host ${sshHost} is not reachable: ${result.stderr || `exit ${result.exitCode}`}.`,
220
+ durationMs,
221
+ };
222
+ }
223
+ // ---------------------------------------------------------------------------
224
+ // Modal lifecycle
225
+ // ---------------------------------------------------------------------------
226
+ function modalHibernate(modalApp, env, timeoutMs) {
227
+ const start = Date.now();
228
+ if (!modalApp) {
229
+ return failedAction('modal', 'hibernate', 'modalApp is required for Modal hibernate.', 0);
230
+ }
231
+ const result = runSpawn('modal', ['app', 'stop', modalApp], env, timeoutMs);
232
+ const durationMs = Date.now() - start;
233
+ if (result.ok) {
234
+ return {
235
+ backendId: 'modal',
236
+ action: 'hibernate',
237
+ result: 'success',
238
+ state: 'hibernated',
239
+ detail: `Modal app ${modalApp} stopped.`,
240
+ durationMs,
241
+ };
242
+ }
243
+ return failedAction('modal', 'hibernate', result.stderr || `modal app stop failed (exit ${result.exitCode}).`, durationMs);
244
+ }
245
+ function modalWake(modalApp, env, timeoutMs) {
246
+ const start = Date.now();
247
+ if (!modalApp) {
248
+ return failedAction('modal', 'wake', 'modalApp is required for Modal wake.', 0);
249
+ }
250
+ const result = runSpawn('modal', ['run', modalApp], env, timeoutMs);
251
+ const durationMs = Date.now() - start;
252
+ if (result.ok) {
253
+ return {
254
+ backendId: 'modal',
255
+ action: 'wake',
256
+ result: 'success',
257
+ state: 'running',
258
+ detail: `Modal app ${modalApp} started.`,
259
+ durationMs,
260
+ };
261
+ }
262
+ return failedAction('modal', 'wake', result.stderr || `modal run failed (exit ${result.exitCode}).`, durationMs);
263
+ }
264
+ function modalStatus(modalApp, env, timeoutMs) {
265
+ const start = Date.now();
266
+ if (!modalApp) {
267
+ return failedAction('modal', 'status', 'modalApp is required for Modal status.', 0);
268
+ }
269
+ const result = runSpawn('modal', ['app', 'list'], env, timeoutMs);
270
+ const durationMs = Date.now() - start;
271
+ if (!result.ok) {
272
+ return failedAction('modal', 'status', result.stderr || `modal app list failed (exit ${result.exitCode}).`, durationMs);
273
+ }
274
+ const isRunning = result.stdout.includes(modalApp);
275
+ return {
276
+ backendId: 'modal',
277
+ action: 'status',
278
+ result: 'success',
279
+ state: isRunning ? 'running' : 'hibernated',
280
+ detail: isRunning
281
+ ? `Modal app ${modalApp} appears in app list.`
282
+ : `Modal app ${modalApp} not found in app list.`,
283
+ durationMs,
284
+ };
285
+ }
286
+ // ---------------------------------------------------------------------------
287
+ // Daytona lifecycle
288
+ // ---------------------------------------------------------------------------
289
+ function daytonaHibernate(workspace, env, timeoutMs) {
290
+ const start = Date.now();
291
+ if (!workspace) {
292
+ return failedAction('daytona', 'hibernate', 'daytonaWorkspace is required for Daytona hibernate.', 0);
293
+ }
294
+ const result = runSpawn('daytona', ['workspace', 'stop', workspace], env, timeoutMs);
295
+ const durationMs = Date.now() - start;
296
+ if (result.ok) {
297
+ return {
298
+ backendId: 'daytona',
299
+ action: 'hibernate',
300
+ result: 'success',
301
+ state: 'hibernated',
302
+ detail: `Daytona workspace ${workspace} stopped.`,
303
+ durationMs,
304
+ };
305
+ }
306
+ return failedAction('daytona', 'hibernate', result.stderr || `daytona workspace stop failed (exit ${result.exitCode}).`, durationMs);
307
+ }
308
+ function daytonaWake(workspace, env, timeoutMs) {
309
+ const start = Date.now();
310
+ if (!workspace) {
311
+ return failedAction('daytona', 'wake', 'daytonaWorkspace is required for Daytona wake.', 0);
312
+ }
313
+ const result = runSpawn('daytona', ['workspace', 'start', workspace], env, timeoutMs);
314
+ const durationMs = Date.now() - start;
315
+ if (result.ok) {
316
+ return {
317
+ backendId: 'daytona',
318
+ action: 'wake',
319
+ result: 'success',
320
+ state: 'running',
321
+ detail: `Daytona workspace ${workspace} started.`,
322
+ durationMs,
323
+ };
324
+ }
325
+ return failedAction('daytona', 'wake', result.stderr || `daytona workspace start failed (exit ${result.exitCode}).`, durationMs);
326
+ }
327
+ function daytonaStatus(workspace, env, timeoutMs) {
328
+ const start = Date.now();
329
+ if (!workspace) {
330
+ return failedAction('daytona', 'status', 'daytonaWorkspace is required for Daytona status.', 0);
331
+ }
332
+ const result = runSpawn('daytona', ['workspace', 'list'], env, timeoutMs);
333
+ const durationMs = Date.now() - start;
334
+ if (!result.ok) {
335
+ return failedAction('daytona', 'status', result.stderr || `daytona workspace list failed (exit ${result.exitCode}).`, durationMs);
336
+ }
337
+ const isRunning = result.stdout.includes(workspace);
338
+ return {
339
+ backendId: 'daytona',
340
+ action: 'status',
341
+ result: 'success',
342
+ state: isRunning ? 'running' : 'hibernated',
343
+ detail: isRunning
344
+ ? `Daytona workspace ${workspace} appears in workspace list.`
345
+ : `Daytona workspace ${workspace} not found in workspace list.`,
346
+ durationMs,
347
+ };
348
+ }
349
+ // ---------------------------------------------------------------------------
350
+ // Public API
351
+ // ---------------------------------------------------------------------------
352
+ /**
353
+ * Execute a lifecycle action on a backend.
354
+ */
355
+ export function executeBackendLifecycleAction(backendId, action, options = {}) {
356
+ const env = options.env ?? process.env;
357
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
358
+ if (UNSUPPORTED_BACKEND_IDS.has(backendId)) {
359
+ return unsupportedAction(backendId, action, 0);
360
+ }
361
+ if (!BACKEND_LABELS[backendId]) {
362
+ return failedAction(backendId, action, `Unknown backend: ${backendId}.`, 0);
363
+ }
364
+ switch (backendId) {
365
+ case 'docker': {
366
+ const containerId = options.containerId ?? env.CODEBUDDY_DOCKER_CONTAINER ?? '';
367
+ if (action === 'hibernate')
368
+ return dockerHibernate(containerId, env, timeoutMs);
369
+ if (action === 'wake')
370
+ return dockerWake(containerId, env, timeoutMs);
371
+ return dockerStatus(containerId, env, timeoutMs);
372
+ }
373
+ case 'ssh': {
374
+ const sshHost = options.sshHost
375
+ ?? env.CODEBUDDY_SSH_HOST
376
+ ?? env.SSH_HOST
377
+ ?? env.CODEBUDDY_REMOTE_HOST
378
+ ?? '';
379
+ if (action === 'hibernate')
380
+ return sshHibernate(sshHost, env, timeoutMs);
381
+ if (action === 'wake')
382
+ return sshWake(sshHost, env, timeoutMs);
383
+ return sshStatus(sshHost, env, timeoutMs);
384
+ }
385
+ case 'modal': {
386
+ const modalApp = options.modalApp ?? env.CODEBUDDY_MODAL_APP ?? '';
387
+ if (action === 'hibernate')
388
+ return modalHibernate(modalApp, env, timeoutMs);
389
+ if (action === 'wake')
390
+ return modalWake(modalApp, env, timeoutMs);
391
+ return modalStatus(modalApp, env, timeoutMs);
392
+ }
393
+ case 'daytona': {
394
+ const workspace = options.daytonaWorkspace ?? env.CODEBUDDY_DAYTONA_WORKSPACE ?? '';
395
+ if (action === 'hibernate')
396
+ return daytonaHibernate(workspace, env, timeoutMs);
397
+ if (action === 'wake')
398
+ return daytonaWake(workspace, env, timeoutMs);
399
+ return daytonaStatus(workspace, env, timeoutMs);
400
+ }
401
+ default:
402
+ return unsupportedAction(backendId, action, 0);
403
+ }
404
+ }
405
+ /**
406
+ * Build a lifecycle status report for all backends.
407
+ */
408
+ export function buildBackendLifecycleStatusReport(options = {}) {
409
+ const now = options.now ?? (() => new Date());
410
+ const entries = ALL_BACKEND_IDS.map((backendId) => {
411
+ const lifecycleSupported = !UNSUPPORTED_BACKEND_IDS.has(backendId);
412
+ if (!lifecycleSupported) {
413
+ return {
414
+ backendId,
415
+ label: BACKEND_LABELS[backendId] ?? backendId,
416
+ lifecycleSupported: false,
417
+ state: 'unsupported',
418
+ detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
419
+ };
420
+ }
421
+ const statusAction = executeBackendLifecycleAction(backendId, 'status', options);
422
+ return {
423
+ backendId,
424
+ label: BACKEND_LABELS[backendId] ?? backendId,
425
+ lifecycleSupported: true,
426
+ state: statusAction.state,
427
+ detail: statusAction.detail,
428
+ };
429
+ });
430
+ return {
431
+ kind: 'hermes_runtime_lifecycle_status',
432
+ schemaVersion: 1,
433
+ generatedAt: now().toISOString(),
434
+ platform: os.platform(),
435
+ arch: os.arch(),
436
+ backends: entries,
437
+ };
438
+ }
439
+ /**
440
+ * Render a lifecycle status report as human-readable text.
441
+ */
442
+ export function renderBackendLifecycleStatusReport(report) {
443
+ const lines = [
444
+ 'Hermes runtime lifecycle status',
445
+ `Platform: ${report.platform}/${report.arch}`,
446
+ `Generated: ${report.generatedAt}`,
447
+ '',
448
+ 'Backends:',
449
+ ...report.backends.map((entry) => {
450
+ const supported = entry.lifecycleSupported ? 'lifecycle' : 'no-lifecycle';
451
+ return ` ${entry.backendId}: ${entry.state} (${supported}) — ${entry.detail}`;
452
+ }),
453
+ ];
454
+ return lines.join('\n');
455
+ }
456
+ /**
457
+ * Render a single lifecycle action result as human-readable text.
458
+ */
459
+ export function renderBackendLifecycleAction(action) {
460
+ const lines = [
461
+ `Hermes runtime lifecycle ${action.action} (${action.backendId}): ${action.result}`,
462
+ ` State: ${action.state}`,
463
+ ` Detail: ${action.detail}`,
464
+ ` Duration: ${action.durationMs}ms`,
465
+ ];
466
+ return lines.join('\n');
467
+ }
468
+ //# sourceMappingURL=hermes-runtime-lifecycle.js.map
@@ -15,6 +15,7 @@
15
15
  import { CodeBuddyClient } from '../codebuddy/client.js';
16
16
  import { detectProviderFromEnv } from '../utils/provider-detector.js';
17
17
  import { getLessonCandidateQueue, } from './lesson-candidate-queue.js';
18
+ import { scanForSecrets } from '../fleet/privacy-lint.js';
18
19
  import { logger } from '../utils/logger.js';
19
20
  const VALID = ['PATTERN', 'RULE', 'CONTEXT', 'INSIGHT'];
20
21
  const SYSTEM_PROMPT = `You extract REUSABLE PROCEDURAL LESSONS from a coding session that just completed.
@@ -98,6 +99,15 @@ export async function proposeLessonsFromSession(chatHistory, workDir = process.c
98
99
  const content = (cand.content || '').trim();
99
100
  if (!content || !VALID.includes(category))
100
101
  continue;
102
+ // WS3 guard-rail: never persist secrets/PII into memory — even into the
103
+ // review queue, where a reviewer could approve them into lessons.md.
104
+ const lint = scanForSecrets(`${content}\n${cand.context ?? ''}`);
105
+ if (lint.hasSecrets) {
106
+ logger.warn('[lesson-auto-proposer] candidate dropped: contains secret/PII material', {
107
+ kinds: lint.matches.map((m) => m.kind),
108
+ });
109
+ continue;
110
+ }
101
111
  try {
102
112
  const { candidate } = queue.propose({
103
113
  category,
@@ -18,3 +18,4 @@ export type { AutoRepairConfig } from './auto-repair-middleware.js';
18
18
  export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
19
19
  export type { QualityGateConfig, QualityGate } from './quality-gate-middleware.js';
20
20
  export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
21
+ export { VisualValidationMiddleware } from './visual-validation-middleware.js';
@@ -14,4 +14,5 @@ export { WorkflowGuardMiddleware } from './workflow-guard.js';
14
14
  export { AutoRepairMiddleware, createAutoRepairMiddleware } from './auto-repair-middleware.js';
15
15
  export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
16
16
  export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
17
+ export { VisualValidationMiddleware } from './visual-validation-middleware.js';
17
18
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Session Duration Middleware (WS3-T3 — auto-pause & resume)
3
+ *
4
+ * Very long sessions (12 h+) degrade context quality and pile up risk.
5
+ * Past a configurable threshold this middleware suggests a clean pause,
6
+ * takes a fresh context snapshot as the resume point (WS3-T2), and
7
+ * reminds periodically instead of nagging every turn.
8
+ *
9
+ * It never stops the loop — pausing stays the operator's decision; the
10
+ * autonomous runner has its own bounded budgets (WS1).
11
+ *
12
+ * @module agent/middleware
13
+ */
14
+ import { ConversationMiddleware, MiddlewareContext, MiddlewareResult } from './types.js';
15
+ export interface SessionDurationOptions {
16
+ /** Pause-suggestion threshold in ms. Default: CODEBUDDY_SESSION_PAUSE_HOURS (12 h); 0 disables. */
17
+ maxSessionMs?: number;
18
+ /** Re-warn cadence once past the threshold. Default 60 min. */
19
+ remindEveryMs?: number;
20
+ /** Injectable clock for tests. */
21
+ now?: () => number;
22
+ /** Take a fresh context snapshot when the pause is suggested (WS3-T2 hook). */
23
+ takeSnapshot?: () => void;
24
+ }
25
+ export declare class SessionDurationMiddleware implements ConversationMiddleware {
26
+ readonly name = "session-duration";
27
+ readonly priority = 35;
28
+ private readonly startedAt;
29
+ private readonly thresholdMs;
30
+ private readonly remindEveryMs;
31
+ private readonly now;
32
+ private readonly takeSnapshot;
33
+ private lastWarnedAt;
34
+ constructor(options?: SessionDurationOptions);
35
+ beforeTurn(_context: MiddlewareContext): MiddlewareResult;
36
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Session Duration Middleware (WS3-T3 — auto-pause & resume)
3
+ *
4
+ * Very long sessions (12 h+) degrade context quality and pile up risk.
5
+ * Past a configurable threshold this middleware suggests a clean pause,
6
+ * takes a fresh context snapshot as the resume point (WS3-T2), and
7
+ * reminds periodically instead of nagging every turn.
8
+ *
9
+ * It never stops the loop — pausing stays the operator's decision; the
10
+ * autonomous runner has its own bounded budgets (WS1).
11
+ *
12
+ * @module agent/middleware
13
+ */
14
+ import { RunStore } from '../../observability/run-store.js';
15
+ import { logger } from '../../utils/logger.js';
16
+ const HOUR_MS = 3_600_000;
17
+ function defaultThresholdMs() {
18
+ const hours = parseFloat(process.env.CODEBUDDY_SESSION_PAUSE_HOURS || '12');
19
+ if (!Number.isFinite(hours) || hours <= 0)
20
+ return 0; // 0/invalid → disabled
21
+ return hours * HOUR_MS;
22
+ }
23
+ export class SessionDurationMiddleware {
24
+ name = 'session-duration';
25
+ priority = 35;
26
+ startedAt;
27
+ thresholdMs;
28
+ remindEveryMs;
29
+ now;
30
+ takeSnapshot;
31
+ lastWarnedAt = null;
32
+ constructor(options = {}) {
33
+ this.now = options.now ?? Date.now;
34
+ this.startedAt = this.now();
35
+ this.thresholdMs = options.maxSessionMs ?? defaultThresholdMs();
36
+ this.remindEveryMs = options.remindEveryMs ?? HOUR_MS;
37
+ this.takeSnapshot = options.takeSnapshot;
38
+ }
39
+ beforeTurn(_context) {
40
+ if (this.thresholdMs <= 0)
41
+ return { action: 'continue' };
42
+ const elapsed = this.now() - this.startedAt;
43
+ if (elapsed < this.thresholdMs)
44
+ return { action: 'continue' };
45
+ if (this.lastWarnedAt !== null && this.now() - this.lastWarnedAt < this.remindEveryMs) {
46
+ return { action: 'continue' };
47
+ }
48
+ this.lastWarnedAt = this.now();
49
+ // Fresh resume point right when the pause is suggested.
50
+ try {
51
+ this.takeSnapshot?.();
52
+ }
53
+ catch (err) {
54
+ logger.debug('[session-duration] snapshot on pause suggestion failed', { error: String(err) });
55
+ }
56
+ try {
57
+ const runStore = RunStore.getInstance();
58
+ if (runStore.getCurrentRunId()) {
59
+ runStore.appendEvent('pause_suggested', {
60
+ elapsedMs: elapsed,
61
+ thresholdMs: this.thresholdMs,
62
+ });
63
+ }
64
+ }
65
+ catch {
66
+ // Observability must never break the loop.
67
+ }
68
+ const hours = (elapsed / HOUR_MS).toFixed(1);
69
+ return {
70
+ action: 'warn',
71
+ message: `⏸️ Session running for ${hours} h. Consider a clean pause: a fresh context ` +
72
+ `snapshot was just written (.codebuddy/context-snapshot.json), a handoff is ` +
73
+ `written on exit (.codebuddy/HANDOFF.md), and \`buddy --continue\` reloads the ` +
74
+ `session. Long sessions degrade context quality — \`/compact\` also helps.`,
75
+ };
76
+ }
77
+ }
78
+ //# sourceMappingURL=session-duration.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Visual Validation Middleware
3
+ *
4
+ * Checks if the agent has saved an Office document (Excel, PowerPoint, Word).
5
+ * If so, it warns the agent to consider running a visual validation step
6
+ * using `snapshot_with_screenshot` to verify the layout, fonts, and alignments.
7
+ *
8
+ * Priority 156
9
+ */
10
+ import type { ConversationMiddleware, MiddlewareContext, MiddlewareResult } from './types.js';
11
+ export declare class VisualValidationMiddleware implements ConversationMiddleware {
12
+ readonly name = "visual-validation";
13
+ readonly priority = 156;
14
+ private hasWarnedForFiles;
15
+ afterTurn(context: MiddlewareContext): Promise<MiddlewareResult>;
16
+ private getRecentlySavedOfficeFiles;
17
+ }