@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
@@ -0,0 +1,37 @@
1
+ import type { PlaygroundRouteDeps } from "./deps.js";
2
+
3
+ /**
4
+ * Body code for flag-off playground 404s. Distinct from the generic
5
+ * `NOT_FOUND` code so the Swift `CompactionPlaygroundClient` can route
6
+ * these to `.notAvailable` (toast: "Playground endpoints disabled")
7
+ * rather than `.notFound` (toast: "Conversation not found"). The two
8
+ * cases are otherwise indistinguishable on conv-scoped routes because
9
+ * `assertPlaygroundEnabled` runs *before* the conversation lookup, so a
10
+ * URL-path heuristic on the client misclassifies flag-off as missing-
11
+ * conversation. See `conversation-not-found.ts` for the matching code on
12
+ * the other branch.
13
+ */
14
+ export const PLAYGROUND_DISABLED_CODE = "playground_disabled";
15
+
16
+ /**
17
+ * Defense-in-depth guard every playground route calls first. Returns a 404
18
+ * Response when the `compaction-playground` feature flag is disabled so the
19
+ * entire /playground/* surface is invisible in production regardless of UI
20
+ * gating.
21
+ */
22
+ export function assertPlaygroundEnabled(
23
+ deps: PlaygroundRouteDeps,
24
+ ): Response | null {
25
+ if (!deps.isPlaygroundEnabled()) {
26
+ return Response.json(
27
+ {
28
+ error: {
29
+ code: PLAYGROUND_DISABLED_CODE,
30
+ message: "Compaction playground is not enabled",
31
+ },
32
+ },
33
+ { status: 404 },
34
+ );
35
+ }
36
+ return null;
37
+ }
@@ -0,0 +1,28 @@
1
+ import type { RouteDefinition } from "../../http-router.js";
2
+ import type { PlaygroundRouteDeps } from "./deps.js";
3
+ import { forceCompactRouteDefinitions } from "./force-compact.js";
4
+ import { injectFailuresRouteDefinitions } from "./inject-failures.js";
5
+ import { resetCircuitRouteDefinitions } from "./reset-circuit.js";
6
+ import { seedConversationRouteDefinitions } from "./seed-conversation.js";
7
+ import { seededConversationsRouteDefinitions } from "./seeded-conversations.js";
8
+ import { stateRouteDefinitions } from "./state.js";
9
+
10
+ export type { PlaygroundRouteDeps };
11
+ export { assertPlaygroundEnabled } from "./guard.js";
12
+
13
+ export function playgroundRouteDefinitions(
14
+ deps: PlaygroundRouteDeps,
15
+ ): RouteDefinition[] {
16
+ // Each playground route file exports its own `*RouteDefinitions(deps)`
17
+ // factory; this aggregator spreads the arrays together. Later PRs in the
18
+ // plan append more imports here — keeping it purely additive minimizes
19
+ // conflicts across concurrent playground PRs.
20
+ return [
21
+ ...forceCompactRouteDefinitions(deps),
22
+ ...injectFailuresRouteDefinitions(deps),
23
+ ...resetCircuitRouteDefinitions(deps),
24
+ ...seedConversationRouteDefinitions(deps),
25
+ ...seededConversationsRouteDefinitions(deps),
26
+ ...stateRouteDefinitions(deps),
27
+ ];
28
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * POST /v1/conversations/:id/playground/inject-compaction-failures
3
+ * directly mutates the compaction circuit-breaker state on a conversation.
4
+ *
5
+ * This is a dev-only playground endpoint gated by the
6
+ * `compaction-playground` feature flag. It lets integration tests and the
7
+ * macOS playground UI drive the circuit breaker into interesting states
8
+ * without having to wait for real consecutive summary-LLM failures.
9
+ *
10
+ * When `circuitOpenForMs` is set to a positive number, the endpoint emits a
11
+ * `compaction_circuit_open` event with reason `3_consecutive_failures`
12
+ * (matching the event shape produced by `trackCompactionOutcome` in
13
+ * `conversation-agent-loop.ts`). Passing `circuitOpenForMs: 0` clears the
14
+ * open-until timestamp and emits `compaction_circuit_closed`, mirroring the
15
+ * transition event the daemon emits on the open → closed edge.
16
+ */
17
+
18
+ import { z } from "zod";
19
+
20
+ import { getConfig } from "../../../config/loader.js";
21
+ import { estimatePromptTokens } from "../../../context/token-estimator.js";
22
+ import type { Conversation } from "../../../daemon/conversation.js";
23
+ import { httpError } from "../../http-errors.js";
24
+ import type { RouteDefinition } from "../../http-router.js";
25
+ import { conversationNotFoundResponse } from "./conversation-not-found.js";
26
+ import type { PlaygroundRouteDeps } from "./deps.js";
27
+ import { assertPlaygroundEnabled } from "./guard.js";
28
+
29
+ const InjectBodySchema = z.object({
30
+ consecutiveFailures: z.number().int().min(0).max(10).optional(),
31
+ circuitOpenForMs: z
32
+ .number()
33
+ .int()
34
+ .min(0)
35
+ .max(24 * 60 * 60 * 1000)
36
+ .optional(),
37
+ });
38
+
39
+ export function injectFailuresRouteDefinitions(
40
+ deps: PlaygroundRouteDeps,
41
+ ): RouteDefinition[] {
42
+ return [
43
+ {
44
+ endpoint: "conversations/:id/playground/inject-compaction-failures",
45
+ method: "POST",
46
+ policyKey: "conversations/playground/inject-failures",
47
+ summary:
48
+ "Directly mutate compaction circuit-breaker state (dev-only playground)",
49
+ tags: ["playground"],
50
+ requestBody: InjectBodySchema,
51
+ handler: async ({ req, params }) => {
52
+ const gate = assertPlaygroundEnabled(deps);
53
+ if (gate) return gate;
54
+
55
+ const conversation = await deps.getConversationById(params.id);
56
+ if (!conversation) {
57
+ return conversationNotFoundResponse(params.id);
58
+ }
59
+
60
+ let rawBody: unknown = {};
61
+ try {
62
+ const contentLength = req.headers.get("content-length");
63
+ if (contentLength !== "0" && req.body !== null) {
64
+ rawBody = await req.json();
65
+ }
66
+ } catch {
67
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
68
+ }
69
+
70
+ const parsed = InjectBodySchema.safeParse(rawBody ?? {});
71
+ if (!parsed.success) {
72
+ return httpError("BAD_REQUEST", parsed.error.message, 400);
73
+ }
74
+ const { consecutiveFailures, circuitOpenForMs } = parsed.data;
75
+
76
+ if (consecutiveFailures !== undefined) {
77
+ conversation.consecutiveCompactionFailures = consecutiveFailures;
78
+ }
79
+
80
+ if (circuitOpenForMs !== undefined) {
81
+ if (circuitOpenForMs === 0) {
82
+ // Mirror `trackCompactionOutcome()` (and `reset-circuit.ts`) —
83
+ // emit `compaction_circuit_closed` only on the open→closed
84
+ // transition so clients don't receive a redundant close event
85
+ // when the breaker was already closed.
86
+ if (conversation.compactionCircuitOpenUntil !== null) {
87
+ conversation.compactionCircuitOpenUntil = null;
88
+ conversation.sendToClient({
89
+ type: "compaction_circuit_closed",
90
+ conversationId: conversation.conversationId,
91
+ });
92
+ }
93
+ } else {
94
+ const openUntil = Date.now() + circuitOpenForMs;
95
+ conversation.compactionCircuitOpenUntil = openUntil;
96
+ conversation.sendToClient({
97
+ type: "compaction_circuit_open",
98
+ conversationId: conversation.conversationId,
99
+ reason: "3_consecutive_failures",
100
+ openUntil,
101
+ });
102
+ }
103
+ }
104
+
105
+ return Response.json(buildCompactionState(conversation));
106
+ },
107
+ },
108
+ ];
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Local state-builder — identical in shape to `buildCompactionStateResponse`
113
+ // in `state.ts` and the local copy in `reset-circuit.ts`. A follow-up cleanup
114
+ // can consolidate these onto `state.ts`'s exported helper.
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export interface CompactionStateResponse {
118
+ estimatedInputTokens: number;
119
+ maxInputTokens: number;
120
+ compactThresholdRatio: number;
121
+ thresholdTokens: number;
122
+ messageCount: number;
123
+ contextCompactedMessageCount: number;
124
+ contextCompactedAt: number | null;
125
+ consecutiveCompactionFailures: number;
126
+ compactionCircuitOpenUntil: number | null;
127
+ isCircuitOpen: boolean;
128
+ isCompactionEnabled: boolean;
129
+ }
130
+
131
+ function buildCompactionState(
132
+ conversation: Conversation,
133
+ ): CompactionStateResponse {
134
+ const ctxConfig = getConfig().llm.default.contextWindow;
135
+ const maxInputTokens = ctxConfig.maxInputTokens;
136
+ const compactThresholdRatio = ctxConfig.compactThreshold;
137
+ const isCompactionEnabled = ctxConfig.enabled;
138
+ const thresholdTokens = Math.floor(maxInputTokens * compactThresholdRatio);
139
+
140
+ const messages = conversation.getMessages();
141
+ const estimatedInputTokens = estimatePromptTokens(messages);
142
+ const circuitOpenUntil = conversation.compactionCircuitOpenUntil;
143
+ const isCircuitOpen =
144
+ circuitOpenUntil !== null && Date.now() < circuitOpenUntil;
145
+
146
+ return {
147
+ estimatedInputTokens,
148
+ maxInputTokens,
149
+ compactThresholdRatio,
150
+ thresholdTokens,
151
+ messageCount: messages.length,
152
+ contextCompactedMessageCount: conversation.contextCompactedMessageCount,
153
+ contextCompactedAt: conversation.contextCompactedAt,
154
+ consecutiveCompactionFailures: conversation.consecutiveCompactionFailures,
155
+ compactionCircuitOpenUntil: circuitOpenUntil,
156
+ isCircuitOpen,
157
+ isCompactionEnabled,
158
+ };
159
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * POST /v1/conversations/:id/playground/reset-compaction-circuit
3
+ *
4
+ * Dev-only playground endpoint that clears the compaction circuit-breaker
5
+ * state on a live conversation. Intended for reproducing flows that normally
6
+ * require three real summary-LLM failures to trigger — without this hatch,
7
+ * exercising the "auto-compaction paused" banner requires bespoke fault
8
+ * injection.
9
+ *
10
+ * Behavior:
11
+ * - `consecutiveCompactionFailures` is always zeroed.
12
+ * - `compactionCircuitOpenUntil` is cleared to null only when it was set;
13
+ * the `compaction_circuit_closed` event is emitted on the open→closed
14
+ * transition so the Swift banner dismisses immediately, mirroring the
15
+ * behavior of a successful compaction in `trackCompactionOutcome()`.
16
+ * Calling this endpoint while the breaker is already closed is a no-op
17
+ * on the event channel — it never emits a redundant close event.
18
+ *
19
+ * Guarded by `assertPlaygroundEnabled()` — the route 404s when the
20
+ * `compaction-playground` feature flag is disabled, so the entire surface
21
+ * is invisible in production.
22
+ */
23
+
24
+ import { getConfig } from "../../../config/loader.js";
25
+ import { estimatePromptTokens } from "../../../context/token-estimator.js";
26
+ import type { Conversation } from "../../../daemon/conversation.js";
27
+ import type { RouteDefinition } from "../../http-router.js";
28
+ // Import directly from the source modules (not ./index.js) — index.ts imports
29
+ // this file's `resetCircuitRouteDefinitions`, so pulling its re-exports back
30
+ // through the barrel would create a cycle.
31
+ import { conversationNotFoundResponse } from "./conversation-not-found.js";
32
+ import type { PlaygroundRouteDeps } from "./deps.js";
33
+ import { assertPlaygroundEnabled } from "./guard.js";
34
+
35
+ export function resetCircuitRouteDefinitions(
36
+ deps: PlaygroundRouteDeps,
37
+ ): RouteDefinition[] {
38
+ return [
39
+ {
40
+ endpoint: "conversations/:id/playground/reset-compaction-circuit",
41
+ method: "POST",
42
+ policyKey: "conversations/playground/reset-circuit",
43
+ summary: "Clear compaction circuit-breaker state (dev-only playground)",
44
+ tags: ["playground"],
45
+ handler: async ({ params }) => {
46
+ const gate = assertPlaygroundEnabled(deps);
47
+ if (gate) return gate;
48
+
49
+ const conversation = await deps.getConversationById(params.id);
50
+ if (!conversation) {
51
+ return conversationNotFoundResponse(params.id);
52
+ }
53
+
54
+ conversation.consecutiveCompactionFailures = 0;
55
+ if (conversation.compactionCircuitOpenUntil !== null) {
56
+ conversation.compactionCircuitOpenUntil = null;
57
+ // Mirror `trackCompactionOutcome()` — emit only on the open→closed
58
+ // transition so clients don't receive a redundant close event when
59
+ // the breaker was already closed.
60
+ conversation.sendToClient({
61
+ type: "compaction_circuit_closed",
62
+ conversationId: conversation.conversationId,
63
+ });
64
+ }
65
+
66
+ return Response.json(buildCompactionState(conversation));
67
+ },
68
+ },
69
+ ];
70
+ }
71
+
72
+ /**
73
+ * Local state-builder with the same shape as the (future) shared
74
+ * `CompactionStateResponse`. Sibling PRs (7, 9) carry near-identical copies;
75
+ * PR 9 extracts a consolidated version and this local copy can be deleted at
76
+ * that cleanup step.
77
+ */
78
+ function buildCompactionState(conversation: Conversation): {
79
+ estimatedInputTokens: number;
80
+ maxInputTokens: number;
81
+ compactThresholdRatio: number;
82
+ thresholdTokens: number;
83
+ messageCount: number;
84
+ contextCompactedMessageCount: number;
85
+ contextCompactedAt: number | null;
86
+ consecutiveCompactionFailures: number;
87
+ compactionCircuitOpenUntil: number | null;
88
+ isCircuitOpen: boolean;
89
+ isCompactionEnabled: boolean;
90
+ } {
91
+ const config = getConfig();
92
+ const contextWindow = config.llm.default.contextWindow;
93
+ const messages = conversation.getMessages();
94
+ const estimatedInputTokens = estimatePromptTokens(messages);
95
+ const maxInputTokens = contextWindow.maxInputTokens;
96
+ const compactThresholdRatio = contextWindow.compactThreshold;
97
+ const thresholdTokens = Math.floor(maxInputTokens * compactThresholdRatio);
98
+ const isCircuitOpen =
99
+ conversation.compactionCircuitOpenUntil !== null &&
100
+ Date.now() < conversation.compactionCircuitOpenUntil;
101
+
102
+ return {
103
+ estimatedInputTokens,
104
+ maxInputTokens,
105
+ compactThresholdRatio,
106
+ thresholdTokens,
107
+ messageCount: messages.length,
108
+ contextCompactedMessageCount: conversation.contextCompactedMessageCount,
109
+ contextCompactedAt: conversation.contextCompactedAt,
110
+ consecutiveCompactionFailures: conversation.consecutiveCompactionFailures,
111
+ compactionCircuitOpenUntil: conversation.compactionCircuitOpenUntil,
112
+ isCircuitOpen,
113
+ isCompactionEnabled: contextWindow.enabled,
114
+ };
115
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * POST /v1/playground/seed-conversation
3
+ *
4
+ * Creates a synthetic conversation for compaction testing. Inserts N
5
+ * user/assistant message pairs of roughly `avgTokensPerTurn` tokens each and
6
+ * returns the new conversation id plus an estimated prompt-token count.
7
+ *
8
+ * Seeded conversations are prefixed with `[Playground] ` so other playground
9
+ * endpoints (e.g. seeded-conversations list/delete) can filter by prefix.
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ import { estimatePromptTokens } from "../../../context/token-estimator.js";
15
+ import type { Message } from "../../../providers/types.js";
16
+ import { httpError } from "../../http-errors.js";
17
+ import type { RouteDefinition } from "../../http-router.js";
18
+ import { assertPlaygroundEnabled, type PlaygroundRouteDeps } from "./index.js";
19
+
20
+ /**
21
+ * Title prefix applied to every seeded-playground conversation. Exported so
22
+ * sibling playground endpoints (list/delete) can share the exact string
23
+ * rather than duplicating a literal.
24
+ */
25
+ export const PLAYGROUND_TITLE_PREFIX = "[Playground] ";
26
+
27
+ const SeedBodySchema = z.object({
28
+ turns: z.number().int().positive().max(500),
29
+ avgTokensPerTurn: z
30
+ .number()
31
+ .int()
32
+ .positive()
33
+ .max(5000)
34
+ .optional()
35
+ .default(500),
36
+ title: z.string().trim().max(120).optional(),
37
+ });
38
+
39
+ const LOREM_BASE =
40
+ "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ";
41
+
42
+ export function seedConversationRouteDefinitions(
43
+ deps: PlaygroundRouteDeps,
44
+ ): RouteDefinition[] {
45
+ return [
46
+ {
47
+ endpoint: "playground/seed-conversation",
48
+ method: "POST",
49
+ policyKey: "playground/seed-conversation",
50
+ summary: "Create a synthetic seeded conversation for compaction testing",
51
+ tags: ["playground"],
52
+ requestBody: SeedBodySchema,
53
+ handler: async ({ req }) => {
54
+ const gate = assertPlaygroundEnabled(deps);
55
+ if (gate) return gate;
56
+
57
+ let rawBody: unknown;
58
+ try {
59
+ rawBody = await req.json();
60
+ } catch {
61
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
62
+ }
63
+
64
+ const parsed = SeedBodySchema.safeParse(rawBody);
65
+ if (!parsed.success) {
66
+ return httpError("BAD_REQUEST", parsed.error.message, 400);
67
+ }
68
+ const { turns, avgTokensPerTurn, title } = parsed.data;
69
+
70
+ // Derive effective title: strip an accidental prefix first so a
71
+ // caller passing "[Playground] foo" doesn't end up with
72
+ // "[Playground] [Playground] foo".
73
+ const userSuppliedTitle =
74
+ title && title.length > 0
75
+ ? title
76
+ : new Date().toISOString().slice(0, 19);
77
+ const withoutPrefix = userSuppliedTitle.startsWith(
78
+ PLAYGROUND_TITLE_PREFIX,
79
+ )
80
+ ? userSuppliedTitle.slice(PLAYGROUND_TITLE_PREFIX.length)
81
+ : userSuppliedTitle;
82
+ const effectiveTitle = PLAYGROUND_TITLE_PREFIX + withoutPrefix;
83
+
84
+ const { id: conversationId } =
85
+ await deps.createConversation(effectiveTitle);
86
+
87
+ // Generate lorem-ipsum-style text. ~4 chars/token is the rough
88
+ // approximation the rest of the daemon uses. Paragraph is repeated
89
+ // and sliced to the target byte count, and a "Turn N" prefix keeps
90
+ // each message unique so downstream search/compaction doesn't
91
+ // collapse them.
92
+ const charsPerMessage = avgTokensPerTurn * 4;
93
+
94
+ const messages: Array<{ role: "user" | "assistant"; text: string }> =
95
+ [];
96
+ for (let i = 0; i < turns; i++) {
97
+ const userBase = `Turn ${i + 1} user message: ` + LOREM_BASE;
98
+ const userText = userBase
99
+ .repeat(Math.ceil(charsPerMessage / userBase.length))
100
+ .slice(0, charsPerMessage);
101
+ const asstBase = `Turn ${i + 1} assistant response: ` + LOREM_BASE;
102
+ const asstText = asstBase
103
+ .repeat(Math.ceil(charsPerMessage / asstBase.length))
104
+ .slice(0, charsPerMessage);
105
+ messages.push({ role: "user", text: userText });
106
+ messages.push({ role: "assistant", text: asstText });
107
+ }
108
+
109
+ for (const msg of messages) {
110
+ const contentJson = JSON.stringify([
111
+ { type: "text", text: msg.text },
112
+ ]);
113
+ // Skip memory/vector indexing for seeded synthetic messages — the
114
+ // lorem-ipsum content has no semantic value and would otherwise
115
+ // spam the embedding pipeline (2 embeddings per turn × up to 500
116
+ // turns) and pollute the vector store.
117
+ await deps.addMessage(conversationId, msg.role, contentJson, {
118
+ skipIndexing: true,
119
+ });
120
+ }
121
+
122
+ // Reconstruct the in-memory Message[] shape estimatePromptTokens
123
+ // expects so the returned estimate matches what compaction will
124
+ // see once the conversation is loaded.
125
+ const estimatorMessages: Message[] = messages.map((m) => ({
126
+ role: m.role,
127
+ content: [{ type: "text", text: m.text }],
128
+ }));
129
+ const estimatedTokens = estimatePromptTokens(estimatorMessages);
130
+
131
+ return Response.json({
132
+ conversationId,
133
+ messagesInserted: messages.length,
134
+ estimatedTokens,
135
+ });
136
+ },
137
+ },
138
+ ];
139
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Playground-only list/delete endpoints for conversations seeded by the
3
+ * seed-conversation route. Prefix-gated on the title (every seeded
4
+ * conversation is titled `[Playground] ...`) so a flag-on caller cannot use
5
+ * these routes to list or delete unrelated conversations.
6
+ */
7
+
8
+ import { httpError } from "../../http-errors.js";
9
+ import type { RouteDefinition } from "../../http-router.js";
10
+ import { assertPlaygroundEnabled, type PlaygroundRouteDeps } from "./index.js";
11
+ import { PLAYGROUND_TITLE_PREFIX } from "./seed-conversation.js";
12
+
13
+ export function seededConversationsRouteDefinitions(
14
+ deps: PlaygroundRouteDeps,
15
+ ): RouteDefinition[] {
16
+ return [
17
+ {
18
+ endpoint: "playground/seeded-conversations",
19
+ method: "GET",
20
+ policyKey: "playground/seeded-conversations/list",
21
+ summary: "List conversations created by the seed-conversation endpoint",
22
+ tags: ["playground"],
23
+ handler: async () => {
24
+ const gate = assertPlaygroundEnabled(deps);
25
+ if (gate) return gate;
26
+ const conversations = deps.listConversationsByTitlePrefix(
27
+ PLAYGROUND_TITLE_PREFIX,
28
+ );
29
+ return Response.json({ conversations });
30
+ },
31
+ },
32
+ {
33
+ endpoint: "playground/seeded-conversations/:id",
34
+ method: "DELETE",
35
+ policyKey: "playground/seeded-conversations/delete-one",
36
+ summary: "Delete a single seeded conversation (prefix-gated)",
37
+ tags: ["playground"],
38
+ handler: async ({ params }) => {
39
+ const gate = assertPlaygroundEnabled(deps);
40
+ if (gate) return gate;
41
+
42
+ // The in-memory `Conversation` object does not carry the DB title,
43
+ // and the seeded row may not be loaded into memory at all. Use the
44
+ // prefix-filtered DB listing as the authoritative membership check
45
+ // so we reject attempts to delete arbitrary conversations via this
46
+ // endpoint even when the flag is on.
47
+ const seeded = deps
48
+ .listConversationsByTitlePrefix(PLAYGROUND_TITLE_PREFIX)
49
+ .find((c) => c.id === params.id);
50
+ if (!seeded) {
51
+ return httpError("FORBIDDEN", "Not a playground conversation", 403);
52
+ }
53
+
54
+ const deleted = deps.deleteConversationById(params.id);
55
+ return Response.json({ deletedCount: deleted ? 1 : 0 });
56
+ },
57
+ },
58
+ {
59
+ endpoint: "playground/seeded-conversations",
60
+ method: "DELETE",
61
+ policyKey: "playground/seeded-conversations/delete-all",
62
+ summary: "Delete every seeded playground conversation (prefix-gated)",
63
+ tags: ["playground"],
64
+ handler: async () => {
65
+ const gate = assertPlaygroundEnabled(deps);
66
+ if (gate) return gate;
67
+ const candidates = deps.listConversationsByTitlePrefix(
68
+ PLAYGROUND_TITLE_PREFIX,
69
+ );
70
+ let deletedCount = 0;
71
+ for (const c of candidates) {
72
+ if (deps.deleteConversationById(c.id)) deletedCount++;
73
+ }
74
+ return Response.json({ deletedCount });
75
+ },
76
+ },
77
+ ];
78
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * GET /v1/conversations/:id/playground/compaction-state
3
+ *
4
+ * Read-only view of compaction-relevant state for a conversation. Returns the
5
+ * token estimate, the configured maxInputTokens / compactThreshold, the
6
+ * derived threshold token count, current message count, compaction-progress
7
+ * counters, and circuit-breaker status.
8
+ *
9
+ * The endpoint is gated by the `compaction-playground` feature flag via the
10
+ * shared `assertPlaygroundEnabled` guard — when disabled the whole surface is
11
+ * invisible in production.
12
+ */
13
+
14
+ import { getConfig } from "../../../config/loader.js";
15
+ import { estimatePromptTokens } from "../../../context/token-estimator.js";
16
+ import type { Conversation } from "../../../daemon/conversation.js";
17
+ import type { RouteDefinition } from "../../http-router.js";
18
+ import { conversationNotFoundResponse } from "./conversation-not-found.js";
19
+ import { assertPlaygroundEnabled, type PlaygroundRouteDeps } from "./index.js";
20
+
21
+ /**
22
+ * Build the `CompactionStateResponse` payload used by:
23
+ * - GET ...playground/compaction-state (this file)
24
+ * - POST ...playground/inject-compaction-failures (PR 7)
25
+ * - POST ...playground/reset-compaction-circuit (PR 8)
26
+ *
27
+ * Exported so follow-up cleanup PRs can replace inline copies in PR 7 / PR 8
28
+ * with this canonical implementation.
29
+ */
30
+ export function buildCompactionStateResponse(conversation: Conversation) {
31
+ const messages = conversation.getMessages();
32
+ const estimatedInputTokens = estimatePromptTokens(messages);
33
+ const cfg = getConfig().llm.default.contextWindow;
34
+ const maxInputTokens = cfg.maxInputTokens;
35
+ const compactThresholdRatio = cfg.compactThreshold;
36
+ const thresholdTokens = Math.floor(maxInputTokens * compactThresholdRatio);
37
+ const compactionCircuitOpenUntil = conversation.compactionCircuitOpenUntil;
38
+ return {
39
+ estimatedInputTokens,
40
+ maxInputTokens,
41
+ compactThresholdRatio,
42
+ thresholdTokens,
43
+ messageCount: messages.length,
44
+ contextCompactedMessageCount: conversation.contextCompactedMessageCount,
45
+ contextCompactedAt: conversation.contextCompactedAt,
46
+ consecutiveCompactionFailures: conversation.consecutiveCompactionFailures,
47
+ compactionCircuitOpenUntil,
48
+ isCircuitOpen:
49
+ compactionCircuitOpenUntil !== null &&
50
+ Date.now() < compactionCircuitOpenUntil,
51
+ isCompactionEnabled: cfg.enabled,
52
+ };
53
+ }
54
+
55
+ export function stateRouteDefinitions(
56
+ deps: PlaygroundRouteDeps,
57
+ ): RouteDefinition[] {
58
+ return [
59
+ {
60
+ endpoint: "conversations/:id/playground/compaction-state",
61
+ method: "GET",
62
+ policyKey: "conversations/playground/state",
63
+ summary: "Read current compaction state for a conversation",
64
+ tags: ["playground"],
65
+ handler: async ({ params }) => {
66
+ const gate = assertPlaygroundEnabled(deps);
67
+ if (gate) return gate;
68
+
69
+ const conversation = await deps.getConversationById(params.id);
70
+ if (!conversation) {
71
+ return conversationNotFoundResponse(params.id);
72
+ }
73
+
74
+ return Response.json(buildCompactionStateResponse(conversation));
75
+ },
76
+ },
77
+ ];
78
+ }