@vellumai/assistant 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1068) hide show
  1. package/.dockerignore +27 -0
  2. package/.env.example +22 -0
  3. package/Dockerfile +99 -0
  4. package/Dockerfile.sandbox +5 -0
  5. package/README.md +248 -0
  6. package/bun.lock +1723 -0
  7. package/bunfig.toml +2 -0
  8. package/docs/skills.md +158 -0
  9. package/drizzle/0000_dizzy_maggott.sql +301 -0
  10. package/drizzle/meta/0000_snapshot.json +1999 -0
  11. package/drizzle/meta/_journal.json +13 -0
  12. package/drizzle.config.ts +7 -0
  13. package/eslint.config.mjs +17 -0
  14. package/hook-templates/debug-prompt-logger/hook.json +7 -0
  15. package/hook-templates/debug-prompt-logger/run.sh +68 -0
  16. package/knip.json +9 -0
  17. package/package.json +70 -0
  18. package/scripts/capture-x-graphql.ts +545 -0
  19. package/scripts/ipc/check-contract-inventory.ts +104 -0
  20. package/scripts/ipc/check-swift-decoder-drift.ts +166 -0
  21. package/scripts/ipc/generate-swift.ts +492 -0
  22. package/scripts/test-filesystem-tools.sh +48 -0
  23. package/scripts/test.sh +127 -0
  24. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2485 -0
  25. package/src/__tests__/account-registry.test.ts +245 -0
  26. package/src/__tests__/active-skill-tools.test.ts +378 -0
  27. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  28. package/src/__tests__/agent-loop-thinking.test.ts +81 -0
  29. package/src/__tests__/agent-loop.test.ts +1135 -0
  30. package/src/__tests__/anthropic-provider.test.ts +778 -0
  31. package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
  32. package/src/__tests__/app-bundler.test.ts +292 -0
  33. package/src/__tests__/app-executors.test.ts +613 -0
  34. package/src/__tests__/app-git-history.test.ts +176 -0
  35. package/src/__tests__/app-git-service.test.ts +169 -0
  36. package/src/__tests__/app-open-proxy.test.ts +62 -0
  37. package/src/__tests__/asset-materialize-tool.test.ts +452 -0
  38. package/src/__tests__/asset-search-tool.test.ts +477 -0
  39. package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
  40. package/src/__tests__/assistant-attachments.test.ts +437 -0
  41. package/src/__tests__/assistant-event-hub.test.ts +226 -0
  42. package/src/__tests__/assistant-event.test.ts +123 -0
  43. package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
  44. package/src/__tests__/attachments-store.test.ts +476 -0
  45. package/src/__tests__/attachments.test.ts +134 -0
  46. package/src/__tests__/audit-log-rotation.test.ts +154 -0
  47. package/src/__tests__/browser-fill-credential.test.ts +309 -0
  48. package/src/__tests__/browser-manager.test.ts +203 -0
  49. package/src/__tests__/browser-runtime-check.test.ts +55 -0
  50. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +68 -0
  51. package/src/__tests__/browser-skill-endstate.test.ts +195 -0
  52. package/src/__tests__/bundle-scanner.test.ts +313 -0
  53. package/src/__tests__/call-bridge.test.ts +517 -0
  54. package/src/__tests__/call-constants.test.ts +40 -0
  55. package/src/__tests__/call-domain.test.ts +163 -0
  56. package/src/__tests__/call-orchestrator.test.ts +625 -0
  57. package/src/__tests__/call-recovery.test.ts +518 -0
  58. package/src/__tests__/call-routes-http.test.ts +699 -0
  59. package/src/__tests__/call-state-machine.test.ts +143 -0
  60. package/src/__tests__/call-state.test.ts +174 -0
  61. package/src/__tests__/call-store.test.ts +691 -0
  62. package/src/__tests__/channel-approval-routes.test.ts +2356 -0
  63. package/src/__tests__/channel-approval.test.ts +299 -0
  64. package/src/__tests__/channel-approvals.test.ts +521 -0
  65. package/src/__tests__/channel-delivery-store.test.ts +447 -0
  66. package/src/__tests__/channel-guardian.test.ts +1005 -0
  67. package/src/__tests__/checker.test.ts +3519 -0
  68. package/src/__tests__/clarification-resolver.test.ts +159 -0
  69. package/src/__tests__/classifier.test.ts +67 -0
  70. package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
  71. package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
  72. package/src/__tests__/cli-discover.test.ts +85 -0
  73. package/src/__tests__/cli.test.ts +26 -0
  74. package/src/__tests__/clipboard.test.ts +80 -0
  75. package/src/__tests__/commit-guarantee.test.ts +335 -0
  76. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  77. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  78. package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
  79. package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
  80. package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
  81. package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
  82. package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
  83. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
  84. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
  85. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
  86. package/src/__tests__/computer-use-tools.test.ts +250 -0
  87. package/src/__tests__/config-schema.test.ts +1462 -0
  88. package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
  89. package/src/__tests__/conflict-policy.test.ts +121 -0
  90. package/src/__tests__/conflict-store.test.ts +332 -0
  91. package/src/__tests__/connection-policy.test.ts +102 -0
  92. package/src/__tests__/contacts-tools.test.ts +331 -0
  93. package/src/__tests__/context-memory-e2e.test.ts +434 -0
  94. package/src/__tests__/context-token-estimator.test.ts +135 -0
  95. package/src/__tests__/context-window-manager.test.ts +376 -0
  96. package/src/__tests__/contradiction-checker.test.ts +314 -0
  97. package/src/__tests__/conversation-store.test.ts +612 -0
  98. package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
  99. package/src/__tests__/credential-broker-server-use.test.ts +554 -0
  100. package/src/__tests__/credential-broker.test.ts +167 -0
  101. package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
  102. package/src/__tests__/credential-metadata-store.test.ts +779 -0
  103. package/src/__tests__/credential-policy-validate.test.ts +121 -0
  104. package/src/__tests__/credential-resolve.test.ts +328 -0
  105. package/src/__tests__/credential-security-e2e.test.ts +352 -0
  106. package/src/__tests__/credential-security-invariants.test.ts +583 -0
  107. package/src/__tests__/credential-selection.test.ts +354 -0
  108. package/src/__tests__/credential-vault-unit.test.ts +780 -0
  109. package/src/__tests__/credential-vault.test.ts +852 -0
  110. package/src/__tests__/daemon-assistant-events.test.ts +164 -0
  111. package/src/__tests__/daemon-server-session-init.test.ts +522 -0
  112. package/src/__tests__/date-context.test.ts +373 -0
  113. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  114. package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
  115. package/src/__tests__/diff.test.ts +121 -0
  116. package/src/__tests__/domain-normalize.test.ts +112 -0
  117. package/src/__tests__/domain-policy.test.ts +124 -0
  118. package/src/__tests__/doordash-client.test.ts +186 -0
  119. package/src/__tests__/doordash-session.test.ts +152 -0
  120. package/src/__tests__/dynamic-page-surface.test.ts +91 -0
  121. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
  122. package/src/__tests__/edit-engine.test.ts +180 -0
  123. package/src/__tests__/elevenlabs-client.test.ts +271 -0
  124. package/src/__tests__/email-cli.test.ts +283 -0
  125. package/src/__tests__/encrypted-store.test.ts +332 -0
  126. package/src/__tests__/entity-extractor.test.ts +190 -0
  127. package/src/__tests__/ephemeral-permissions.test.ts +362 -0
  128. package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
  129. package/src/__tests__/event-bus.test.ts +222 -0
  130. package/src/__tests__/file-edit-tool.test.ts +122 -0
  131. package/src/__tests__/file-ops-service.test.ts +330 -0
  132. package/src/__tests__/file-read-tool.test.ts +75 -0
  133. package/src/__tests__/file-write-tool.test.ts +113 -0
  134. package/src/__tests__/filesystem-tools.test.ts +579 -0
  135. package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
  136. package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
  137. package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
  138. package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
  139. package/src/__tests__/followup-tools.test.ts +303 -0
  140. package/src/__tests__/forbidden-legacy-symbols.test.ts +71 -0
  141. package/src/__tests__/fuzzy-match-property.test.ts +216 -0
  142. package/src/__tests__/fuzzy-match.test.ts +138 -0
  143. package/src/__tests__/gateway-only-enforcement.test.ts +631 -0
  144. package/src/__tests__/gemini-image-service.test.ts +261 -0
  145. package/src/__tests__/gemini-provider.test.ts +651 -0
  146. package/src/__tests__/get-weather.test.ts +318 -0
  147. package/src/__tests__/gmail-integration.test.ts +73 -0
  148. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
  149. package/src/__tests__/handlers-cu-observation-blob.test.ts +352 -0
  150. package/src/__tests__/handlers-ipc-blob-probe.test.ts +191 -0
  151. package/src/__tests__/handlers-slack-config.test.ts +200 -0
  152. package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
  153. package/src/__tests__/handlers-telegram-config.test.ts +968 -0
  154. package/src/__tests__/handlers-twilio-config.test.ts +659 -0
  155. package/src/__tests__/handlers-twitter-config.test.ts +858 -0
  156. package/src/__tests__/headless-browser-interactions.test.ts +536 -0
  157. package/src/__tests__/headless-browser-navigate.test.ts +211 -0
  158. package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
  159. package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
  160. package/src/__tests__/history-repair-observability.test.ts +56 -0
  161. package/src/__tests__/history-repair.test.ts +510 -0
  162. package/src/__tests__/home-base-bootstrap.test.ts +82 -0
  163. package/src/__tests__/hooks-blocking.test.ts +128 -0
  164. package/src/__tests__/hooks-cli.test.ts +144 -0
  165. package/src/__tests__/hooks-config.test.ts +93 -0
  166. package/src/__tests__/hooks-discovery.test.ts +199 -0
  167. package/src/__tests__/hooks-integration.test.ts +189 -0
  168. package/src/__tests__/hooks-manager.test.ts +187 -0
  169. package/src/__tests__/hooks-runner.test.ts +182 -0
  170. package/src/__tests__/hooks-settings.test.ts +154 -0
  171. package/src/__tests__/hooks-templates.test.ts +137 -0
  172. package/src/__tests__/hooks-ts-runner.test.ts +125 -0
  173. package/src/__tests__/hooks-watch.test.ts +100 -0
  174. package/src/__tests__/host-file-edit-tool.test.ts +228 -0
  175. package/src/__tests__/host-file-read-tool.test.ts +123 -0
  176. package/src/__tests__/host-file-write-tool.test.ts +136 -0
  177. package/src/__tests__/host-shell-tool.test.ts +562 -0
  178. package/src/__tests__/ingress-reconcile.test.ts +581 -0
  179. package/src/__tests__/ingress-url-consistency.test.ts +214 -0
  180. package/src/__tests__/intent-routing.test.ts +259 -0
  181. package/src/__tests__/ipc-blob-store.test.ts +315 -0
  182. package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
  183. package/src/__tests__/ipc-contract.test.ts +74 -0
  184. package/src/__tests__/ipc-protocol.test.ts +113 -0
  185. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  186. package/src/__tests__/ipc-snapshot.test.ts +1769 -0
  187. package/src/__tests__/ipc-validate.test.ts +407 -0
  188. package/src/__tests__/key-migration.test.ts +206 -0
  189. package/src/__tests__/keychain.test.ts +258 -0
  190. package/src/__tests__/llm-usage-store.test.ts +221 -0
  191. package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
  192. package/src/__tests__/managed-store.test.ts +608 -0
  193. package/src/__tests__/media-generate-image.test.ts +238 -0
  194. package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
  195. package/src/__tests__/media-visibility-policy.test.ts +141 -0
  196. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +235 -0
  197. package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
  198. package/src/__tests__/memory-query-builder.test.ts +59 -0
  199. package/src/__tests__/memory-recall-quality.test.ts +846 -0
  200. package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
  201. package/src/__tests__/memory-regressions.test.ts +4435 -0
  202. package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
  203. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  204. package/src/__tests__/migration-cli-flows.test.ts +169 -0
  205. package/src/__tests__/migration-ordering.test.ts +249 -0
  206. package/src/__tests__/mock-signup-server.test.ts +528 -0
  207. package/src/__tests__/oauth-callback-registry.test.ts +92 -0
  208. package/src/__tests__/oauth2-gateway-transport.test.ts +285 -0
  209. package/src/__tests__/onboarding-starter-tasks.test.ts +176 -0
  210. package/src/__tests__/onboarding-template-contract.test.ts +58 -0
  211. package/src/__tests__/openai-provider.test.ts +753 -0
  212. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  213. package/src/__tests__/parser.test.ts +472 -0
  214. package/src/__tests__/path-classifier.test.ts +73 -0
  215. package/src/__tests__/path-policy.test.ts +435 -0
  216. package/src/__tests__/platform-move-helper.test.ts +99 -0
  217. package/src/__tests__/platform-socket-path.test.ts +52 -0
  218. package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
  219. package/src/__tests__/platform.test.ts +131 -0
  220. package/src/__tests__/playbook-execution.test.ts +502 -0
  221. package/src/__tests__/playbook-tools.test.ts +340 -0
  222. package/src/__tests__/prebuilt-home-base-seed.test.ts +75 -0
  223. package/src/__tests__/pricing.test.ts +256 -0
  224. package/src/__tests__/profile-compiler.test.ts +374 -0
  225. package/src/__tests__/provider-commit-message-generator.test.ts +342 -0
  226. package/src/__tests__/provider-registry-ollama.test.ts +16 -0
  227. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  228. package/src/__tests__/proxy-approval-callback.test.ts +601 -0
  229. package/src/__tests__/public-ingress-urls.test.ts +256 -0
  230. package/src/__tests__/qdrant-manager.test.ts +267 -0
  231. package/src/__tests__/ratelimit.test.ts +297 -0
  232. package/src/__tests__/recurrence-engine-rruleset.test.ts +175 -0
  233. package/src/__tests__/recurrence-engine.test.ts +78 -0
  234. package/src/__tests__/recurrence-types.test.ts +79 -0
  235. package/src/__tests__/registry.test.ts +494 -0
  236. package/src/__tests__/relay-server.test.ts +688 -0
  237. package/src/__tests__/reminder-store.test.ts +223 -0
  238. package/src/__tests__/reminder.test.ts +229 -0
  239. package/src/__tests__/request-file-tool.test.ts +158 -0
  240. package/src/__tests__/run-orchestrator-assistant-events.test.ts +227 -0
  241. package/src/__tests__/run-orchestrator.test.ts +425 -0
  242. package/src/__tests__/runtime-attachment-metadata.test.ts +189 -0
  243. package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
  244. package/src/__tests__/runtime-events-sse.test.ts +162 -0
  245. package/src/__tests__/runtime-runs-http.test.ts +438 -0
  246. package/src/__tests__/runtime-runs.test.ts +260 -0
  247. package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
  248. package/src/__tests__/sandbox-host-parity.test.ts +950 -0
  249. package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
  250. package/src/__tests__/schedule-store.test.ts +484 -0
  251. package/src/__tests__/schedule-tools.test.ts +783 -0
  252. package/src/__tests__/scheduler-recurrence.test.ts +430 -0
  253. package/src/__tests__/script-proxy-certs.test.ts +90 -0
  254. package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
  255. package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
  256. package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
  257. package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
  258. package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
  259. package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
  260. package/src/__tests__/script-proxy-policy.test.ts +310 -0
  261. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
  262. package/src/__tests__/script-proxy-router.test.ts +180 -0
  263. package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
  264. package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
  265. package/src/__tests__/secret-allowlist.test.ts +230 -0
  266. package/src/__tests__/secret-ingress-handler.test.ts +110 -0
  267. package/src/__tests__/secret-onetime-send.test.ts +130 -0
  268. package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
  269. package/src/__tests__/secret-response-routing.test.ts +93 -0
  270. package/src/__tests__/secret-scanner-executor.test.ts +348 -0
  271. package/src/__tests__/secret-scanner.test.ts +900 -0
  272. package/src/__tests__/secure-keys.test.ts +323 -0
  273. package/src/__tests__/server-history-render.test.ts +431 -0
  274. package/src/__tests__/session-abort-tool-results.test.ts +240 -0
  275. package/src/__tests__/session-conflict-gate.test.ts +1136 -0
  276. package/src/__tests__/session-error.test.ts +369 -0
  277. package/src/__tests__/session-evictor.test.ts +188 -0
  278. package/src/__tests__/session-init.benchmark.test.ts +465 -0
  279. package/src/__tests__/session-load-history-repair.test.ts +222 -0
  280. package/src/__tests__/session-pre-run-repair.test.ts +213 -0
  281. package/src/__tests__/session-process-bridge.test.ts +242 -0
  282. package/src/__tests__/session-profile-injection.test.ts +444 -0
  283. package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
  284. package/src/__tests__/session-queue.test.ts +1535 -0
  285. package/src/__tests__/session-runtime-assembly.test.ts +476 -0
  286. package/src/__tests__/session-runtime-workspace.test.ts +183 -0
  287. package/src/__tests__/session-skill-tools.test.ts +2431 -0
  288. package/src/__tests__/session-slash-known.test.ts +368 -0
  289. package/src/__tests__/session-slash-queue.test.ts +288 -0
  290. package/src/__tests__/session-slash-unknown.test.ts +271 -0
  291. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  292. package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
  293. package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
  294. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
  295. package/src/__tests__/session-undo.test.ts +75 -0
  296. package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
  297. package/src/__tests__/session-workspace-injection.test.ts +327 -0
  298. package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
  299. package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
  300. package/src/__tests__/shell-credential-ref.test.ts +187 -0
  301. package/src/__tests__/shell-identity.test.ts +256 -0
  302. package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
  303. package/src/__tests__/shell-parser-property.test.ts +433 -0
  304. package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
  305. package/src/__tests__/signup-e2e.test.ts +353 -0
  306. package/src/__tests__/size-guard.test.ts +117 -0
  307. package/src/__tests__/skill-include-graph.test.ts +303 -0
  308. package/src/__tests__/skill-load-tool.test.ts +409 -0
  309. package/src/__tests__/skill-projection.benchmark.test.ts +338 -0
  310. package/src/__tests__/skill-script-runner-host.test.ts +489 -0
  311. package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
  312. package/src/__tests__/skill-script-runner.test.ts +159 -0
  313. package/src/__tests__/skill-tool-factory.test.ts +252 -0
  314. package/src/__tests__/skill-tool-manifest.test.ts +658 -0
  315. package/src/__tests__/skill-version-hash.test.ts +182 -0
  316. package/src/__tests__/skills.test.ts +680 -0
  317. package/src/__tests__/slash-commands-catalog.test.ts +86 -0
  318. package/src/__tests__/slash-commands-parser.test.ts +119 -0
  319. package/src/__tests__/slash-commands-resolver.test.ts +193 -0
  320. package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
  321. package/src/__tests__/speaker-identification.test.ts +52 -0
  322. package/src/__tests__/starter-bundle.test.ts +136 -0
  323. package/src/__tests__/starter-task-flow.test.ts +143 -0
  324. package/src/__tests__/subagent-manager-notify.test.ts +404 -0
  325. package/src/__tests__/subagent-tools.test.ts +801 -0
  326. package/src/__tests__/subagent-types.test.ts +78 -0
  327. package/src/__tests__/swarm-orchestrator.test.ts +428 -0
  328. package/src/__tests__/swarm-plan-validator.test.ts +330 -0
  329. package/src/__tests__/swarm-recursion.test.ts +165 -0
  330. package/src/__tests__/swarm-router-planner.test.ts +208 -0
  331. package/src/__tests__/swarm-session-integration.test.ts +274 -0
  332. package/src/__tests__/swarm-tool.test.ts +145 -0
  333. package/src/__tests__/swarm-worker-backend.test.ts +129 -0
  334. package/src/__tests__/swarm-worker-runner.test.ts +272 -0
  335. package/src/__tests__/system-prompt.test.ts +439 -0
  336. package/src/__tests__/task-compiler.test.ts +284 -0
  337. package/src/__tests__/task-management-tools.test.ts +936 -0
  338. package/src/__tests__/task-runner.test.ts +216 -0
  339. package/src/__tests__/task-scheduler.test.ts +217 -0
  340. package/src/__tests__/task-tools.test.ts +595 -0
  341. package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
  342. package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
  343. package/src/__tests__/terminal-sandbox.test.ts +202 -0
  344. package/src/__tests__/terminal-tools.test.ts +840 -0
  345. package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
  346. package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
  347. package/src/__tests__/tool-audit-listener.test.ts +113 -0
  348. package/src/__tests__/tool-domain-event-publisher.test.ts +253 -0
  349. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  350. package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
  351. package/src/__tests__/tool-executor-redaction.test.ts +289 -0
  352. package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
  353. package/src/__tests__/tool-executor.test.ts +1989 -0
  354. package/src/__tests__/tool-metrics-listener.test.ts +225 -0
  355. package/src/__tests__/tool-notification-listener.test.ts +49 -0
  356. package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
  357. package/src/__tests__/tool-policy.test.ts +54 -0
  358. package/src/__tests__/tool-profiling-listener.test.ts +268 -0
  359. package/src/__tests__/tool-result-truncation.test.ts +217 -0
  360. package/src/__tests__/tool-trace-listener.test.ts +226 -0
  361. package/src/__tests__/top-level-renderer.test.ts +121 -0
  362. package/src/__tests__/top-level-scanner.test.ts +141 -0
  363. package/src/__tests__/trace-emitter.test.ts +173 -0
  364. package/src/__tests__/trust-store.test.ts +1605 -0
  365. package/src/__tests__/turn-commit.test.ts +554 -0
  366. package/src/__tests__/twilio-provider.test.ts +329 -0
  367. package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
  368. package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
  369. package/src/__tests__/twilio-routes.test.ts +577 -0
  370. package/src/__tests__/twitter-auth-handler.test.ts +667 -0
  371. package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
  372. package/src/__tests__/twitter-cli-routing.test.ts +252 -0
  373. package/src/__tests__/twitter-oauth-client.test.ts +209 -0
  374. package/src/__tests__/url-safety.test.ts +418 -0
  375. package/src/__tests__/view-image-tool.test.ts +217 -0
  376. package/src/__tests__/weather-skill-regression.test.ts +225 -0
  377. package/src/__tests__/web-fetch.test.ts +869 -0
  378. package/src/__tests__/web-search.test.ts +584 -0
  379. package/src/__tests__/workspace-git-service.test.ts +1153 -0
  380. package/src/__tests__/workspace-heartbeat-service.test.ts +486 -0
  381. package/src/__tests__/workspace-lifecycle.test.ts +292 -0
  382. package/src/__tests__/workspace-policy.test.ts +213 -0
  383. package/src/agent/attachments.ts +35 -0
  384. package/src/agent/loop.ts +500 -0
  385. package/src/agent/message-types.ts +17 -0
  386. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  387. package/src/autonomy/autonomy-resolver.ts +60 -0
  388. package/src/autonomy/autonomy-store.ts +122 -0
  389. package/src/autonomy/disposition-mapper.ts +31 -0
  390. package/src/autonomy/index.ts +11 -0
  391. package/src/autonomy/types.ts +39 -0
  392. package/src/bundler/app-bundler.ts +295 -0
  393. package/src/bundler/bundle-scanner.ts +535 -0
  394. package/src/bundler/bundle-signer.ts +124 -0
  395. package/src/bundler/manifest.ts +21 -0
  396. package/src/bundler/signature-verifier.ts +184 -0
  397. package/src/calls/call-bridge.ts +168 -0
  398. package/src/calls/call-constants.ts +48 -0
  399. package/src/calls/call-domain.ts +430 -0
  400. package/src/calls/call-orchestrator.ts +498 -0
  401. package/src/calls/call-recovery.ts +207 -0
  402. package/src/calls/call-state-machine.ts +68 -0
  403. package/src/calls/call-state.ts +87 -0
  404. package/src/calls/call-store.ts +422 -0
  405. package/src/calls/elevenlabs-client.ts +97 -0
  406. package/src/calls/elevenlabs-config.ts +31 -0
  407. package/src/calls/relay-server.ts +390 -0
  408. package/src/calls/speaker-identification.ts +213 -0
  409. package/src/calls/twilio-config.ts +45 -0
  410. package/src/calls/twilio-provider.ts +263 -0
  411. package/src/calls/twilio-rest.ts +156 -0
  412. package/src/calls/twilio-routes.ts +311 -0
  413. package/src/calls/types.ts +39 -0
  414. package/src/calls/voice-provider.ts +14 -0
  415. package/src/calls/voice-quality.ts +114 -0
  416. package/src/cli/autonomy.ts +188 -0
  417. package/src/cli/config-commands.ts +334 -0
  418. package/src/cli/contacts.ts +149 -0
  419. package/src/cli/core-commands.ts +784 -0
  420. package/src/cli/doordash.ts +1055 -0
  421. package/src/cli/email-guardrails.ts +200 -0
  422. package/src/cli/email.ts +405 -0
  423. package/src/cli/ipc-client.ts +82 -0
  424. package/src/cli/main-screen.tsx +53 -0
  425. package/src/cli/map.ts +270 -0
  426. package/src/cli/twitter.ts +754 -0
  427. package/src/cli.ts +918 -0
  428. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  429. package/src/commands/cc-command-registry.ts +209 -0
  430. package/src/config/bundled-skills/.gitkeep +0 -0
  431. package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
  432. package/src/config/bundled-skills/agentmail/icon.svg +21 -0
  433. package/src/config/bundled-skills/app-builder/SKILL.md +1404 -0
  434. package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
  435. package/src/config/bundled-skills/app-builder/icon.svg +9 -0
  436. package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
  437. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
  438. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
  439. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
  440. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
  441. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
  442. package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
  443. package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
  444. package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
  445. package/src/config/bundled-skills/browser/SKILL.md +28 -0
  446. package/src/config/bundled-skills/browser/TOOLS.json +234 -0
  447. package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
  448. package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
  449. package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
  450. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
  451. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
  452. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
  453. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
  454. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
  455. package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
  456. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
  457. package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
  458. package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
  459. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
  460. package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
  461. package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
  462. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
  463. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
  464. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
  465. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
  466. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
  467. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
  468. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
  469. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
  470. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
  471. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
  472. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
  473. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
  474. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
  475. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  476. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  477. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +57 -0
  478. package/src/config/bundled-skills/contacts/tools/contact-search.ts +60 -0
  479. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +66 -0
  480. package/src/config/bundled-skills/document/SKILL.md +26 -0
  481. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  482. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  483. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  484. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  485. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  486. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  487. package/src/config/bundled-skills/followups/icon.svg +24 -0
  488. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  489. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  490. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  491. package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
  492. package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
  493. package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
  494. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
  495. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
  496. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
  497. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
  498. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
  499. package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
  500. package/src/config/bundled-skills/google-calendar/types.ts +97 -0
  501. package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
  502. package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
  503. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +115 -0
  504. package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
  505. package/src/config/bundled-skills/messaging/SKILL.md +153 -0
  506. package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
  507. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
  508. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
  509. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
  510. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
  511. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
  512. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
  513. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
  514. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
  515. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +125 -0
  516. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
  517. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
  518. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
  519. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
  520. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
  521. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +32 -0
  522. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
  523. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +31 -0
  524. package/src/config/bundled-skills/messaging/tools/shared.ts +76 -0
  525. package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
  526. package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
  527. package/src/config/bundled-skills/phone-calls/SKILL.md +533 -0
  528. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  529. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  530. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +98 -0
  531. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +54 -0
  532. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +76 -0
  533. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +113 -0
  534. package/src/config/bundled-skills/public-ingress/SKILL.md +200 -0
  535. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  536. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  537. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  538. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  539. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  540. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  541. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  542. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  543. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  544. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  545. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  546. package/src/config/bundled-skills/self-upgrade/SKILL.md +68 -0
  547. package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
  548. package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
  549. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  550. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  551. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  552. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  553. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  554. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  555. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  556. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  557. package/src/config/bundled-skills/tasks/TOOLS.json +281 -0
  558. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  559. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  560. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  561. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  562. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  563. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  564. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
  565. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  566. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  567. package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
  568. package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
  569. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
  570. package/src/config/bundled-skills/twitter/SKILL.md +220 -0
  571. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  572. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  573. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  574. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  575. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  576. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  577. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  578. package/src/config/bundled-skills/weather/SKILL.md +37 -0
  579. package/src/config/bundled-skills/weather/TOOLS.json +32 -0
  580. package/src/config/bundled-skills/weather/icon.svg +24 -0
  581. package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
  582. package/src/config/computer-use-prompt.ts +97 -0
  583. package/src/config/defaults.ts +263 -0
  584. package/src/config/loader.ts +339 -0
  585. package/src/config/schema.ts +1436 -0
  586. package/src/config/skill-state.ts +95 -0
  587. package/src/config/skills.ts +972 -0
  588. package/src/config/system-prompt.ts +675 -0
  589. package/src/config/templates/BOOTSTRAP.md +70 -0
  590. package/src/config/templates/IDENTITY.md +25 -0
  591. package/src/config/templates/LOOKS.md +25 -0
  592. package/src/config/templates/SOUL.md +37 -0
  593. package/src/config/templates/USER.md +19 -0
  594. package/src/config/types.ts +42 -0
  595. package/src/config/vellum-skills/chatgpt-import/SKILL.md +24 -0
  596. package/src/config/vellum-skills/chatgpt-import/TOOLS.json +23 -0
  597. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +284 -0
  598. package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
  599. package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
  600. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +199 -0
  601. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +153 -0
  602. package/src/config/vellum-skills/telegram-setup/SKILL.md +143 -0
  603. package/src/config/vellum-skills/twilio-setup/SKILL.md +213 -0
  604. package/src/contacts/contact-store.ts +410 -0
  605. package/src/contacts/index.ts +11 -0
  606. package/src/contacts/types.ts +28 -0
  607. package/src/context/token-estimator.ts +108 -0
  608. package/src/context/tool-result-truncation.ts +128 -0
  609. package/src/context/window-manager.ts +531 -0
  610. package/src/daemon/assistant-attachments.ts +691 -0
  611. package/src/daemon/classifier.ts +110 -0
  612. package/src/daemon/computer-use-session.ts +903 -0
  613. package/src/daemon/connection-policy.ts +41 -0
  614. package/src/daemon/date-context.ts +136 -0
  615. package/src/daemon/handlers/apps.ts +530 -0
  616. package/src/daemon/handlers/browser.ts +54 -0
  617. package/src/daemon/handlers/computer-use.ts +187 -0
  618. package/src/daemon/handlers/config.ts +1517 -0
  619. package/src/daemon/handlers/diagnostics.ts +338 -0
  620. package/src/daemon/handlers/documents.ts +173 -0
  621. package/src/daemon/handlers/home-base.ts +78 -0
  622. package/src/daemon/handlers/identity.ts +127 -0
  623. package/src/daemon/handlers/index.ts +129 -0
  624. package/src/daemon/handlers/misc.ts +331 -0
  625. package/src/daemon/handlers/open-bundle-handler.ts +80 -0
  626. package/src/daemon/handlers/publish.ts +187 -0
  627. package/src/daemon/handlers/sessions.ts +555 -0
  628. package/src/daemon/handlers/shared.ts +570 -0
  629. package/src/daemon/handlers/signing.ts +37 -0
  630. package/src/daemon/handlers/skills.ts +486 -0
  631. package/src/daemon/handlers/subagents.ts +210 -0
  632. package/src/daemon/handlers/twitter-auth.ts +198 -0
  633. package/src/daemon/handlers/work-items.ts +632 -0
  634. package/src/daemon/handlers/workspace-files.ts +75 -0
  635. package/src/daemon/handlers.ts +17 -0
  636. package/src/daemon/history-repair.ts +214 -0
  637. package/src/daemon/ipc-blob-store.ts +231 -0
  638. package/src/daemon/ipc-contract-inventory.json +495 -0
  639. package/src/daemon/ipc-contract-inventory.ts +126 -0
  640. package/src/daemon/ipc-contract.ts +2551 -0
  641. package/src/daemon/ipc-protocol.ts +75 -0
  642. package/src/daemon/ipc-validate.ts +188 -0
  643. package/src/daemon/lifecycle.ts +582 -0
  644. package/src/daemon/main.ts +21 -0
  645. package/src/daemon/media-visibility-policy.ts +57 -0
  646. package/src/daemon/ride-shotgun-handler.ts +309 -0
  647. package/src/daemon/server.ts +1215 -0
  648. package/src/daemon/session-agent-loop.ts +922 -0
  649. package/src/daemon/session-attachments.ts +196 -0
  650. package/src/daemon/session-conflict-gate.ts +184 -0
  651. package/src/daemon/session-dynamic-profile.ts +63 -0
  652. package/src/daemon/session-error.ts +290 -0
  653. package/src/daemon/session-evictor.ts +196 -0
  654. package/src/daemon/session-history.ts +437 -0
  655. package/src/daemon/session-lifecycle.ts +147 -0
  656. package/src/daemon/session-media-retry.ts +147 -0
  657. package/src/daemon/session-memory.ts +212 -0
  658. package/src/daemon/session-messaging.ts +145 -0
  659. package/src/daemon/session-notifiers.ts +193 -0
  660. package/src/daemon/session-process.ts +323 -0
  661. package/src/daemon/session-queue-manager.ts +82 -0
  662. package/src/daemon/session-runtime-assembly.ts +447 -0
  663. package/src/daemon/session-skill-tools.ts +356 -0
  664. package/src/daemon/session-slash.ts +305 -0
  665. package/src/daemon/session-surfaces.ts +702 -0
  666. package/src/daemon/session-tool-setup.ts +523 -0
  667. package/src/daemon/session-usage.ts +72 -0
  668. package/src/daemon/session-workspace.ts +19 -0
  669. package/src/daemon/session.ts +400 -0
  670. package/src/daemon/tls-certs.ts +189 -0
  671. package/src/daemon/trace-emitter.ts +82 -0
  672. package/src/daemon/video-thumbnail.ts +62 -0
  673. package/src/daemon/watch-handler.ts +274 -0
  674. package/src/doordash/client.ts +999 -0
  675. package/src/doordash/queries.ts +1311 -0
  676. package/src/doordash/query-extractor.ts +93 -0
  677. package/src/doordash/session.ts +82 -0
  678. package/src/email/provider.ts +117 -0
  679. package/src/email/providers/agentmail.ts +317 -0
  680. package/src/email/providers/index.ts +58 -0
  681. package/src/email/service.ts +303 -0
  682. package/src/email/types.ts +126 -0
  683. package/src/events/bus.ts +157 -0
  684. package/src/events/domain-events.ts +83 -0
  685. package/src/events/index.ts +18 -0
  686. package/src/events/tool-audit-listener.ts +80 -0
  687. package/src/events/tool-domain-event-publisher.ts +111 -0
  688. package/src/events/tool-metrics-listener.ts +159 -0
  689. package/src/events/tool-notification-listener.ts +17 -0
  690. package/src/events/tool-profiling-listener.ts +158 -0
  691. package/src/events/tool-trace-listener.ts +75 -0
  692. package/src/export/formatter.ts +98 -0
  693. package/src/followups/followup-store.ts +168 -0
  694. package/src/followups/index.ts +10 -0
  695. package/src/followups/types.ts +29 -0
  696. package/src/gallery/default-gallery.ts +795 -0
  697. package/src/gallery/gallery-manifest.ts +24 -0
  698. package/src/home-base/app-link-store.ts +82 -0
  699. package/src/home-base/bootstrap.ts +68 -0
  700. package/src/home-base/prebuilt/index.html +662 -0
  701. package/src/home-base/prebuilt/seed-metadata.json +21 -0
  702. package/src/home-base/prebuilt/seed.ts +112 -0
  703. package/src/home-base/prebuilt-home-base-updater.ts +30 -0
  704. package/src/hooks/cli.ts +163 -0
  705. package/src/hooks/config.ts +88 -0
  706. package/src/hooks/discovery.ts +110 -0
  707. package/src/hooks/manager.ts +124 -0
  708. package/src/hooks/runner.ts +123 -0
  709. package/src/hooks/templates.ts +52 -0
  710. package/src/hooks/types.ts +72 -0
  711. package/src/inbound/public-ingress-urls.ts +123 -0
  712. package/src/index.ts +81 -0
  713. package/src/instrument.ts +60 -0
  714. package/src/logfire.ts +99 -0
  715. package/src/media/gemini-image-service.ts +136 -0
  716. package/src/memory/account-store.ts +108 -0
  717. package/src/memory/admin.ts +211 -0
  718. package/src/memory/app-git-service.ts +295 -0
  719. package/src/memory/app-store.ts +577 -0
  720. package/src/memory/attachments-store.ts +397 -0
  721. package/src/memory/channel-delivery-store.ts +353 -0
  722. package/src/memory/channel-guardian-store.ts +669 -0
  723. package/src/memory/checkpoints.ts +52 -0
  724. package/src/memory/clarification-resolver.ts +298 -0
  725. package/src/memory/conflict-intent.ts +157 -0
  726. package/src/memory/conflict-policy.ts +73 -0
  727. package/src/memory/conflict-store.ts +350 -0
  728. package/src/memory/contradiction-checker.ts +358 -0
  729. package/src/memory/conversation-key-store.ts +122 -0
  730. package/src/memory/conversation-store.ts +470 -0
  731. package/src/memory/db.ts +1991 -0
  732. package/src/memory/embedding-backend.ts +229 -0
  733. package/src/memory/embedding-gemini.ts +52 -0
  734. package/src/memory/embedding-local.ts +65 -0
  735. package/src/memory/embedding-ollama.ts +55 -0
  736. package/src/memory/embedding-openai.ts +25 -0
  737. package/src/memory/entity-extractor.ts +474 -0
  738. package/src/memory/external-conversation-store.ts +234 -0
  739. package/src/memory/fingerprint.ts +20 -0
  740. package/src/memory/indexer.ts +156 -0
  741. package/src/memory/items-extractor.ts +461 -0
  742. package/src/memory/job-handlers/backfill.ts +139 -0
  743. package/src/memory/job-handlers/cleanup.ts +58 -0
  744. package/src/memory/job-handlers/conflict.ts +141 -0
  745. package/src/memory/job-handlers/embedding.ts +61 -0
  746. package/src/memory/job-handlers/extraction.ts +123 -0
  747. package/src/memory/job-handlers/index-maintenance.ts +54 -0
  748. package/src/memory/job-handlers/summarization.ts +286 -0
  749. package/src/memory/job-utils.ts +170 -0
  750. package/src/memory/jobs-store.ts +401 -0
  751. package/src/memory/jobs-worker.ts +313 -0
  752. package/src/memory/llm-request-log-store.ts +45 -0
  753. package/src/memory/llm-usage-store.ts +60 -0
  754. package/src/memory/message-content.ts +54 -0
  755. package/src/memory/profile-compiler.ts +160 -0
  756. package/src/memory/published-pages-store.ts +137 -0
  757. package/src/memory/qdrant-client.ts +366 -0
  758. package/src/memory/qdrant-manager.ts +242 -0
  759. package/src/memory/query-builder.ts +45 -0
  760. package/src/memory/retrieval-budget.ts +30 -0
  761. package/src/memory/retriever.ts +653 -0
  762. package/src/memory/runs-store.ts +305 -0
  763. package/src/memory/schema.ts +677 -0
  764. package/src/memory/search/entity.ts +298 -0
  765. package/src/memory/search/formatting.ts +207 -0
  766. package/src/memory/search/lexical.ts +227 -0
  767. package/src/memory/search/ranking.ts +401 -0
  768. package/src/memory/search/semantic.ts +121 -0
  769. package/src/memory/search/types.ts +137 -0
  770. package/src/memory/segmenter.ts +68 -0
  771. package/src/memory/shared-app-links-store.ts +138 -0
  772. package/src/memory/tool-usage-store.ts +62 -0
  773. package/src/messaging/activity-analyzer.ts +76 -0
  774. package/src/messaging/draft-store.ts +88 -0
  775. package/src/messaging/index.ts +3 -0
  776. package/src/messaging/provider-types.ts +80 -0
  777. package/src/messaging/provider.ts +52 -0
  778. package/src/messaging/providers/gmail/adapter.ts +193 -0
  779. package/src/messaging/providers/gmail/client.ts +204 -0
  780. package/src/messaging/providers/gmail/types.ts +90 -0
  781. package/src/messaging/providers/slack/adapter.ts +202 -0
  782. package/src/messaging/providers/slack/client.ts +198 -0
  783. package/src/messaging/providers/slack/types.ts +119 -0
  784. package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
  785. package/src/messaging/providers/telegram-bot/client.ts +104 -0
  786. package/src/messaging/providers/telegram-bot/types.ts +15 -0
  787. package/src/messaging/registry.ts +35 -0
  788. package/src/messaging/style-analyzer.ts +159 -0
  789. package/src/messaging/thread-summarizer.ts +306 -0
  790. package/src/messaging/triage-engine.ts +323 -0
  791. package/src/messaging/types.ts +55 -0
  792. package/src/permissions/checker.ts +640 -0
  793. package/src/permissions/defaults.ts +254 -0
  794. package/src/permissions/prompter.ts +98 -0
  795. package/src/permissions/secret-prompter.ts +114 -0
  796. package/src/permissions/shell-identity.ts +227 -0
  797. package/src/permissions/trust-store.ts +607 -0
  798. package/src/permissions/types.ts +43 -0
  799. package/src/permissions/workspace-policy.ts +114 -0
  800. package/src/playbooks/index.ts +2 -0
  801. package/src/playbooks/playbook-compiler.ts +90 -0
  802. package/src/playbooks/types.ts +55 -0
  803. package/src/providers/anthropic/client.ts +751 -0
  804. package/src/providers/failover.ts +129 -0
  805. package/src/providers/fireworks/client.ts +20 -0
  806. package/src/providers/gemini/client.ts +285 -0
  807. package/src/providers/ollama/client.ts +30 -0
  808. package/src/providers/openai/client.ts +337 -0
  809. package/src/providers/openrouter/client.ts +20 -0
  810. package/src/providers/ratelimit.ts +93 -0
  811. package/src/providers/registry.ts +146 -0
  812. package/src/providers/retry.ts +81 -0
  813. package/src/providers/stream-timeout.ts +38 -0
  814. package/src/providers/types.ts +109 -0
  815. package/src/runtime/assistant-event-hub.ts +157 -0
  816. package/src/runtime/assistant-event.ts +82 -0
  817. package/src/runtime/channel-approval-parser.ts +60 -0
  818. package/src/runtime/channel-approval-types.ts +73 -0
  819. package/src/runtime/channel-approvals.ts +206 -0
  820. package/src/runtime/channel-guardian-service.ts +212 -0
  821. package/src/runtime/gateway-client.ts +58 -0
  822. package/src/runtime/http-server.ts +1076 -0
  823. package/src/runtime/http-types.ts +66 -0
  824. package/src/runtime/routes/app-routes.ts +174 -0
  825. package/src/runtime/routes/attachment-routes.ts +133 -0
  826. package/src/runtime/routes/call-routes.ts +190 -0
  827. package/src/runtime/routes/channel-routes.ts +1404 -0
  828. package/src/runtime/routes/conversation-routes.ts +352 -0
  829. package/src/runtime/routes/events-routes.ts +148 -0
  830. package/src/runtime/routes/run-routes.ts +257 -0
  831. package/src/runtime/routes/secret-routes.ts +76 -0
  832. package/src/runtime/run-orchestrator.ts +330 -0
  833. package/src/schedule/recurrence-engine.ts +162 -0
  834. package/src/schedule/recurrence-types.ts +67 -0
  835. package/src/schedule/schedule-store.ts +506 -0
  836. package/src/schedule/scheduler.ts +171 -0
  837. package/src/security/encrypted-store.ts +238 -0
  838. package/src/security/keychain.ts +252 -0
  839. package/src/security/oauth-callback-registry.ts +66 -0
  840. package/src/security/oauth2.ts +274 -0
  841. package/src/security/redaction.ts +89 -0
  842. package/src/security/secret-allowlist.ts +164 -0
  843. package/src/security/secret-ingress.ts +57 -0
  844. package/src/security/secret-scanner.ts +550 -0
  845. package/src/security/secure-keys.ts +180 -0
  846. package/src/security/token-manager.ts +141 -0
  847. package/src/services/published-app-updater.ts +69 -0
  848. package/src/services/vercel-deploy.ts +73 -0
  849. package/src/skills/active-skill-tools.ts +81 -0
  850. package/src/skills/clawhub.ts +414 -0
  851. package/src/skills/include-graph.ts +146 -0
  852. package/src/skills/managed-store.ts +233 -0
  853. package/src/skills/path-classifier.ts +128 -0
  854. package/src/skills/slash-commands.ts +174 -0
  855. package/src/skills/tool-manifest.ts +165 -0
  856. package/src/skills/version-hash.ts +110 -0
  857. package/src/slack/slack-webhook.ts +61 -0
  858. package/src/subagent/index.ts +19 -0
  859. package/src/subagent/manager.ts +511 -0
  860. package/src/subagent/types.ts +69 -0
  861. package/src/swarm/backend-claude-code.ts +145 -0
  862. package/src/swarm/index.ts +44 -0
  863. package/src/swarm/limits.ts +37 -0
  864. package/src/swarm/orchestrator.ts +279 -0
  865. package/src/swarm/plan-validator.ts +151 -0
  866. package/src/swarm/router-planner.ts +100 -0
  867. package/src/swarm/router-prompts.ts +36 -0
  868. package/src/swarm/synthesizer.ts +62 -0
  869. package/src/swarm/types.ts +62 -0
  870. package/src/swarm/worker-backend.ts +121 -0
  871. package/src/swarm/worker-prompts.ts +79 -0
  872. package/src/swarm/worker-runner.ts +164 -0
  873. package/src/tasks/SPEC.md +139 -0
  874. package/src/tasks/candidate-store.ts +86 -0
  875. package/src/tasks/ephemeral-permissions.ts +48 -0
  876. package/src/tasks/task-compiler.ts +199 -0
  877. package/src/tasks/task-runner.ts +90 -0
  878. package/src/tasks/task-scheduler.ts +21 -0
  879. package/src/tasks/task-store.ts +127 -0
  880. package/src/tasks/tool-sanitizer.ts +36 -0
  881. package/src/tools/apps/definitions.ts +59 -0
  882. package/src/tools/apps/executors.ts +313 -0
  883. package/src/tools/apps/open-proxy.ts +43 -0
  884. package/src/tools/apps/registry.ts +16 -0
  885. package/src/tools/assets/materialize.ts +218 -0
  886. package/src/tools/assets/search.ts +361 -0
  887. package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
  888. package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
  889. package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
  890. package/src/tools/browser/api-map.ts +293 -0
  891. package/src/tools/browser/auth-cache.ts +149 -0
  892. package/src/tools/browser/auth-detector.ts +347 -0
  893. package/src/tools/browser/auto-navigate.ts +270 -0
  894. package/src/tools/browser/browser-execution.ts +980 -0
  895. package/src/tools/browser/browser-handoff.ts +79 -0
  896. package/src/tools/browser/browser-manager.ts +715 -0
  897. package/src/tools/browser/browser-screencast.ts +217 -0
  898. package/src/tools/browser/headless-browser.ts +450 -0
  899. package/src/tools/browser/jit-auth.ts +51 -0
  900. package/src/tools/browser/network-recorder.ts +349 -0
  901. package/src/tools/browser/network-recording-types.ts +49 -0
  902. package/src/tools/browser/recording-store.ts +49 -0
  903. package/src/tools/browser/runtime-check.ts +43 -0
  904. package/src/tools/browser/x-auto-navigate.ts +207 -0
  905. package/src/tools/calls/call-end.ts +67 -0
  906. package/src/tools/calls/call-start.ts +81 -0
  907. package/src/tools/calls/call-status.ts +81 -0
  908. package/src/tools/claude-code/claude-code.ts +428 -0
  909. package/src/tools/computer-use/definitions.ts +443 -0
  910. package/src/tools/computer-use/registry.ts +22 -0
  911. package/src/tools/computer-use/request-computer-control.ts +53 -0
  912. package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
  913. package/src/tools/credentials/account-registry.ts +127 -0
  914. package/src/tools/credentials/broker-types.ts +107 -0
  915. package/src/tools/credentials/broker.ts +372 -0
  916. package/src/tools/credentials/domain-policy.ts +51 -0
  917. package/src/tools/credentials/host-pattern-match.ts +60 -0
  918. package/src/tools/credentials/metadata-store.ts +335 -0
  919. package/src/tools/credentials/policy-types.ts +52 -0
  920. package/src/tools/credentials/policy-validate.ts +80 -0
  921. package/src/tools/credentials/resolve.ts +122 -0
  922. package/src/tools/credentials/selection.ts +159 -0
  923. package/src/tools/credentials/tool-policy.ts +25 -0
  924. package/src/tools/credentials/vault.ts +657 -0
  925. package/src/tools/document/document-tool.ts +92 -0
  926. package/src/tools/document/editor-template.ts +237 -0
  927. package/src/tools/execution-target.ts +21 -0
  928. package/src/tools/execution-timeout.ts +49 -0
  929. package/src/tools/executor.ts +815 -0
  930. package/src/tools/filesystem/edit.ts +127 -0
  931. package/src/tools/filesystem/fuzzy-match.ts +202 -0
  932. package/src/tools/filesystem/read.ts +71 -0
  933. package/src/tools/filesystem/view-image.ts +199 -0
  934. package/src/tools/filesystem/write.ts +79 -0
  935. package/src/tools/followups/followup_create.ts +76 -0
  936. package/src/tools/followups/followup_list.ts +60 -0
  937. package/src/tools/followups/followup_resolve.ts +56 -0
  938. package/src/tools/host-filesystem/edit.ts +125 -0
  939. package/src/tools/host-filesystem/read.ts +80 -0
  940. package/src/tools/host-filesystem/write.ts +76 -0
  941. package/src/tools/host-terminal/cli-discover.ts +180 -0
  942. package/src/tools/host-terminal/host-shell.ts +191 -0
  943. package/src/tools/memory/definitions.ts +69 -0
  944. package/src/tools/memory/handlers.ts +246 -0
  945. package/src/tools/memory/register.ts +66 -0
  946. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  947. package/src/tools/network/domain-normalize.ts +85 -0
  948. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  949. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  950. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  951. package/src/tools/network/script-proxy/certs.ts +237 -0
  952. package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
  953. package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
  954. package/src/tools/network/script-proxy/index.ts +28 -0
  955. package/src/tools/network/script-proxy/logging.ts +196 -0
  956. package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
  957. package/src/tools/network/script-proxy/policy.ts +152 -0
  958. package/src/tools/network/script-proxy/router.ts +60 -0
  959. package/src/tools/network/script-proxy/server.ts +136 -0
  960. package/src/tools/network/script-proxy/session-manager.ts +534 -0
  961. package/src/tools/network/script-proxy/types.ts +125 -0
  962. package/src/tools/network/url-safety.ts +227 -0
  963. package/src/tools/network/web-fetch.ts +713 -0
  964. package/src/tools/network/web-search.ts +296 -0
  965. package/src/tools/policy-context.ts +29 -0
  966. package/src/tools/registry.ts +295 -0
  967. package/src/tools/reminder/reminder-store.ts +148 -0
  968. package/src/tools/reminder/reminder.ts +80 -0
  969. package/src/tools/schedule/create.ts +81 -0
  970. package/src/tools/schedule/delete.ts +28 -0
  971. package/src/tools/schedule/list.ts +69 -0
  972. package/src/tools/schedule/update.ts +97 -0
  973. package/src/tools/shared/filesystem/edit-engine.ts +56 -0
  974. package/src/tools/shared/filesystem/errors.ts +85 -0
  975. package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
  976. package/src/tools/shared/filesystem/format-diff.ts +35 -0
  977. package/src/tools/shared/filesystem/path-policy.ts +125 -0
  978. package/src/tools/shared/filesystem/size-guard.ts +41 -0
  979. package/src/tools/shared/filesystem/types.ts +80 -0
  980. package/src/tools/shared/shell-output.ts +52 -0
  981. package/src/tools/skills/delete-managed.ts +60 -0
  982. package/src/tools/skills/load.ts +139 -0
  983. package/src/tools/skills/sandbox-runner.ts +279 -0
  984. package/src/tools/skills/scaffold-managed.ts +150 -0
  985. package/src/tools/skills/script-contract.ts +6 -0
  986. package/src/tools/skills/skill-script-runner.ts +86 -0
  987. package/src/tools/skills/skill-tool-factory.ts +64 -0
  988. package/src/tools/skills/vellum-catalog.ts +217 -0
  989. package/src/tools/subagent/abort.ts +33 -0
  990. package/src/tools/subagent/message.ts +39 -0
  991. package/src/tools/subagent/read.ts +67 -0
  992. package/src/tools/subagent/spawn.ts +46 -0
  993. package/src/tools/subagent/status.ts +45 -0
  994. package/src/tools/swarm/delegate.ts +183 -0
  995. package/src/tools/system/request-permission.ts +98 -0
  996. package/src/tools/system/version.ts +43 -0
  997. package/src/tools/tasks/index.ts +27 -0
  998. package/src/tools/tasks/task-delete.ts +82 -0
  999. package/src/tools/tasks/task-list.ts +44 -0
  1000. package/src/tools/tasks/task-run.ts +97 -0
  1001. package/src/tools/tasks/task-save.ts +47 -0
  1002. package/src/tools/tasks/work-item-enqueue.ts +234 -0
  1003. package/src/tools/tasks/work-item-list.ts +55 -0
  1004. package/src/tools/tasks/work-item-remove.ts +60 -0
  1005. package/src/tools/tasks/work-item-run.ts +78 -0
  1006. package/src/tools/tasks/work-item-update.ts +114 -0
  1007. package/src/tools/terminal/backends/docker.ts +372 -0
  1008. package/src/tools/terminal/backends/native.ts +190 -0
  1009. package/src/tools/terminal/backends/types.ts +26 -0
  1010. package/src/tools/terminal/evaluate-typescript.ts +275 -0
  1011. package/src/tools/terminal/parser.ts +413 -0
  1012. package/src/tools/terminal/safe-env.ts +37 -0
  1013. package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
  1014. package/src/tools/terminal/sandbox.ts +44 -0
  1015. package/src/tools/terminal/shell.ts +257 -0
  1016. package/src/tools/tool-manifest.ts +198 -0
  1017. package/src/tools/types.ts +176 -0
  1018. package/src/tools/ui-surface/definitions.ts +244 -0
  1019. package/src/tools/ui-surface/registry.ts +14 -0
  1020. package/src/tools/watch/screen-watch.ts +130 -0
  1021. package/src/tools/watch/watch-state.ts +119 -0
  1022. package/src/tools/watcher/create.ts +64 -0
  1023. package/src/tools/watcher/delete.ts +27 -0
  1024. package/src/tools/watcher/digest.ts +50 -0
  1025. package/src/tools/watcher/list.ts +60 -0
  1026. package/src/tools/watcher/update.ts +56 -0
  1027. package/src/tools/weather/service.ts +551 -0
  1028. package/src/twitter/client.ts +690 -0
  1029. package/src/twitter/oauth-client.ts +102 -0
  1030. package/src/twitter/router.ts +101 -0
  1031. package/src/twitter/session.ts +91 -0
  1032. package/src/usage/actors.ts +24 -0
  1033. package/src/usage/types.ts +37 -0
  1034. package/src/util/clipboard.ts +33 -0
  1035. package/src/util/content-id.ts +16 -0
  1036. package/src/util/debounce.ts +88 -0
  1037. package/src/util/diff.ts +181 -0
  1038. package/src/util/errors.ts +129 -0
  1039. package/src/util/logger.ts +243 -0
  1040. package/src/util/network-info.ts +47 -0
  1041. package/src/util/platform.ts +632 -0
  1042. package/src/util/pricing.ts +150 -0
  1043. package/src/util/promise-guard.ts +37 -0
  1044. package/src/util/retry.ts +98 -0
  1045. package/src/util/spinner.ts +51 -0
  1046. package/src/util/time.ts +16 -0
  1047. package/src/util/truncate.ts +6 -0
  1048. package/src/util/xml.ts +4 -0
  1049. package/src/version.ts +3 -0
  1050. package/src/watcher/constants.ts +11 -0
  1051. package/src/watcher/engine.ts +199 -0
  1052. package/src/watcher/provider-registry.ts +15 -0
  1053. package/src/watcher/provider-types.ts +48 -0
  1054. package/src/watcher/providers/gmail.ts +198 -0
  1055. package/src/watcher/providers/google-calendar.ts +228 -0
  1056. package/src/watcher/providers/slack.ts +129 -0
  1057. package/src/watcher/watcher-store.ts +419 -0
  1058. package/src/work-items/work-item-runner.ts +171 -0
  1059. package/src/work-items/work-item-store.ts +325 -0
  1060. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  1061. package/src/workspace/commit-message-provider.ts +95 -0
  1062. package/src/workspace/git-service.ts +857 -0
  1063. package/src/workspace/heartbeat-service.ts +345 -0
  1064. package/src/workspace/provider-commit-message-generator.ts +285 -0
  1065. package/src/workspace/top-level-renderer.ts +19 -0
  1066. package/src/workspace/top-level-scanner.ts +41 -0
  1067. package/src/workspace/turn-commit.ts +175 -0
  1068. package/tsconfig.json +21 -0
@@ -0,0 +1,1153 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { execFileSync } from 'node:child_process';
6
+ import {
7
+ WorkspaceGitService,
8
+ getWorkspaceGitService,
9
+ _resetGitServiceRegistry,
10
+ _resetBreaker,
11
+ _getConsecutiveFailures,
12
+ _resetInitBreaker,
13
+ _getInitConsecutiveFailures,
14
+ isDeadlineExpired,
15
+ } from '../workspace/git-service.js';
16
+
17
+ describe('WorkspaceGitService', () => {
18
+ let testDir: string;
19
+
20
+ beforeEach(() => {
21
+ // Create a unique test directory for each test
22
+ testDir = join(tmpdir(), `vellum-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
23
+ mkdirSync(testDir, { recursive: true });
24
+ _resetGitServiceRegistry();
25
+ });
26
+
27
+ afterEach(() => {
28
+ // Clean up test directory
29
+ if (existsSync(testDir)) {
30
+ rmSync(testDir, { recursive: true, force: true });
31
+ }
32
+ });
33
+
34
+ describe('lazy initialization', () => {
35
+ test('initializes git repo on first ensureInitialized call', async () => {
36
+ const service = new WorkspaceGitService(testDir);
37
+
38
+ expect(service.isInitialized()).toBe(false);
39
+
40
+ await service.ensureInitialized();
41
+
42
+ expect(service.isInitialized()).toBe(true);
43
+ expect(existsSync(join(testDir, '.git'))).toBe(true);
44
+ });
45
+
46
+ test('creates .gitignore with proper exclusions', async () => {
47
+ const service = new WorkspaceGitService(testDir);
48
+ await service.ensureInitialized();
49
+
50
+ const gitignorePath = join(testDir, '.gitignore');
51
+ expect(existsSync(gitignorePath)).toBe(true);
52
+
53
+ const content = readFileSync(gitignorePath, 'utf-8');
54
+ expect(content).toContain('data/db/');
55
+ expect(content).toContain('data/qdrant/');
56
+ expect(content).toContain('data/ipc-blobs/');
57
+ expect(content).toContain('*.log');
58
+ expect(content).toContain('*.sock');
59
+ expect(content).toContain('*.pid');
60
+ expect(content).toContain('vellum.sock');
61
+ expect(content).toContain('session-token');
62
+ });
63
+
64
+ test('sets git identity correctly', async () => {
65
+ const service = new WorkspaceGitService(testDir);
66
+ await service.ensureInitialized();
67
+
68
+ const userName = execFileSync('git', ['config', 'user.name'], {
69
+ cwd: testDir,
70
+ encoding: 'utf-8',
71
+ }).trim();
72
+ const userEmail = execFileSync('git', ['config', 'user.email'], {
73
+ cwd: testDir,
74
+ encoding: 'utf-8',
75
+ }).trim();
76
+
77
+ expect(userName).toBe('Vellum Assistant');
78
+ expect(userEmail).toBe('assistant@vellum.ai');
79
+ });
80
+
81
+ test('multiple ensureInitialized calls are idempotent', async () => {
82
+ const service = new WorkspaceGitService(testDir);
83
+
84
+ await service.ensureInitialized();
85
+ await service.ensureInitialized();
86
+ await service.ensureInitialized();
87
+
88
+ expect(service.isInitialized()).toBe(true);
89
+ });
90
+
91
+ test('handles concurrent ensureInitialized calls', async () => {
92
+ const service = new WorkspaceGitService(testDir);
93
+
94
+ // Start multiple initialization calls concurrently
95
+ const promises = [
96
+ service.ensureInitialized(),
97
+ service.ensureInitialized(),
98
+ service.ensureInitialized(),
99
+ ];
100
+
101
+ await Promise.all(promises);
102
+
103
+ expect(service.isInitialized()).toBe(true);
104
+ });
105
+ });
106
+
107
+ describe('initial commit', () => {
108
+ test('creates initial commit for new empty workspace', async () => {
109
+ const service = new WorkspaceGitService(testDir);
110
+ await service.ensureInitialized();
111
+
112
+ const log = execFileSync('git', ['log', '--oneline'], {
113
+ cwd: testDir,
114
+ encoding: 'utf-8',
115
+ });
116
+
117
+ expect(log).toContain('Initial commit: new workspace');
118
+ });
119
+
120
+ test('creates initial commit for existing workspace with files', async () => {
121
+ // Create some files before initializing git
122
+ writeFileSync(join(testDir, 'README.md'), '# Test\n');
123
+ writeFileSync(join(testDir, 'config.json'), '{}');
124
+ mkdirSync(join(testDir, 'subdir'));
125
+ writeFileSync(join(testDir, 'subdir', 'file.txt'), 'content');
126
+
127
+ const service = new WorkspaceGitService(testDir);
128
+ await service.ensureInitialized();
129
+
130
+ const log = execFileSync('git', ['log', '--oneline'], {
131
+ cwd: testDir,
132
+ encoding: 'utf-8',
133
+ });
134
+
135
+ expect(log).toContain('Initial commit: migrated existing workspace');
136
+
137
+ // Verify files were committed
138
+ const files = execFileSync('git', ['ls-files'], {
139
+ cwd: testDir,
140
+ encoding: 'utf-8',
141
+ }).trim().split('\n');
142
+
143
+ expect(files).toContain('.gitignore');
144
+ expect(files).toContain('README.md');
145
+ expect(files).toContain('config.json');
146
+ expect(files).toContain('subdir/file.txt');
147
+ });
148
+
149
+ test('initial commit completes within ensureInitialized', async () => {
150
+ // Create some files before initializing git
151
+ for (let i = 0; i < 10; i++) {
152
+ writeFileSync(join(testDir, `file${i}.txt`), 'content');
153
+ }
154
+
155
+ const service = new WorkspaceGitService(testDir);
156
+ await service.ensureInitialized();
157
+
158
+ // Initial commit should already be done - no need to wait
159
+ const log = execFileSync('git', ['log', '--oneline'], {
160
+ cwd: testDir,
161
+ encoding: 'utf-8',
162
+ });
163
+
164
+ expect(log).toContain('Initial commit: migrated existing workspace');
165
+ });
166
+
167
+ test('initial commit does not race with first commitChanges', async () => {
168
+ // Pre-populate workspace with files (simulating a migrated workspace)
169
+ writeFileSync(join(testDir, 'existing.txt'), 'pre-existing content');
170
+
171
+ const service = new WorkspaceGitService(testDir);
172
+
173
+ // Initialize - the initial commit now happens synchronously within
174
+ // ensureInitialized, so it completes before we can write new files.
175
+ await service.ensureInitialized();
176
+
177
+ // Now write a file AFTER init and commit it
178
+ writeFileSync(join(testDir, 'user-edit.txt'), 'user content');
179
+ await service.commitChanges('User turn 1');
180
+
181
+ // The user's commit (HEAD) should contain user-edit.txt
182
+ const userCommitFiles = execFileSync(
183
+ 'git', ['diff', '--name-only', 'HEAD~1', 'HEAD'],
184
+ { cwd: testDir, encoding: 'utf-8' },
185
+ ).trim();
186
+
187
+ expect(userCommitFiles).toContain('user-edit.txt');
188
+ // user-edit.txt should NOT appear in the initial commit
189
+ expect(userCommitFiles).not.toContain('existing.txt');
190
+
191
+ // The initial commit (HEAD~1) should contain existing.txt and .gitignore
192
+ const initialCommitFiles = execFileSync(
193
+ 'git', ['show', '--name-only', '--pretty=format:', 'HEAD~1'],
194
+ { cwd: testDir, encoding: 'utf-8' },
195
+ ).trim();
196
+
197
+ expect(initialCommitFiles).toContain('existing.txt');
198
+ expect(initialCommitFiles).toContain('.gitignore');
199
+ // The initial commit should NOT contain user-edit.txt
200
+ expect(initialCommitFiles).not.toContain('user-edit.txt');
201
+ });
202
+ });
203
+
204
+ describe('commitChanges', () => {
205
+ test('commits changes with message', async () => {
206
+ const service = new WorkspaceGitService(testDir);
207
+ await service.ensureInitialized();
208
+
209
+ writeFileSync(join(testDir, 'test.txt'), 'hello world');
210
+ await service.commitChanges('Add test file');
211
+
212
+ const log = execFileSync('git', ['log', '--oneline', '-n', '1'], {
213
+ cwd: testDir,
214
+ encoding: 'utf-8',
215
+ });
216
+
217
+ expect(log).toContain('Add test file');
218
+ });
219
+
220
+ test('commits with metadata', async () => {
221
+ const service = new WorkspaceGitService(testDir);
222
+ await service.ensureInitialized();
223
+
224
+ writeFileSync(join(testDir, 'test.txt'), 'content');
225
+ await service.commitChanges('Add file', {
226
+ sessionId: 'session-123',
227
+ timestamp: 1234567890,
228
+ author: 'user@example.com',
229
+ });
230
+
231
+ const message = execFileSync('git', ['log', '-1', '--pretty=%B'], {
232
+ cwd: testDir,
233
+ encoding: 'utf-8',
234
+ });
235
+
236
+ expect(message).toContain('Add file');
237
+ expect(message).toContain('sessionId: "session-123"');
238
+ expect(message).toContain('timestamp: 1234567890');
239
+ expect(message).toContain('author: "user@example.com"');
240
+ });
241
+
242
+ test('commits multiple files at once', async () => {
243
+ const service = new WorkspaceGitService(testDir);
244
+ await service.ensureInitialized();
245
+
246
+ writeFileSync(join(testDir, 'file1.txt'), 'content1');
247
+ writeFileSync(join(testDir, 'file2.txt'), 'content2');
248
+ writeFileSync(join(testDir, 'file3.txt'), 'content3');
249
+
250
+ await service.commitChanges('Add multiple files');
251
+
252
+ const files = execFileSync('git', ['diff', '--name-only', 'HEAD~1', 'HEAD'], {
253
+ cwd: testDir,
254
+ encoding: 'utf-8',
255
+ }).trim().split('\n');
256
+
257
+ expect(files).toContain('file1.txt');
258
+ expect(files).toContain('file2.txt');
259
+ expect(files).toContain('file3.txt');
260
+ });
261
+
262
+ test('allows empty commits', async () => {
263
+ const service = new WorkspaceGitService(testDir);
264
+ await service.ensureInitialized();
265
+
266
+ // Commit without any changes
267
+ await service.commitChanges('Empty commit for checkpoint');
268
+
269
+ const log = execFileSync('git', ['log', '--oneline', '-n', '1'], {
270
+ cwd: testDir,
271
+ encoding: 'utf-8',
272
+ });
273
+
274
+ expect(log).toContain('Empty commit for checkpoint');
275
+ });
276
+ });
277
+
278
+ describe('getStatus', () => {
279
+ test('returns clean status for new workspace', async () => {
280
+ const service = new WorkspaceGitService(testDir);
281
+ await service.ensureInitialized();
282
+
283
+ const status = await service.getStatus();
284
+
285
+ expect(status.clean).toBe(true);
286
+ expect(status.staged).toEqual([]);
287
+ expect(status.modified).toEqual([]);
288
+ expect(status.untracked).toEqual([]);
289
+ });
290
+
291
+ test('detects untracked files', async () => {
292
+ const service = new WorkspaceGitService(testDir);
293
+ await service.ensureInitialized();
294
+
295
+ writeFileSync(join(testDir, 'new-file.txt'), 'content');
296
+
297
+ const status = await service.getStatus();
298
+
299
+ expect(status.clean).toBe(false);
300
+ expect(status.untracked).toContain('new-file.txt');
301
+ });
302
+
303
+ test('detects modified files', async () => {
304
+ const service = new WorkspaceGitService(testDir);
305
+ await service.ensureInitialized();
306
+
307
+ writeFileSync(join(testDir, 'file.txt'), 'original');
308
+ await service.commitChanges('Add file');
309
+
310
+ writeFileSync(join(testDir, 'file.txt'), 'modified');
311
+
312
+ const status = await service.getStatus();
313
+
314
+ expect(status.clean).toBe(false);
315
+ expect(status.modified).toContain('file.txt');
316
+ });
317
+
318
+ test('detects staged files', async () => {
319
+ const service = new WorkspaceGitService(testDir);
320
+ await service.ensureInitialized();
321
+
322
+ writeFileSync(join(testDir, 'file.txt'), 'content');
323
+
324
+ // Manually stage the file
325
+ execFileSync('git', ['add', 'file.txt'], { cwd: testDir });
326
+
327
+ const status = await service.getStatus();
328
+
329
+ expect(status.clean).toBe(false);
330
+ expect(status.staged).toContain('file.txt');
331
+ });
332
+ });
333
+
334
+ describe('mutex locking', () => {
335
+ test('serializes concurrent commit operations', async () => {
336
+ const service = new WorkspaceGitService(testDir);
337
+ await service.ensureInitialized();
338
+
339
+ // Start multiple concurrent commits
340
+ const commits = [];
341
+ for (let i = 0; i < 10; i++) {
342
+ commits.push(
343
+ (async () => {
344
+ writeFileSync(join(testDir, `file${i}.txt`), `content ${i}`);
345
+ await service.commitChanges(`Add file ${i}`);
346
+ })(),
347
+ );
348
+ }
349
+
350
+ await Promise.all(commits);
351
+
352
+ // All commits should have succeeded
353
+ const log = execFileSync('git', ['log', '--oneline'], {
354
+ cwd: testDir,
355
+ encoding: 'utf-8',
356
+ });
357
+
358
+ for (let i = 0; i < 10; i++) {
359
+ expect(log).toContain(`Add file ${i}`);
360
+ }
361
+
362
+ // Count commits (excluding initial commit)
363
+ const commitCount = log.trim().split('\n').length;
364
+ expect(commitCount).toBe(11); // 10 + 1 initial
365
+ });
366
+
367
+ test('serializes concurrent status checks', async () => {
368
+ const service = new WorkspaceGitService(testDir);
369
+ await service.ensureInitialized();
370
+
371
+ // Start multiple concurrent status checks
372
+ const checks = [];
373
+ for (let i = 0; i < 20; i++) {
374
+ checks.push(service.getStatus());
375
+ }
376
+
377
+ const results = await Promise.all(checks);
378
+
379
+ // All should succeed and return consistent results
380
+ for (const status of results) {
381
+ expect(status).toBeDefined();
382
+ expect(status.clean).toBe(true);
383
+ }
384
+ });
385
+ });
386
+
387
+ describe('getWorkspaceGitService singleton', () => {
388
+ test('returns same instance for same workspace', () => {
389
+ const service1 = getWorkspaceGitService(testDir);
390
+ const service2 = getWorkspaceGitService(testDir);
391
+
392
+ expect(service1).toBe(service2);
393
+ });
394
+
395
+ test('returns different instances for different workspaces', () => {
396
+ const testDir2 = join(tmpdir(), `vellum-test-${Date.now()}-other`);
397
+ mkdirSync(testDir2, { recursive: true });
398
+
399
+ try {
400
+ const service1 = getWorkspaceGitService(testDir);
401
+ const service2 = getWorkspaceGitService(testDir2);
402
+
403
+ expect(service1).not.toBe(service2);
404
+ expect(service1.getWorkspaceDir()).toBe(testDir);
405
+ expect(service2.getWorkspaceDir()).toBe(testDir2);
406
+ } finally {
407
+ rmSync(testDir2, { recursive: true, force: true });
408
+ }
409
+ });
410
+ });
411
+
412
+ describe('error handling', () => {
413
+ test('handles invalid workspace directory', async () => {
414
+ const invalidDir = '/nonexistent/path/that/does/not/exist';
415
+ const service = new WorkspaceGitService(invalidDir);
416
+
417
+ await expect(service.ensureInitialized()).rejects.toThrow();
418
+ });
419
+
420
+ test('failed initialization can be retried', async () => {
421
+ // Create a service pointing to a directory that doesn't exist yet
422
+ const retryDir = join(tmpdir(), `vellum-retry-${Date.now()}-${Math.random().toString(36).slice(2)}`);
423
+ const service = new WorkspaceGitService(retryDir);
424
+
425
+ // First attempt: directory doesn't exist, should fail
426
+ await expect(service.ensureInitialized()).rejects.toThrow();
427
+
428
+ // Create the directory so the retry can succeed
429
+ mkdirSync(retryDir, { recursive: true });
430
+
431
+ try {
432
+ // Second attempt: directory now exists, should succeed because
433
+ // the .catch handler cleared initPromise after the first failure
434
+ await service.ensureInitialized();
435
+ expect(service.isInitialized()).toBe(true);
436
+
437
+ // Verify the repo was actually initialized
438
+ const log = execFileSync('git', ['log', '--oneline'], {
439
+ cwd: retryDir,
440
+ encoding: 'utf-8',
441
+ });
442
+ expect(log).toContain('Initial commit');
443
+ } finally {
444
+ rmSync(retryDir, { recursive: true, force: true });
445
+ }
446
+ });
447
+
448
+ test('continues to work after failed operation', async () => {
449
+ const service = new WorkspaceGitService(testDir);
450
+ await service.ensureInitialized();
451
+
452
+ // Try to commit without any changes and without allow-empty
453
+ // (This should succeed with --allow-empty, but let's test recovery)
454
+ writeFileSync(join(testDir, 'test.txt'), 'content');
455
+ await service.commitChanges('Valid commit');
456
+
457
+ // Service should still work
458
+ const status = await service.getStatus();
459
+ expect(status).toBeDefined();
460
+ });
461
+ });
462
+
463
+ describe('existing repo normalization', () => {
464
+ test('existing repo on feature branch auto-switches to main on init', async () => {
465
+ // Set up a pre-existing git repo on a feature branch
466
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
467
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: testDir });
468
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: testDir });
469
+ writeFileSync(join(testDir, 'file.txt'), 'content');
470
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
471
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
472
+ execFileSync('git', ['checkout', '-b', 'feature-branch'], { cwd: testDir });
473
+
474
+ // Verify we're on feature-branch
475
+ const branchBefore = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
476
+ cwd: testDir,
477
+ encoding: 'utf-8',
478
+ }).trim();
479
+ expect(branchBefore).toBe('feature-branch');
480
+
481
+ // Initialize the service — should auto-switch to main
482
+ const service = new WorkspaceGitService(testDir);
483
+ await service.ensureInitialized();
484
+
485
+ const branchAfter = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
486
+ cwd: testDir,
487
+ encoding: 'utf-8',
488
+ }).trim();
489
+ expect(branchAfter).toBe('main');
490
+ });
491
+
492
+ test('detached HEAD recovers to main on init', async () => {
493
+ // Set up a pre-existing git repo then detach HEAD
494
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
495
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: testDir });
496
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: testDir });
497
+ writeFileSync(join(testDir, 'file.txt'), 'content');
498
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
499
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
500
+ // Detach HEAD by checking out the commit hash
501
+ const commitHash = execFileSync('git', ['rev-parse', 'HEAD'], {
502
+ cwd: testDir,
503
+ encoding: 'utf-8',
504
+ }).trim();
505
+ execFileSync('git', ['checkout', commitHash], { cwd: testDir });
506
+
507
+ // Verify we're in detached HEAD
508
+ let isDetached = false;
509
+ try {
510
+ execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], { cwd: testDir });
511
+ } catch {
512
+ isDetached = true;
513
+ }
514
+ expect(isDetached).toBe(true);
515
+
516
+ // Initialize the service — should recover to main
517
+ const service = new WorkspaceGitService(testDir);
518
+ await service.ensureInitialized();
519
+
520
+ const branchAfter = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
521
+ cwd: testDir,
522
+ encoding: 'utf-8',
523
+ }).trim();
524
+ expect(branchAfter).toBe('main');
525
+ });
526
+
527
+ test('existing repo on feature branch with dirty working tree switches to main', async () => {
528
+ // Set up a pre-existing git repo on a feature branch with uncommitted changes.
529
+ // This exercises the --discard-changes fallback in ensureOnMainLocked().
530
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
531
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: testDir });
532
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: testDir });
533
+ writeFileSync(join(testDir, 'file.txt'), 'original content');
534
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
535
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
536
+ execFileSync('git', ['checkout', '-b', 'feature-branch'], { cwd: testDir });
537
+
538
+ // Create uncommitted changes that would block a normal `git switch main`
539
+ writeFileSync(join(testDir, 'file.txt'), 'modified on feature branch');
540
+
541
+ // Verify we're on feature-branch with dirty working tree
542
+ const branchBefore = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
543
+ cwd: testDir,
544
+ encoding: 'utf-8',
545
+ }).trim();
546
+ expect(branchBefore).toBe('feature-branch');
547
+ const statusBefore = execFileSync('git', ['status', '--porcelain'], {
548
+ cwd: testDir,
549
+ encoding: 'utf-8',
550
+ }).trim();
551
+ expect(statusBefore).toContain('file.txt');
552
+
553
+ // Initialize the service — should auto-switch to main despite dirty tree
554
+ const service = new WorkspaceGitService(testDir);
555
+ await service.ensureInitialized();
556
+
557
+ const branchAfter = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
558
+ cwd: testDir,
559
+ encoding: 'utf-8',
560
+ }).trim();
561
+ expect(branchAfter).toBe('main');
562
+ });
563
+
564
+ test('existing repo gets .gitignore rules appended on init', async () => {
565
+ // Set up a pre-existing git repo without our gitignore rules
566
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
567
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: testDir });
568
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: testDir });
569
+ writeFileSync(join(testDir, '.gitignore'), 'node_modules/\n');
570
+ writeFileSync(join(testDir, 'file.txt'), 'content');
571
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
572
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
573
+
574
+ // Verify .gitignore does NOT have our rules yet
575
+ const contentBefore = readFileSync(join(testDir, '.gitignore'), 'utf-8');
576
+ expect(contentBefore).not.toContain('data/db/');
577
+ expect(contentBefore).not.toContain('vellum.sock');
578
+
579
+ // Initialize the service — should append rules
580
+ const service = new WorkspaceGitService(testDir);
581
+ await service.ensureInitialized();
582
+
583
+ const contentAfter = readFileSync(join(testDir, '.gitignore'), 'utf-8');
584
+ expect(contentAfter).toContain('node_modules/'); // original rule preserved
585
+ expect(contentAfter).toContain('data/db/');
586
+ expect(contentAfter).toContain('data/qdrant/');
587
+ expect(contentAfter).toContain('data/ipc-blobs/');
588
+ expect(contentAfter).toContain('*.log');
589
+ expect(contentAfter).toContain('vellum.sock');
590
+ expect(contentAfter).toContain('session-token');
591
+ });
592
+
593
+ test('existing repo with old data/ rule gets it replaced with selective rules', async () => {
594
+ // Set up a pre-existing git repo with the OLD broad data/ rule
595
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
596
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: testDir });
597
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: testDir });
598
+ const oldGitignore = '# Runtime state - excluded from git tracking\ndata/\nlogs/\n*.log\n*.sock\n*.pid\n*.sqlite\n*.sqlite-journal\n*.sqlite-wal\n*.sqlite-shm\n*.db\n*.db-journal\n*.db-wal\n*.db-shm\nvellum.sock\nvellum.pid\nsession-token\nhttp-token\n';
599
+ writeFileSync(join(testDir, '.gitignore'), oldGitignore);
600
+ writeFileSync(join(testDir, 'file.txt'), 'content');
601
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
602
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
603
+
604
+ // Verify the old broad rule is present
605
+ const contentBefore = readFileSync(join(testDir, '.gitignore'), 'utf-8');
606
+ expect(contentBefore).toContain('data/\n');
607
+
608
+ // Initialize the service — should migrate the gitignore
609
+ const service = new WorkspaceGitService(testDir);
610
+ await service.ensureInitialized();
611
+
612
+ const contentAfter = readFileSync(join(testDir, '.gitignore'), 'utf-8');
613
+
614
+ // Old broad rule should be removed
615
+ expect(contentAfter).not.toMatch(/^data\/$/m);
616
+
617
+ // New selective rules should be present
618
+ expect(contentAfter).toContain('data/db/');
619
+ expect(contentAfter).toContain('data/qdrant/');
620
+ expect(contentAfter).toContain('data/ipc-blobs/');
621
+
622
+ // Other existing rules should be preserved
623
+ expect(contentAfter).toContain('logs/');
624
+ expect(contentAfter).toContain('*.log');
625
+ expect(contentAfter).toContain('vellum.sock');
626
+ });
627
+
628
+ test('existing repo gets local identity set on init', async () => {
629
+ // Set up a pre-existing git repo with a different identity
630
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
631
+ execFileSync('git', ['config', 'user.name', 'Old Name'], { cwd: testDir });
632
+ execFileSync('git', ['config', 'user.email', 'old@example.com'], { cwd: testDir });
633
+ writeFileSync(join(testDir, 'file.txt'), 'content');
634
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
635
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
636
+
637
+ // Initialize the service — should set identity
638
+ const service = new WorkspaceGitService(testDir);
639
+ await service.ensureInitialized();
640
+
641
+ const userName = execFileSync('git', ['config', 'user.name'], {
642
+ cwd: testDir,
643
+ encoding: 'utf-8',
644
+ }).trim();
645
+ const userEmail = execFileSync('git', ['config', 'user.email'], {
646
+ cwd: testDir,
647
+ encoding: 'utf-8',
648
+ }).trim();
649
+
650
+ expect(userName).toBe('Vellum Assistant');
651
+ expect(userEmail).toBe('assistant@vellum.ai');
652
+ });
653
+
654
+ test('existing repo with correct config is idempotent', async () => {
655
+ // Set up a repo that already has everything configured correctly
656
+ execFileSync('git', ['init', '-b', 'main'], { cwd: testDir });
657
+ execFileSync('git', ['config', 'user.name', 'Vellum Assistant'], { cwd: testDir });
658
+ execFileSync('git', ['config', 'user.email', 'assistant@vellum.ai'], { cwd: testDir });
659
+ const gitignoreContent = '# Runtime state - excluded from git tracking\ndata/db/\ndata/qdrant/\ndata/ipc-blobs/\nlogs/\n*.log\n*.sock\n*.pid\n*.sqlite\n*.sqlite-journal\n*.sqlite-wal\n*.sqlite-shm\n*.db\n*.db-journal\n*.db-wal\n*.db-shm\nvellum.sock\nvellum.pid\nsession-token\nhttp-token\n';
660
+ writeFileSync(join(testDir, '.gitignore'), gitignoreContent);
661
+ writeFileSync(join(testDir, 'file.txt'), 'content');
662
+ execFileSync('git', ['add', '-A'], { cwd: testDir });
663
+ execFileSync('git', ['commit', '-m', 'init'], { cwd: testDir });
664
+
665
+ const gitignoreBefore = readFileSync(join(testDir, '.gitignore'), 'utf-8');
666
+
667
+ // Initialize the service — should be a no-op
668
+ const service = new WorkspaceGitService(testDir);
669
+ await service.ensureInitialized();
670
+
671
+ // Verify nothing changed
672
+ const gitignoreAfter = readFileSync(join(testDir, '.gitignore'), 'utf-8');
673
+ expect(gitignoreAfter).toBe(gitignoreBefore);
674
+
675
+ const userName = execFileSync('git', ['config', 'user.name'], {
676
+ cwd: testDir,
677
+ encoding: 'utf-8',
678
+ }).trim();
679
+ expect(userName).toBe('Vellum Assistant');
680
+
681
+ const branch = execFileSync('git', ['symbolic-ref', '--short', 'HEAD'], {
682
+ cwd: testDir,
683
+ encoding: 'utf-8',
684
+ }).trim();
685
+ expect(branch).toBe('main');
686
+
687
+ // No errors, no duplicate rules
688
+ const ruleCount = (gitignoreAfter.match(/data\/db\//g) || []).length;
689
+ expect(ruleCount).toBe(1);
690
+ });
691
+ });
692
+
693
+ describe('gitignore behavior', () => {
694
+ test('ignores data/db/ but tracks other data/ subdirectories', async () => {
695
+ const service = new WorkspaceGitService(testDir);
696
+ await service.ensureInitialized();
697
+
698
+ // Create files in ignored data subdirectories
699
+ mkdirSync(join(testDir, 'data', 'db'), { recursive: true });
700
+ writeFileSync(join(testDir, 'data', 'db', 'conversations.sqlite'), 'db content');
701
+ mkdirSync(join(testDir, 'data', 'qdrant'), { recursive: true });
702
+ writeFileSync(join(testDir, 'data', 'qdrant', 'index.bin'), 'qdrant content');
703
+ mkdirSync(join(testDir, 'data', 'ipc-blobs'), { recursive: true });
704
+ writeFileSync(join(testDir, 'data', 'ipc-blobs', 'blob1'), 'ipc content');
705
+
706
+ // Create files in tracked data subdirectories
707
+ mkdirSync(join(testDir, 'data', 'memory'), { recursive: true });
708
+ writeFileSync(join(testDir, 'data', 'memory', 'index.json'), '{}');
709
+ mkdirSync(join(testDir, 'data', 'apps'), { recursive: true });
710
+ writeFileSync(join(testDir, 'data', 'apps', 'state.json'), '{}');
711
+
712
+ // Commit all changes, then verify what was included
713
+ await service.commitChanges('test commit');
714
+
715
+ const committedFiles = execFileSync(
716
+ 'git', ['diff', '--name-only', 'HEAD~1', 'HEAD'],
717
+ { cwd: testDir, encoding: 'utf-8' },
718
+ ).trim();
719
+
720
+ // Ignored subdirectories should NOT be in the commit
721
+ expect(committedFiles).not.toContain('data/db/');
722
+ expect(committedFiles).not.toContain('data/qdrant/');
723
+ expect(committedFiles).not.toContain('data/ipc-blobs/');
724
+
725
+ // Tracked subdirectories SHOULD be in the commit
726
+ expect(committedFiles).toContain('data/memory/index.json');
727
+ expect(committedFiles).toContain('data/apps/state.json');
728
+ });
729
+
730
+ test('respects .gitignore for log files', async () => {
731
+ const service = new WorkspaceGitService(testDir);
732
+ await service.ensureInitialized();
733
+
734
+ writeFileSync(join(testDir, 'test.log'), 'log content');
735
+
736
+ const status = await service.getStatus();
737
+
738
+ // .log files should be ignored
739
+ expect(status.untracked).not.toContain('test.log');
740
+ });
741
+
742
+ test('tracks non-ignored files', async () => {
743
+ const service = new WorkspaceGitService(testDir);
744
+ await service.ensureInitialized();
745
+
746
+ writeFileSync(join(testDir, 'config.json'), '{}');
747
+ writeFileSync(join(testDir, 'README.md'), '# Test');
748
+
749
+ const status = await service.getStatus();
750
+
751
+ expect(status.untracked).toContain('config.json');
752
+ expect(status.untracked).toContain('README.md');
753
+ });
754
+ });
755
+
756
+ describe('deadline-aware commitIfDirty', () => {
757
+ test('deadline expired before lock acquisition skips commit quickly', async () => {
758
+ const service = new WorkspaceGitService(testDir);
759
+ await service.ensureInitialized();
760
+
761
+ // Create a file so the workspace is dirty
762
+ writeFileSync(join(testDir, 'test.txt'), 'content');
763
+
764
+ // Use a deadline that has already passed
765
+ const pastDeadline = Date.now() - 1000;
766
+ const result = await service.commitIfDirty(
767
+ () => ({ message: 'should not commit' }),
768
+ { deadlineMs: pastDeadline },
769
+ );
770
+
771
+ expect(result.committed).toBe(false);
772
+
773
+ // File should still be uncommitted
774
+ const status = await service.getStatus();
775
+ expect(status.clean).toBe(false);
776
+ expect(status.untracked).toContain('test.txt');
777
+ });
778
+
779
+ test('deadline far in the future allows commit to proceed', async () => {
780
+ const service = new WorkspaceGitService(testDir);
781
+ await service.ensureInitialized();
782
+
783
+ writeFileSync(join(testDir, 'test.txt'), 'content');
784
+
785
+ // Use a deadline far in the future
786
+ const futureDeadline = Date.now() + 60_000;
787
+ const result = await service.commitIfDirty(
788
+ () => ({ message: 'deadline commit' }),
789
+ { deadlineMs: futureDeadline },
790
+ );
791
+
792
+ expect(result.committed).toBe(true);
793
+
794
+ // Verify the commit was actually created
795
+ const log = execFileSync('git', ['log', '--oneline', '-n', '1'], {
796
+ cwd: testDir,
797
+ encoding: 'utf-8',
798
+ });
799
+ expect(log).toContain('deadline commit');
800
+ });
801
+
802
+ test('no deadline option allows commit as normal', async () => {
803
+ const service = new WorkspaceGitService(testDir);
804
+ await service.ensureInitialized();
805
+
806
+ writeFileSync(join(testDir, 'test.txt'), 'content');
807
+
808
+ // No deadline option at all
809
+ const result = await service.commitIfDirty(
810
+ () => ({ message: 'no deadline commit' }),
811
+ );
812
+
813
+ expect(result.committed).toBe(true);
814
+ });
815
+ });
816
+
817
+ describe('breaker re-check under lock', () => {
818
+ test('queued call that acquires lock after breaker opens skips commit', async () => {
819
+ const service = new WorkspaceGitService(testDir);
820
+ await service.ensureInitialized();
821
+
822
+ writeFileSync(join(testDir, 'test.txt'), 'content');
823
+
824
+ // Simulate a breaker that opened between the pre-lock check and lock acquisition.
825
+ // We do this by:
826
+ // 1. Starting a commitIfDirty call (which passes the pre-lock breaker check)
827
+ // 2. Forcing the breaker open while the call is in progress
828
+
829
+ // First, force the breaker open by setting internal state directly.
830
+ // Since commitIfDirty re-checks the breaker after acquiring the lock,
831
+ // a call that passes the pre-lock check but finds the breaker open
832
+ // after acquiring the lock should bail out.
833
+ const internal = service as unknown as {
834
+ consecutiveFailures: number;
835
+ nextAllowedAttemptMs: number;
836
+ };
837
+ internal.consecutiveFailures = 5;
838
+ internal.nextAllowedAttemptMs = Date.now() + 60_000; // far in the future
839
+
840
+ // With breaker open, commitIfDirty should skip (pre-lock check)
841
+ const result = await service.commitIfDirty(
842
+ () => ({ message: 'should not commit' }),
843
+ );
844
+ expect(result.committed).toBe(false);
845
+
846
+ // Reset breaker
847
+ _resetBreaker(service);
848
+
849
+ // Now the commit should proceed normally
850
+ const result2 = await service.commitIfDirty(
851
+ () => ({ message: 'after breaker reset' }),
852
+ );
853
+ expect(result2.committed).toBe(true);
854
+ });
855
+
856
+ test('breaker early return inside lock does not reset breaker via recordSuccess', async () => {
857
+ const service = new WorkspaceGitService(testDir);
858
+ await service.ensureInitialized();
859
+
860
+ writeFileSync(join(testDir, 'test.txt'), 'content');
861
+
862
+ // Force breaker open with known failure count
863
+ const internal = service as unknown as {
864
+ consecutiveFailures: number;
865
+ nextAllowedAttemptMs: number;
866
+ };
867
+ internal.consecutiveFailures = 5;
868
+ internal.nextAllowedAttemptMs = Date.now() + 60_000;
869
+
870
+ // Pre-lock check catches the open breaker and returns early.
871
+ // Verify that consecutiveFailures is NOT reset to 0.
872
+ const result = await service.commitIfDirty(
873
+ () => ({ message: 'should not commit' }),
874
+ );
875
+ expect(result.committed).toBe(false);
876
+ expect(_getConsecutiveFailures(service)).toBe(5);
877
+ });
878
+
879
+ test('deadline early return inside lock does not reset breaker via recordSuccess', async () => {
880
+ const service = new WorkspaceGitService(testDir);
881
+ await service.ensureInitialized();
882
+
883
+ writeFileSync(join(testDir, 'test.txt'), 'content');
884
+
885
+ // Set up prior failures (breaker closed but failures recorded)
886
+ const internal = service as unknown as {
887
+ consecutiveFailures: number;
888
+ nextAllowedAttemptMs: number;
889
+ };
890
+ internal.consecutiveFailures = 3;
891
+ // Set nextAllowedAttemptMs in the past so the breaker check passes
892
+ // but consecutiveFailures is non-zero
893
+ internal.nextAllowedAttemptMs = Date.now() - 1000;
894
+
895
+ // Use a deadline that has already passed — this triggers the pre-lock
896
+ // deadline fast-path. consecutiveFailures should NOT be reset.
897
+ const result = await service.commitIfDirty(
898
+ () => ({ message: 'should not commit' }),
899
+ { deadlineMs: Date.now() - 1000 },
900
+ );
901
+ expect(result.committed).toBe(false);
902
+ expect(_getConsecutiveFailures(service)).toBe(3);
903
+ });
904
+
905
+ test('successful git operation after failures resets breaker', async () => {
906
+ const service = new WorkspaceGitService(testDir);
907
+ await service.ensureInitialized();
908
+
909
+ writeFileSync(join(testDir, 'test.txt'), 'content');
910
+
911
+ // Set up prior failures with breaker closed (backoff expired)
912
+ const internal = service as unknown as {
913
+ consecutiveFailures: number;
914
+ nextAllowedAttemptMs: number;
915
+ };
916
+ internal.consecutiveFailures = 3;
917
+ internal.nextAllowedAttemptMs = Date.now() - 1000;
918
+
919
+ // Commit should succeed and reset the breaker
920
+ const result = await service.commitIfDirty(
921
+ () => ({ message: 'recovery commit' }),
922
+ );
923
+ expect(result.committed).toBe(true);
924
+ expect(_getConsecutiveFailures(service)).toBe(0);
925
+ });
926
+
927
+ test('bypassBreaker ignores breaker state', async () => {
928
+ const service = new WorkspaceGitService(testDir);
929
+ await service.ensureInitialized();
930
+
931
+ writeFileSync(join(testDir, 'test.txt'), 'content');
932
+
933
+ // Force breaker open
934
+ const internal = service as unknown as {
935
+ consecutiveFailures: number;
936
+ nextAllowedAttemptMs: number;
937
+ };
938
+ internal.consecutiveFailures = 5;
939
+ internal.nextAllowedAttemptMs = Date.now() + 60_000;
940
+
941
+ // With bypassBreaker, commit should succeed despite open breaker
942
+ const result = await service.commitIfDirty(
943
+ () => ({ message: 'bypass breaker commit' }),
944
+ { bypassBreaker: true },
945
+ );
946
+ expect(result.committed).toBe(true);
947
+ });
948
+ });
949
+
950
+ describe('commitIfDirty diff error handling', () => {
951
+ test('non-1 exit code from git diff --cached --quiet is treated as an error', async () => {
952
+ const service = new WorkspaceGitService(testDir);
953
+ await service.ensureInitialized();
954
+
955
+ // Create a file so the workspace is dirty and commitIfDirty will
956
+ // proceed past the "clean" early-return.
957
+ writeFileSync(join(testDir, 'test.txt'), 'content');
958
+
959
+ // Access the private execGit method and wrap it so that
960
+ // 'git diff --cached --quiet' throws with exit code 2 (simulating
961
+ // a real git error rather than the expected exit code 1 for
962
+ // "staged changes exist").
963
+ const proto = Object.getPrototypeOf(service);
964
+ const originalExecGit = proto.execGit;
965
+ proto.execGit = async function (this: unknown, args: string[]) {
966
+ if (args[0] === 'diff' && args[1] === '--cached' && args[2] === '--quiet') {
967
+ const err = new Error(
968
+ 'Git command failed: git diff --cached --quiet\nError: simulated error\nStderr: ',
969
+ ) as Error & { code?: number };
970
+ err.code = 2;
971
+ throw err;
972
+ }
973
+ return originalExecGit.call(this, args);
974
+ };
975
+
976
+ try {
977
+ // commitIfDirty should propagate the error (not treat code 2 as
978
+ // "staged changes exist")
979
+ await expect(
980
+ service.commitIfDirty(() => ({ message: 'should not commit' })),
981
+ ).rejects.toThrow();
982
+ } finally {
983
+ // Restore the original method
984
+ proto.execGit = originalExecGit;
985
+ }
986
+ });
987
+ });
988
+
989
+ describe('init circuit breaker', () => {
990
+ test('init breaker opens after consecutive failures', async () => {
991
+ // Use a directory that doesn't exist so init fails
992
+ const badDir = '/nonexistent/path/that/does/not/exist';
993
+ const service = new WorkspaceGitService(badDir);
994
+
995
+ // First failure — breaker does NOT open (requires 2+ failures)
996
+ await expect(service.ensureInitialized()).rejects.toThrow();
997
+ expect(_getInitConsecutiveFailures(service)).toBe(1);
998
+
999
+ // Second failure — expire the backoff window from the first failure
1000
+ // so the attempt actually runs (not blocked by breaker).
1001
+ const internal = service as unknown as {
1002
+ initNextAllowedAttemptMs: number;
1003
+ };
1004
+ internal.initNextAllowedAttemptMs = Date.now() - 1;
1005
+
1006
+ await expect(service.ensureInitialized()).rejects.toThrow();
1007
+ expect(_getInitConsecutiveFailures(service)).toBe(2);
1008
+
1009
+ // Third attempt within the backoff window — breaker is now open
1010
+ // (2+ consecutive failures) so the attempt is skipped.
1011
+ await expect(service.ensureInitialized()).rejects.toThrow(
1012
+ 'Init circuit breaker open: backing off after repeated failures',
1013
+ );
1014
+ // Failure count should NOT increase (the breaker prevented the attempt)
1015
+ expect(_getInitConsecutiveFailures(service)).toBe(2);
1016
+ });
1017
+
1018
+ test('init breaker skips init attempts during backoff window', async () => {
1019
+ const service = new WorkspaceGitService(testDir);
1020
+
1021
+ // Force the init breaker open
1022
+ const internal = service as unknown as {
1023
+ initConsecutiveFailures: number;
1024
+ initNextAllowedAttemptMs: number;
1025
+ };
1026
+ internal.initConsecutiveFailures = 3;
1027
+ internal.initNextAllowedAttemptMs = Date.now() + 60_000; // far in the future
1028
+
1029
+ // ensureInitialized should throw with circuit breaker message
1030
+ await expect(service.ensureInitialized()).rejects.toThrow(
1031
+ 'Init circuit breaker open: backing off after repeated failures',
1032
+ );
1033
+
1034
+ // Failure count should NOT increase (the breaker prevented the attempt)
1035
+ expect(_getInitConsecutiveFailures(service)).toBe(3);
1036
+ });
1037
+
1038
+ test('init breaker resets on success', async () => {
1039
+ const service = new WorkspaceGitService(testDir);
1040
+
1041
+ // Simulate prior init failures
1042
+ const internal = service as unknown as {
1043
+ initConsecutiveFailures: number;
1044
+ initNextAllowedAttemptMs: number;
1045
+ };
1046
+ internal.initConsecutiveFailures = 3;
1047
+ // Set backoff in the past so the breaker is closed and allows retry
1048
+ internal.initNextAllowedAttemptMs = Date.now() - 1;
1049
+
1050
+ // This should succeed and reset the init breaker
1051
+ await service.ensureInitialized();
1052
+
1053
+ expect(_getInitConsecutiveFailures(service)).toBe(0);
1054
+ expect(service.isInitialized()).toBe(true);
1055
+ });
1056
+
1057
+ test('init breaker allows retry after backoff window expires', async () => {
1058
+ const service = new WorkspaceGitService(testDir);
1059
+
1060
+ // Simulate prior init failures with expired backoff
1061
+ const internal = service as unknown as {
1062
+ initConsecutiveFailures: number;
1063
+ initNextAllowedAttemptMs: number;
1064
+ };
1065
+ internal.initConsecutiveFailures = 5;
1066
+ internal.initNextAllowedAttemptMs = Date.now() - 1; // expired
1067
+
1068
+ // Breaker should be closed (backoff expired), allowing init to proceed
1069
+ await service.ensureInitialized();
1070
+
1071
+ expect(_getInitConsecutiveFailures(service)).toBe(0);
1072
+ expect(service.isInitialized()).toBe(true);
1073
+ });
1074
+
1075
+ test('init breaker is independent from commit breaker', async () => {
1076
+ const service = new WorkspaceGitService(testDir);
1077
+ await service.ensureInitialized();
1078
+
1079
+ // Force commit breaker open
1080
+ const internal = service as unknown as {
1081
+ consecutiveFailures: number;
1082
+ nextAllowedAttemptMs: number;
1083
+ };
1084
+ internal.consecutiveFailures = 5;
1085
+ internal.nextAllowedAttemptMs = Date.now() + 60_000;
1086
+
1087
+ // Init breaker should still be clean
1088
+ expect(_getInitConsecutiveFailures(service)).toBe(0);
1089
+
1090
+ // Commit breaker should be open
1091
+ expect(_getConsecutiveFailures(service)).toBe(5);
1092
+
1093
+ // Reset commit breaker
1094
+ _resetBreaker(service);
1095
+
1096
+ // Force init breaker open
1097
+ const internal2 = service as unknown as {
1098
+ initConsecutiveFailures: number;
1099
+ initNextAllowedAttemptMs: number;
1100
+ };
1101
+ internal2.initConsecutiveFailures = 3;
1102
+ internal2.initNextAllowedAttemptMs = Date.now() + 60_000;
1103
+
1104
+ // Commit breaker should be clean
1105
+ expect(_getConsecutiveFailures(service)).toBe(0);
1106
+
1107
+ // Init breaker should be open
1108
+ expect(_getInitConsecutiveFailures(service)).toBe(3);
1109
+
1110
+ // Reset init breaker
1111
+ _resetInitBreaker(service);
1112
+ expect(_getInitConsecutiveFailures(service)).toBe(0);
1113
+ });
1114
+
1115
+ test('already initialized service bypasses init breaker check', async () => {
1116
+ const service = new WorkspaceGitService(testDir);
1117
+ await service.ensureInitialized();
1118
+ expect(service.isInitialized()).toBe(true);
1119
+
1120
+ // Force init breaker open
1121
+ const internal = service as unknown as {
1122
+ initConsecutiveFailures: number;
1123
+ initNextAllowedAttemptMs: number;
1124
+ };
1125
+ internal.initConsecutiveFailures = 5;
1126
+ internal.initNextAllowedAttemptMs = Date.now() + 60_000;
1127
+
1128
+ // ensureInitialized should succeed via the fast path (already initialized)
1129
+ // without hitting the breaker
1130
+ await service.ensureInitialized();
1131
+ });
1132
+ });
1133
+
1134
+ describe('isDeadlineExpired helper', () => {
1135
+ test('returns false when deadlineMs is undefined', () => {
1136
+ expect(isDeadlineExpired(undefined)).toBe(false);
1137
+ });
1138
+
1139
+ test('returns false when deadline is in the future', () => {
1140
+ expect(isDeadlineExpired(Date.now() + 60_000)).toBe(false);
1141
+ });
1142
+
1143
+ test('returns true when deadline is in the past', () => {
1144
+ expect(isDeadlineExpired(Date.now() - 1000)).toBe(true);
1145
+ });
1146
+
1147
+ test('returns true when deadline equals current time', () => {
1148
+ const now = Date.now();
1149
+ // Use a deadline slightly in the past to avoid timing flakes
1150
+ expect(isDeadlineExpired(now - 1)).toBe(true);
1151
+ });
1152
+ });
1153
+ });