@vellumai/assistant 0.6.5 → 0.6.6

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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -1,89 +0,0 @@
1
- /**
2
- * Scope resolution and policy enforcement for OAuth providers.
3
- *
4
- * Pure module (no side effects, no I/O) that resolves the final set of
5
- * scopes for an OAuth flow based on the provider profile's scope policy.
6
- */
7
-
8
- import type { OAuthScopePolicy } from "./connect-types.js";
9
-
10
- // ---------------------------------------------------------------------------
11
- // Result types
12
- // ---------------------------------------------------------------------------
13
-
14
- export type ScopeResolutionResult =
15
- | { ok: true; scopes: string[] }
16
- | { ok: false; error: string; allowedScopes?: string[] };
17
-
18
- // ---------------------------------------------------------------------------
19
- // Implementation
20
- // ---------------------------------------------------------------------------
21
-
22
- /**
23
- * Resolve the final set of scopes for an OAuth flow.
24
- *
25
- * - If `requestedScopes` is undefined or empty, returns the provider's
26
- * `defaultScopes`.
27
- * - Otherwise, starts with `defaultScopes` and validates each additional
28
- * requested scope against the provider's `scopePolicy`.
29
- * - Returns a deduplicated union of default + approved requested scopes.
30
- */
31
- /** Minimal shape needed by the scope resolver. */
32
- export interface ScopeResolverInput {
33
- service: string;
34
- defaultScopes: string[];
35
- scopePolicy: OAuthScopePolicy;
36
- }
37
-
38
- export function resolveScopes(
39
- profile: ScopeResolverInput,
40
- requestedScopes?: string[],
41
- ): ScopeResolutionResult {
42
- const { defaultScopes, scopePolicy, service } = profile;
43
-
44
- // No requested scopes — use defaults
45
- if (!requestedScopes || requestedScopes.length === 0) {
46
- return { ok: true, scopes: [...defaultScopes] };
47
- }
48
-
49
- const defaultSet = new Set(defaultScopes);
50
- const finalScopes = new Set(defaultScopes);
51
-
52
- for (const scope of requestedScopes) {
53
- // Already in defaults — no policy check needed
54
- if (defaultSet.has(scope)) {
55
- continue;
56
- }
57
-
58
- // Check forbidden list first
59
- if (scopePolicy.forbiddenScopes.includes(scope)) {
60
- return {
61
- ok: false,
62
- error: `Scope '${scope}' is forbidden for ${service}`,
63
- };
64
- }
65
-
66
- // Additional scopes not allowed at all
67
- if (!scopePolicy.allowAdditionalScopes) {
68
- return {
69
- ok: false,
70
- error: `Additional scopes are not allowed for ${service}. Only the default scopes may be used.`,
71
- allowedScopes: [...defaultScopes],
72
- };
73
- }
74
-
75
- // Additional scopes allowed, but this one isn't in the optional list
76
- if (!scopePolicy.allowedOptionalScopes.includes(scope)) {
77
- return {
78
- ok: false,
79
- error: `Scope '${scope}' is not in the allowed optional scopes for ${service}`,
80
- allowedScopes: [...defaultScopes, ...scopePolicy.allowedOptionalScopes],
81
- };
82
- }
83
-
84
- // Passed all checks — include it
85
- finalScopes.add(scope);
86
- }
87
-
88
- return { ok: true, scopes: [...finalScopes] };
89
- }
@@ -1,94 +0,0 @@
1
- import { getGatewayInternalBaseUrl } from "../config/env.js";
2
- import { mintEdgeRelayToken } from "./auth/token-service.js";
3
-
4
- export class GatewayRequestError extends Error {
5
- statusCode: number;
6
- gatewayError: string | undefined;
7
-
8
- constructor(
9
- message: string,
10
- statusCode: number,
11
- gatewayError: string | undefined,
12
- ) {
13
- super(message);
14
- this.name = "GatewayRequestError";
15
- this.statusCode = statusCode;
16
- this.gatewayError = gatewayError;
17
- }
18
- }
19
-
20
- /**
21
- * Parse a non-ok gateway response into a human-readable error message.
22
- * Matches the existing pattern in contact tools: try JSON parse, extract
23
- * `.error` string, fall back to raw body text, fall back to status code.
24
- */
25
- async function parseErrorResponse(
26
- resp: Response,
27
- ): Promise<GatewayRequestError> {
28
- const body = await resp.text();
29
- let gatewayError: string | undefined;
30
- let message = `Gateway request failed (${resp.status})`;
31
-
32
- try {
33
- const parsed = JSON.parse(body) as {
34
- error?: string | { code?: string; message?: string };
35
- };
36
- if (parsed.error) {
37
- // Runtime httpError() returns { error: { code, message } } while
38
- // gateway returns { error: "string" }. Handle both formats.
39
- const errStr =
40
- typeof parsed.error === "string"
41
- ? parsed.error
42
- : (parsed.error.message ?? JSON.stringify(parsed.error));
43
- gatewayError = errStr;
44
- message = errStr;
45
- }
46
- } catch {
47
- if (body) message = body;
48
- }
49
-
50
- return new GatewayRequestError(message, resp.status, gatewayError);
51
- }
52
-
53
- export async function gatewayGet<T>(path: string): Promise<T> {
54
- const baseUrl = getGatewayInternalBaseUrl();
55
- const token = mintEdgeRelayToken();
56
-
57
- const resp = await fetch(`${baseUrl}${path}`, {
58
- method: "GET",
59
- headers: {
60
- Accept: "application/json",
61
- Authorization: `Bearer ${token}`,
62
- },
63
- });
64
-
65
- if (!resp.ok) {
66
- throw await parseErrorResponse(resp);
67
- }
68
-
69
- return (await resp.json()) as T;
70
- }
71
-
72
- export async function gatewayPost<T>(
73
- path: string,
74
- body: unknown,
75
- ): Promise<{ status: number; data: T }> {
76
- const baseUrl = getGatewayInternalBaseUrl();
77
- const token = mintEdgeRelayToken();
78
-
79
- const resp = await fetch(`${baseUrl}${path}`, {
80
- method: "POST",
81
- headers: {
82
- "Content-Type": "application/json",
83
- Authorization: `Bearer ${token}`,
84
- },
85
- body: JSON.stringify(body),
86
- });
87
-
88
- if (!resp.ok) {
89
- throw await parseErrorResponse(resp);
90
- }
91
-
92
- const data = (await resp.json()) as T;
93
- return { status: resp.status, data };
94
- }
@@ -1,156 +0,0 @@
1
- /**
2
- * HTTP route handler for watch (ambient observation) functionality.
3
- *
4
- * Decoupled from computer-use routes so that the watch endpoint has
5
- * zero dependency on CU session state.
6
- */
7
-
8
- import { z } from "zod";
9
-
10
- import { getLogger } from "../../util/logger.js";
11
- import { httpError } from "../http-errors.js";
12
- import type { RouteDefinition } from "../http-router.js";
13
-
14
- const log = getLogger("watch-routes");
15
-
16
- // ---------------------------------------------------------------------------
17
- // Dependency injection interface
18
- // ---------------------------------------------------------------------------
19
-
20
- /**
21
- * Minimal interface for watch observation handling.
22
- * The daemon wires a concrete implementation at startup.
23
- */
24
- export interface WatchDeps {
25
- /** Handle a watch observation. */
26
- handleWatchObservation: (params: {
27
- watchId: string;
28
- conversationId: string;
29
- ocrText: string;
30
- appName?: string;
31
- windowTitle?: string;
32
- bundleIdentifier?: string;
33
- timestamp: number;
34
- captureIndex: number;
35
- totalExpected: number;
36
- }) => Promise<void>;
37
- }
38
-
39
- // ---------------------------------------------------------------------------
40
- // Route handler
41
- // ---------------------------------------------------------------------------
42
-
43
- /**
44
- * POST /v1/computer-use/watch — send a watch observation.
45
- *
46
- * Body: { watchId, conversationId, ocrText, appName?, windowTitle?,
47
- * bundleIdentifier?, timestamp, captureIndex, totalExpected }
48
- */
49
- async function handleWatchObservationRoute(
50
- req: Request,
51
- deps: WatchDeps,
52
- ): Promise<Response> {
53
- const body = (await req.json()) as {
54
- watchId?: string;
55
- conversationId?: string;
56
- ocrText?: string;
57
- appName?: string;
58
- windowTitle?: string;
59
- bundleIdentifier?: string;
60
- timestamp?: number;
61
- captureIndex?: number;
62
- totalExpected?: number;
63
- };
64
-
65
- if (!body.watchId || typeof body.watchId !== "string") {
66
- return httpError("BAD_REQUEST", "watchId is required", 400);
67
- }
68
- if (!body.conversationId || typeof body.conversationId !== "string") {
69
- return httpError("BAD_REQUEST", "conversationId is required", 400);
70
- }
71
- if (!body.ocrText || typeof body.ocrText !== "string") {
72
- return httpError("BAD_REQUEST", "ocrText is required", 400);
73
- }
74
- if (typeof body.timestamp !== "number") {
75
- return httpError("BAD_REQUEST", "timestamp is required", 400);
76
- }
77
- if (typeof body.captureIndex !== "number") {
78
- return httpError("BAD_REQUEST", "captureIndex is required", 400);
79
- }
80
- if (typeof body.totalExpected !== "number") {
81
- return httpError("BAD_REQUEST", "totalExpected is required", 400);
82
- }
83
-
84
- try {
85
- await deps.handleWatchObservation({
86
- watchId: body.watchId,
87
- conversationId: body.conversationId,
88
- ocrText: body.ocrText,
89
- appName: body.appName,
90
- windowTitle: body.windowTitle,
91
- bundleIdentifier: body.bundleIdentifier,
92
- timestamp: body.timestamp,
93
- captureIndex: body.captureIndex,
94
- totalExpected: body.totalExpected,
95
- });
96
-
97
- return Response.json({ ok: true });
98
- } catch (err) {
99
- const message = err instanceof Error ? err.message : String(err);
100
- log.error(
101
- { err, watchId: body.watchId },
102
- "Failed to handle watch observation via HTTP",
103
- );
104
- return httpError("INTERNAL_ERROR", message, 500);
105
- }
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // Route definitions
110
- // ---------------------------------------------------------------------------
111
-
112
- export function watchRouteDefinitions(deps: {
113
- getWatchDeps?: () => WatchDeps;
114
- }): RouteDefinition[] {
115
- const getDeps = (): WatchDeps => {
116
- if (!deps.getWatchDeps) {
117
- throw new Error("Watch deps not available");
118
- }
119
- return deps.getWatchDeps();
120
- };
121
-
122
- return [
123
- {
124
- endpoint: "computer-use/watch",
125
- method: "POST",
126
- policyKey: "computer-use/watch",
127
- summary: "Submit watch observation",
128
- description: "Send a screen observation from ambient watch mode.",
129
- tags: ["computer-use"],
130
- handler: async ({ req }) => handleWatchObservationRoute(req, getDeps()),
131
- requestBody: z.object({
132
- watchId: z.string().describe("Watch session ID"),
133
- conversationId: z.string().describe("Conversation to associate with"),
134
- ocrText: z.string().describe("OCR text from screen capture"),
135
- appName: z.string().describe("Active application name").optional(),
136
- windowTitle: z.string().describe("Active window title").optional(),
137
- bundleIdentifier: z
138
- .string()
139
- .describe("Application bundle identifier")
140
- .optional(),
141
- timestamp: z.number().describe("Capture timestamp (epoch ms)"),
142
- captureIndex: z
143
- .number()
144
- .int()
145
- .describe("Index of this capture in the batch"),
146
- totalExpected: z
147
- .number()
148
- .int()
149
- .describe("Total captures expected in the batch"),
150
- }),
151
- responseBody: z.object({
152
- ok: z.boolean(),
153
- }),
154
- },
155
- ];
156
- }
@@ -1,203 +0,0 @@
1
- /**
2
- * Handle shotgun (screen-watch) signals delivered via signal files from the CLI.
3
- *
4
- * Each invocation writes JSON to a unique `signals/shotgun.<requestId>` file.
5
- * ConfigWatcher detects the new file and invokes {@link handleShotgunSignal},
6
- * which reads the payload, creates or queries a watch session, and writes
7
- * the result to `signals/shotgun.<requestId>.result` for the CLI to pick up.
8
- *
9
- * Supports two actions:
10
- * - `start`: Create a new watch session and return the watchId/conversationId.
11
- * - `status`: Query the status of an existing watch session by watchId.
12
- */
13
-
14
- import crypto from "node:crypto";
15
- import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
16
- import { join } from "node:path";
17
-
18
- import { getIsContainerized } from "../config/env-registry.js";
19
- import {
20
- fireWatchCompletionNotifier,
21
- fireWatchStartNotifier,
22
- type WatchSession,
23
- watchSessions,
24
- } from "../tools/watch/watch-state.js";
25
- import { getLogger } from "../util/logger.js";
26
- import { getSignalsDir } from "../util/platform.js";
27
-
28
- const log = getLogger("signal:shotgun");
29
-
30
- const SHORT_HASH_LENGTH = 8;
31
-
32
- interface ShotgunResult {
33
- requestId: string;
34
- ok: boolean;
35
- error?: string;
36
- watchId?: string;
37
- conversationId?: string;
38
- status?: string;
39
- }
40
-
41
- function writeResult(requestId: string, result: ShotgunResult): void {
42
- try {
43
- writeFileSync(
44
- join(getSignalsDir(), `shotgun.${requestId}.result`),
45
- JSON.stringify(result),
46
- );
47
- } catch (err) {
48
- log.error({ err, requestId }, "Failed to write shotgun signal result");
49
- }
50
- }
51
-
52
- /**
53
- * Read a `signals/shotgun.<requestId>` file, process the action, and write
54
- * the result to `signals/shotgun.<requestId>.result`. Called by ConfigWatcher
55
- * when a matching signal file is created or modified.
56
- */
57
- export function handleShotgunSignal(filename: string): void {
58
- if (getIsContainerized()) return;
59
-
60
- const signalPath = join(getSignalsDir(), filename);
61
- let raw: string;
62
- try {
63
- raw = readFileSync(signalPath, "utf-8");
64
- } catch {
65
- return;
66
- }
67
-
68
- try {
69
- unlinkSync(signalPath);
70
- } catch {
71
- // Best-effort cleanup.
72
- }
73
-
74
- let payload: {
75
- requestId?: string;
76
- action?: string;
77
- durationSeconds?: number;
78
- intervalSeconds?: number;
79
- focusArea?: string;
80
- watchId?: string;
81
- };
82
- try {
83
- payload = JSON.parse(raw) as typeof payload;
84
- } catch (err) {
85
- log.error({ err, filename }, "Failed to parse shotgun signal file");
86
- return;
87
- }
88
-
89
- const { requestId } = payload;
90
- if (!requestId || typeof requestId !== "string") {
91
- log.warn("Shotgun signal missing requestId");
92
- return;
93
- }
94
-
95
- const { action } = payload;
96
-
97
- if (action === "start") {
98
- handleStart(requestId, payload);
99
- } else if (action === "status") {
100
- handleStatus(requestId, payload);
101
- } else {
102
- writeResult(requestId, {
103
- requestId,
104
- ok: false,
105
- error: `Unknown action: ${String(action)}`,
106
- });
107
- }
108
- }
109
-
110
- function handleStart(
111
- requestId: string,
112
- payload: {
113
- durationSeconds?: number;
114
- intervalSeconds?: number;
115
- focusArea?: string;
116
- },
117
- ): void {
118
- const durationSeconds =
119
- typeof payload.durationSeconds === "number" && payload.durationSeconds > 0
120
- ? payload.durationSeconds
121
- : 300;
122
-
123
- const intervalSeconds =
124
- typeof payload.intervalSeconds === "number" && payload.intervalSeconds > 0
125
- ? payload.intervalSeconds
126
- : 5;
127
-
128
- const focusArea =
129
- typeof payload.focusArea === "string" && payload.focusArea.length > 0
130
- ? payload.focusArea
131
- : "general observation";
132
-
133
- const watchId = crypto.randomUUID().slice(0, SHORT_HASH_LENGTH);
134
- const conversationId = `shotgun-${watchId}`;
135
- const now = Date.now();
136
-
137
- const session: WatchSession = {
138
- watchId,
139
- conversationId,
140
- focusArea,
141
- durationSeconds,
142
- intervalSeconds,
143
- observations: [],
144
- commentaryCount: 0,
145
- status: "active",
146
- startedAt: now,
147
- };
148
-
149
- watchSessions.set(watchId, session);
150
- fireWatchStartNotifier(conversationId, session);
151
-
152
- session.timeoutHandle = setTimeout(() => {
153
- session.status = "completing";
154
- session.timeoutHandle = undefined;
155
- log.info(
156
- { watchId, focusArea },
157
- "Shotgun session duration expired, marking as completing",
158
- );
159
- fireWatchCompletionNotifier(conversationId, session);
160
- }, durationSeconds * 1000);
161
-
162
- log.info(
163
- { watchId, conversationId, focusArea, durationSeconds, intervalSeconds },
164
- "Shotgun watch session started via signal",
165
- );
166
-
167
- writeResult(requestId, {
168
- requestId,
169
- ok: true,
170
- watchId,
171
- conversationId,
172
- });
173
- }
174
-
175
- function handleStatus(requestId: string, payload: { watchId?: string }): void {
176
- const { watchId } = payload;
177
- if (!watchId || typeof watchId !== "string") {
178
- writeResult(requestId, {
179
- requestId,
180
- ok: false,
181
- error: "Missing watchId for status query",
182
- });
183
- return;
184
- }
185
-
186
- const session = watchSessions.get(watchId);
187
- if (!session) {
188
- writeResult(requestId, {
189
- requestId,
190
- ok: false,
191
- error: `No watch session found for watchId: ${watchId}`,
192
- });
193
- return;
194
- }
195
-
196
- writeResult(requestId, {
197
- requestId,
198
- ok: true,
199
- watchId,
200
- conversationId: session.conversationId,
201
- status: session.status,
202
- });
203
- }
@@ -1,144 +0,0 @@
1
- import crypto from "node:crypto";
2
-
3
- import { RiskLevel } from "../../permissions/types.js";
4
- import type { ToolDefinition } from "../../providers/types.js";
5
- import { getLogger } from "../../util/logger.js";
6
- import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
7
- import type { WatchSession } from "./watch-state.js";
8
- import {
9
- fireWatchCompletionNotifier,
10
- fireWatchStartNotifier,
11
- getActiveWatchSession,
12
- watchSessions,
13
- } from "./watch-state.js";
14
-
15
- const log = getLogger("screen-watch");
16
-
17
- const SHORT_HASH_LENGTH = 8;
18
-
19
- class ScreenWatchTool implements Tool {
20
- name = "start_screen_watch";
21
- description =
22
- "Start observing the screen at regular intervals for a specified duration. Captures OCR text from the active window and provides periodic commentary.";
23
- category = "observation";
24
- defaultRiskLevel = RiskLevel.Low;
25
-
26
- getDefinition(): ToolDefinition {
27
- return {
28
- name: this.name,
29
- description: this.description,
30
- input_schema: {
31
- type: "object",
32
- properties: {
33
- duration_minutes: {
34
- type: "number",
35
- description: "How long to watch in minutes (1-15, default 5)",
36
- },
37
- interval_seconds: {
38
- type: "number",
39
- description: "Seconds between screen captures (5-30, default 10)",
40
- },
41
- focus_area: {
42
- type: "string",
43
- description: "What to focus on observing",
44
- },
45
- },
46
- required: ["focus_area"],
47
- },
48
- };
49
- }
50
-
51
- async execute(
52
- input: Record<string, unknown>,
53
- context: ToolContext,
54
- ): Promise<ToolExecutionResult> {
55
- const { conversationId } = context;
56
-
57
- // Validate focus_area
58
- const focusArea =
59
- typeof input.focus_area === "string" && input.focus_area.length > 0
60
- ? input.focus_area
61
- : undefined;
62
-
63
- if (!focusArea) {
64
- return {
65
- content: "Error: focus_area is required and must be a non-empty string",
66
- isError: true,
67
- };
68
- }
69
-
70
- // Clamp duration to 1-15 minutes
71
- let durationMinutes =
72
- typeof input.duration_minutes === "number" ? input.duration_minutes : 5;
73
- durationMinutes = Math.max(1, Math.min(15, durationMinutes));
74
-
75
- // Clamp interval to 5-30 seconds
76
- let intervalSeconds =
77
- typeof input.interval_seconds === "number" ? input.interval_seconds : 10;
78
- intervalSeconds = Math.max(5, Math.min(30, intervalSeconds));
79
-
80
- // Check for existing active session
81
- const existing = getActiveWatchSession(conversationId);
82
- if (existing) {
83
- return {
84
- content: `Error: An active watch session already exists for this conversation (watchId: ${existing.watchId}, focus: "${existing.focusArea}"). Cancel or wait for it to complete before starting a new one.`,
85
- isError: true,
86
- };
87
- }
88
-
89
- // Generate watchId
90
- const watchId = crypto.randomUUID().slice(0, SHORT_HASH_LENGTH);
91
- const now = Date.now();
92
- const durationSeconds = durationMinutes * 60;
93
-
94
- // Create session
95
- const session: WatchSession = {
96
- watchId,
97
- conversationId,
98
- focusArea,
99
- durationSeconds,
100
- intervalSeconds,
101
- observations: [],
102
- commentaryCount: 0,
103
- status: "active",
104
- startedAt: now,
105
- };
106
-
107
- // Store in sessions map
108
- watchSessions.set(watchId, session);
109
-
110
- // Fire start notifier
111
- fireWatchStartNotifier(conversationId, session);
112
-
113
- // Set timeout for duration expiry
114
- session.timeoutHandle = setTimeout(() => {
115
- session.status = "completing";
116
- session.timeoutHandle = undefined;
117
- log.info(
118
- { watchId, focusArea },
119
- "Watch session duration expired, marking as completing",
120
- );
121
- fireWatchCompletionNotifier(conversationId, session);
122
- }, durationSeconds * 1000);
123
-
124
- log.info(
125
- { watchId, conversationId, focusArea, durationMinutes, intervalSeconds },
126
- "Screen watch session started",
127
- );
128
-
129
- const expectedCaptures = Math.floor(durationSeconds / intervalSeconds);
130
- const content = [
131
- `Screen watch started (watchId: ${watchId})`,
132
- `Focus: ${focusArea}`,
133
- `Duration: ${durationMinutes} minute${durationMinutes !== 1 ? "s" : ""}`,
134
- `Interval: every ${intervalSeconds} seconds`,
135
- `Expected captures: ~${expectedCaptures}`,
136
- `Started at: ${new Date(now).toISOString()}`,
137
- `Expected end: ${new Date(now + durationSeconds * 1000).toISOString()}`,
138
- ].join("\n");
139
-
140
- return { content, isError: false };
141
- }
142
- }
143
-
144
- export const screenWatchTool = new ScreenWatchTool();