@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,1135 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { AgentLoop } from '../agent/loop.js';
3
+ import type { AgentEvent, CheckpointInfo, CheckpointDecision } from '../agent/loop.js';
4
+ import type {
5
+ Provider,
6
+ Message,
7
+ ProviderResponse,
8
+ SendMessageOptions,
9
+ ToolDefinition,
10
+ ContentBlock,
11
+ } from '../providers/types.js';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /** A mock provider that returns pre-configured responses in sequence. */
18
+ function createMockProvider(
19
+ responses: ProviderResponse[],
20
+ ): { provider: Provider; calls: { messages: Message[]; tools?: ToolDefinition[]; systemPrompt?: string }[] } {
21
+ const calls: { messages: Message[]; tools?: ToolDefinition[]; systemPrompt?: string }[] = [];
22
+ let callIndex = 0;
23
+
24
+ const provider: Provider = {
25
+ name: 'mock',
26
+ async sendMessage(
27
+ messages: Message[],
28
+ tools?: ToolDefinition[],
29
+ systemPrompt?: string,
30
+ options?: SendMessageOptions,
31
+ ): Promise<ProviderResponse> {
32
+ calls.push({ messages: [...messages], tools, systemPrompt });
33
+ const response = responses[callIndex] ?? responses[responses.length - 1];
34
+ callIndex++;
35
+
36
+ // Emit streaming events if the response has text blocks
37
+ if (options?.onEvent) {
38
+ for (const block of response.content) {
39
+ if (block.type === 'text') {
40
+ options.onEvent({ type: 'text_delta', text: block.text });
41
+ }
42
+ }
43
+ }
44
+
45
+ return response;
46
+ },
47
+ };
48
+
49
+ return { provider, calls };
50
+ }
51
+
52
+ function textResponse(text: string): ProviderResponse {
53
+ return {
54
+ content: [{ type: 'text', text }],
55
+ model: 'mock-model',
56
+ usage: { inputTokens: 10, outputTokens: 5 },
57
+ stopReason: 'end_turn',
58
+ };
59
+ }
60
+
61
+ function toolUseResponse(id: string, name: string, input: Record<string, unknown>): ProviderResponse {
62
+ return {
63
+ content: [{ type: 'tool_use', id, name, input }],
64
+ model: 'mock-model',
65
+ usage: { inputTokens: 10, outputTokens: 5 },
66
+ stopReason: 'tool_use',
67
+ };
68
+ }
69
+
70
+ const dummyTools: ToolDefinition[] = [
71
+ { name: 'read_file', description: 'Read a file', input_schema: { type: 'object', properties: { path: { type: 'string' } } } },
72
+ ];
73
+
74
+ const userMessage: Message = {
75
+ role: 'user',
76
+ content: [{ type: 'text', text: 'Hello' }],
77
+ };
78
+
79
+ function collectEvents(events: AgentEvent[]): (event: AgentEvent) => void {
80
+ return (event) => events.push(event);
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Tests
85
+ // ---------------------------------------------------------------------------
86
+
87
+ describe('AgentLoop', () => {
88
+ // 1. Basic text response
89
+ test('returns history with assistant message for simple text response', async () => {
90
+ const { provider } = createMockProvider([textResponse('Hi there!')]);
91
+ const loop = new AgentLoop(provider, 'system prompt');
92
+
93
+ const events: AgentEvent[] = [];
94
+ const history = await loop.run([userMessage], collectEvents(events));
95
+
96
+ // History should contain original user message + assistant response
97
+ expect(history).toHaveLength(2);
98
+ expect(history[0]).toEqual(userMessage);
99
+ expect(history[1].role).toBe('assistant');
100
+ expect(history[1].content).toEqual([{ type: 'text', text: 'Hi there!' }]);
101
+ });
102
+
103
+ // 2. Tool execution — provider returns tool_use, verify tool executor is called
104
+ test('executes tool and passes result back to provider', async () => {
105
+ const toolCallId = 'tool-1';
106
+ const { provider, calls } = createMockProvider([
107
+ toolUseResponse(toolCallId, 'read_file', { path: '/tmp/test.txt' }),
108
+ textResponse('File contents received.'),
109
+ ]);
110
+
111
+ const toolCalls: { name: string; input: Record<string, unknown> }[] = [];
112
+ const toolExecutor = async (name: string, input: Record<string, unknown>) => {
113
+ toolCalls.push({ name, input });
114
+ return { content: 'file data here', isError: false };
115
+ };
116
+
117
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
118
+ const events: AgentEvent[] = [];
119
+ const history = await loop.run([userMessage], collectEvents(events));
120
+
121
+ // Tool executor was called with correct args
122
+ expect(toolCalls).toHaveLength(1);
123
+ expect(toolCalls[0].name).toBe('read_file');
124
+ expect(toolCalls[0].input).toEqual({ path: '/tmp/test.txt' });
125
+
126
+ // Provider was called twice (initial + after tool result)
127
+ expect(calls).toHaveLength(2);
128
+
129
+ // Second call should include the tool result as a user message
130
+ const secondCallMessages = calls[1].messages;
131
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
132
+ expect(lastMsg.role).toBe('user');
133
+
134
+ const toolResultBlock = lastMsg.content.find(
135
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
136
+ );
137
+ expect(toolResultBlock).toBeDefined();
138
+ expect(toolResultBlock!.tool_use_id).toBe(toolCallId);
139
+ expect(toolResultBlock!.content).toBe('file data here');
140
+ expect(toolResultBlock!.is_error).toBe(false);
141
+
142
+ // Final history: user, assistant(tool_use), user(tool_result), assistant(text)
143
+ expect(history).toHaveLength(4);
144
+ expect(history[3].role).toBe('assistant');
145
+ expect(history[3].content).toEqual([{ type: 'text', text: 'File contents received.' }]);
146
+ });
147
+
148
+ // 3. Multi-turn tool loop
149
+ test('supports multi-turn tool execution', async () => {
150
+ const { provider, calls } = createMockProvider([
151
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
152
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
153
+ textResponse('Done reading both files.'),
154
+ ]);
155
+
156
+ const toolExecutor = async (name: string, input: Record<string, unknown>) => {
157
+ return { content: `contents of ${(input as { path: string }).path}`, isError: false };
158
+ };
159
+
160
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
161
+ const history = await loop.run([userMessage], () => {});
162
+
163
+ // Provider called 3 times (two tool rounds + final text)
164
+ expect(calls).toHaveLength(3);
165
+
166
+ // History: user, assistant(t1), user(result1), assistant(t2), user(result2), assistant(text)
167
+ expect(history).toHaveLength(6);
168
+ expect(history[5].content).toEqual([{ type: 'text', text: 'Done reading both files.' }]);
169
+ });
170
+
171
+ // 4. Loop stops when provider returns tool_use but no executor is configured
172
+ test('stops when tool_use returned but no tool executor configured', async () => {
173
+ const { provider } = createMockProvider([
174
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
175
+ ]);
176
+
177
+ // No tool executor provided
178
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools);
179
+ const history = await loop.run([userMessage], () => {});
180
+
181
+ // Should stop after first response (no executor to handle tool use)
182
+ expect(history).toHaveLength(2);
183
+ expect(history[1].role).toBe('assistant');
184
+ });
185
+
186
+ // 5. Error handling — provider throws, verify error event and loop stops
187
+ test('emits error event and stops when provider throws', async () => {
188
+ const error = new Error('API rate limit exceeded');
189
+ const provider: Provider = {
190
+ name: 'mock',
191
+ async sendMessage(): Promise<ProviderResponse> {
192
+ throw error;
193
+ },
194
+ };
195
+
196
+ const loop = new AgentLoop(provider, 'system');
197
+ const events: AgentEvent[] = [];
198
+ const history = await loop.run([userMessage], collectEvents(events));
199
+
200
+ // Only the original message remains (no assistant message added on error)
201
+ expect(history).toHaveLength(1);
202
+
203
+ // Error event was emitted
204
+ const errorEvents = events.filter((e) => e.type === 'error');
205
+ expect(errorEvents).toHaveLength(1);
206
+ expect((errorEvents[0] as { type: 'error'; error: Error }).error.message).toBe('API rate limit exceeded');
207
+ });
208
+
209
+ // 6. Abort signal — verify the loop respects AbortSignal
210
+ test('stops when abort signal is triggered before provider call', async () => {
211
+ const controller = new AbortController();
212
+ controller.abort(); // abort immediately
213
+
214
+ const { provider } = createMockProvider([textResponse('Should not reach')]);
215
+ const loop = new AgentLoop(provider, 'system');
216
+ const history = await loop.run([userMessage], () => {}, controller.signal);
217
+
218
+ // Loop should exit immediately, returning only original messages
219
+ expect(history).toHaveLength(1);
220
+ });
221
+
222
+ test('stops when abort signal is triggered between turns', async () => {
223
+ const controller = new AbortController();
224
+ let turnCount = 0;
225
+
226
+ const { provider } = createMockProvider([
227
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
228
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
229
+ textResponse('Should not reach'),
230
+ ]);
231
+
232
+ const toolExecutor = async () => {
233
+ turnCount++;
234
+ if (turnCount === 1) {
235
+ // Abort after the first tool turn completes
236
+ controller.abort();
237
+ }
238
+ return { content: 'data', isError: false };
239
+ };
240
+
241
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
242
+ const history = await loop.run([userMessage], () => {}, controller.signal);
243
+
244
+ // After the first tool turn, abort fires. The while loop checks signal at the
245
+ // top and breaks. History: user, assistant(t1), user(result1)
246
+ // The second provider call may or may not happen depending on when the abort
247
+ // check triggers, but the loop should eventually stop.
248
+ // At minimum, verify it doesn't run all 3 provider calls.
249
+ expect(history.length).toBeLessThanOrEqual(4);
250
+
251
+ // Verify the loop didn't reach the final text response
252
+ const lastAssistant = [...history].reverse().find(m => m.role === 'assistant');
253
+ expect(lastAssistant).toBeDefined();
254
+ const hasToolUse = lastAssistant!.content.some(b => b.type === 'tool_use');
255
+ // The last assistant message should be a tool_use, not the final text
256
+ expect(hasToolUse).toBe(true);
257
+ });
258
+
259
+ // 6b. Abort signal during long-running tool execution — loop exits immediately
260
+ test('stops immediately when abort fires during a stuck tool execution', async () => {
261
+ const controller = new AbortController();
262
+
263
+ const { provider } = createMockProvider([
264
+ toolUseResponse('t1', 'read_file', { path: '/stuck.txt' }),
265
+ textResponse('Should not reach'),
266
+ ]);
267
+
268
+ // Simulate a stuck tool that never resolves — abort fires while it's running
269
+ const toolExecutor = async () => {
270
+ // Abort from a timer while this tool is "stuck"
271
+ setTimeout(() => controller.abort(), 50);
272
+ // Simulate being stuck for a long time
273
+ await new Promise(resolve => setTimeout(resolve, 10_000));
274
+ return { content: 'should never return', isError: false };
275
+ };
276
+
277
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
278
+ const start = Date.now();
279
+ const history = await loop.run([userMessage], () => {}, controller.signal);
280
+ const elapsed = Date.now() - start;
281
+
282
+ // The loop should exit quickly (~50ms for abort), not wait 10s for the tool
283
+ expect(elapsed).toBeLessThan(2000);
284
+
285
+ // User message + assistant tool_use + synthesized cancellation tool_result
286
+ expect(history).toHaveLength(3);
287
+ const lastMsg = history[2];
288
+ expect(lastMsg.role).toBe('user');
289
+ expect(lastMsg.content).toHaveLength(1);
290
+ expect(lastMsg.content[0].type).toBe('tool_result');
291
+ expect((lastMsg.content[0] as { type: 'tool_result'; tool_use_id: string; content: string; is_error: boolean }).content).toBe('Cancelled by user');
292
+ expect((lastMsg.content[0] as { type: 'tool_result'; tool_use_id: string; content: string; is_error: boolean }).is_error).toBe(true);
293
+ });
294
+
295
+ // 7. Events — verify text_delta and other events are emitted
296
+ test('emits text_delta events during streaming', async () => {
297
+ const { provider } = createMockProvider([textResponse('Hello world')]);
298
+ const loop = new AgentLoop(provider, 'system');
299
+
300
+ const events: AgentEvent[] = [];
301
+ await loop.run([userMessage], collectEvents(events));
302
+
303
+ const textDeltas = events.filter((e) => e.type === 'text_delta');
304
+ expect(textDeltas).toHaveLength(1);
305
+ expect((textDeltas[0] as { type: 'text_delta'; text: string }).text).toBe('Hello world');
306
+ });
307
+
308
+ test('emits usage events', async () => {
309
+ const { provider } = createMockProvider([textResponse('Hi')]);
310
+ const loop = new AgentLoop(provider, 'system');
311
+
312
+ const events: AgentEvent[] = [];
313
+ await loop.run([userMessage], collectEvents(events));
314
+
315
+ const usageEvents = events.filter((e) => e.type === 'usage');
316
+ expect(usageEvents).toHaveLength(1);
317
+ const usage = usageEvents[0] as Extract<AgentEvent, { type: 'usage' }>;
318
+ expect(usage.type).toBe('usage');
319
+ expect(usage.inputTokens).toBe(10);
320
+ expect(usage.outputTokens).toBe(5);
321
+ expect(usage.model).toBe('mock-model');
322
+ expect(typeof usage.providerDurationMs).toBe('number');
323
+ expect(usage.providerDurationMs).toBeGreaterThanOrEqual(0);
324
+ });
325
+
326
+ test('emits message_complete events', async () => {
327
+ const { provider } = createMockProvider([textResponse('Done')]);
328
+ const loop = new AgentLoop(provider, 'system');
329
+
330
+ const events: AgentEvent[] = [];
331
+ await loop.run([userMessage], collectEvents(events));
332
+
333
+ const completeEvents = events.filter((e) => e.type === 'message_complete');
334
+ expect(completeEvents).toHaveLength(1);
335
+ expect((completeEvents[0] as { type: 'message_complete'; message: Message }).message.role).toBe('assistant');
336
+ });
337
+
338
+ test('emits tool_use and tool_result events during tool execution', async () => {
339
+ const { provider } = createMockProvider([
340
+ toolUseResponse('t1', 'read_file', { path: '/test.txt' }),
341
+ textResponse('Done'),
342
+ ]);
343
+
344
+ const toolExecutor = async () => ({ content: 'file data', isError: false });
345
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
346
+
347
+ const events: AgentEvent[] = [];
348
+ await loop.run([userMessage], collectEvents(events));
349
+
350
+ const toolUseEvents = events.filter((e) => e.type === 'tool_use');
351
+ expect(toolUseEvents).toHaveLength(1);
352
+ expect(toolUseEvents[0]).toEqual({
353
+ type: 'tool_use',
354
+ id: 't1',
355
+ name: 'read_file',
356
+ input: { path: '/test.txt' },
357
+ });
358
+
359
+ const toolResultEvents = events.filter((e) => e.type === 'tool_result');
360
+ expect(toolResultEvents).toHaveLength(1);
361
+ expect((toolResultEvents[0] as Extract<AgentEvent, { type: 'tool_result' }>).toolUseId).toBe('t1');
362
+ expect((toolResultEvents[0] as Extract<AgentEvent, { type: 'tool_result' }>).content).toBe('file data');
363
+ expect((toolResultEvents[0] as Extract<AgentEvent, { type: 'tool_result' }>).isError).toBe(false);
364
+ });
365
+
366
+ // 8. Progress reminder injection every 5 tool-use turns
367
+ test('injects progress reminder after every 5 tool-use turns', async () => {
368
+ // Create 6 tool responses followed by a text response
369
+ const responses: ProviderResponse[] = [];
370
+ for (let i = 0; i < 6; i++) {
371
+ responses.push(toolUseResponse(`t${i}`, 'read_file', { path: `/file${i}.txt` }));
372
+ }
373
+ responses.push(textResponse('Finally done'));
374
+
375
+ const { provider, calls } = createMockProvider(responses);
376
+ const toolExecutor = async () => ({ content: 'data', isError: false });
377
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
378
+
379
+ await loop.run([userMessage], () => {});
380
+
381
+ // After the 5th tool-use turn, the user message should contain a progress reminder
382
+ // calls[5] is the 6th provider call; its messages[-1] should have the reminder
383
+ const fifthTurnResultMsg = calls[5].messages[calls[5].messages.length - 1];
384
+ const reminderBlock = fifthTurnResultMsg.content.find(
385
+ (b): b is Extract<ContentBlock, { type: 'text' }> =>
386
+ b.type === 'text' && b.text.includes('making meaningful progress'),
387
+ );
388
+ expect(reminderBlock).toBeDefined();
389
+ });
390
+
391
+ test('stops after configured maxToolUseTurns to prevent runaway loops', async () => {
392
+ const responses: ProviderResponse[] = [
393
+ toolUseResponse('t1', 'read_file', { path: '/one.txt' }),
394
+ toolUseResponse('t2', 'read_file', { path: '/two.txt' }),
395
+ toolUseResponse('t3', 'read_file', { path: '/three.txt' }),
396
+ textResponse('Should never be requested'),
397
+ ];
398
+ const { provider, calls } = createMockProvider(responses);
399
+ const toolExecutor = async () => ({ content: 'data', isError: false });
400
+ const loop = new AgentLoop(
401
+ provider,
402
+ 'system',
403
+ { maxToolUseTurns: 3 },
404
+ dummyTools,
405
+ toolExecutor,
406
+ );
407
+
408
+ const events: AgentEvent[] = [];
409
+ const history = await loop.run([userMessage], collectEvents(events));
410
+
411
+ // The loop should stop immediately after the 3rd tool-use turn, before the next provider call.
412
+ expect(calls).toHaveLength(3);
413
+
414
+ const errorEvents = events.filter(
415
+ (e): e is Extract<AgentEvent, { type: 'error' }> => e.type === 'error',
416
+ );
417
+ expect(errorEvents).toHaveLength(1);
418
+ expect(errorEvents[0].error.message).toContain('Tool-use turn limit reached (3)');
419
+
420
+ const lastMessage = history[history.length - 1];
421
+ expect(lastMessage.role).toBe('user');
422
+ const limitText = lastMessage.content.find(
423
+ (b): b is Extract<ContentBlock, { type: 'text' }> =>
424
+ b.type === 'text' && b.text.includes('Tool-use turn limit reached (3)'),
425
+ );
426
+ expect(limitText).toBeDefined();
427
+ });
428
+
429
+ // 9. Tool executor error results are forwarded correctly
430
+ test('forwards tool error results to provider', async () => {
431
+ const { provider, calls } = createMockProvider([
432
+ toolUseResponse('t1', 'read_file', { path: '/nonexistent.txt' }),
433
+ textResponse('File not found, sorry.'),
434
+ ]);
435
+
436
+ const toolExecutor = async () => ({ content: 'ENOENT: file not found', isError: true });
437
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
438
+
439
+ await loop.run([userMessage], () => {});
440
+
441
+ const secondCallMessages = calls[1].messages;
442
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
443
+ const toolResultBlock = lastMsg.content.find(
444
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
445
+ );
446
+ expect(toolResultBlock).toBeDefined();
447
+ expect(toolResultBlock!.is_error).toBe(true);
448
+ expect(toolResultBlock!.content).toBe('ENOENT: file not found');
449
+ });
450
+
451
+ // 10. Tool output chunks are forwarded via onEvent
452
+ test('emits tool_output_chunk events during tool execution', async () => {
453
+ const { provider } = createMockProvider([
454
+ toolUseResponse('t1', 'read_file', { path: '/test.txt' }),
455
+ textResponse('Done'),
456
+ ]);
457
+
458
+ const toolExecutor = async (
459
+ _name: string,
460
+ _input: Record<string, unknown>,
461
+ onOutput?: (chunk: string) => void,
462
+ ) => {
463
+ onOutput?.('chunk1');
464
+ onOutput?.('chunk2');
465
+ return { content: 'full output', isError: false };
466
+ };
467
+
468
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
469
+ const events: AgentEvent[] = [];
470
+ await loop.run([userMessage], collectEvents(events));
471
+
472
+ const chunkEvents = events.filter((e) => e.type === 'tool_output_chunk');
473
+ expect(chunkEvents).toHaveLength(2);
474
+ expect((chunkEvents[0] as Extract<AgentEvent, { type: 'tool_output_chunk' }>).chunk).toBe('chunk1');
475
+ expect((chunkEvents[1] as Extract<AgentEvent, { type: 'tool_output_chunk' }>).chunk).toBe('chunk2');
476
+ });
477
+
478
+ // 11. System prompt and tools are passed to provider
479
+ test('passes system prompt and tools to provider', async () => {
480
+ const { provider, calls } = createMockProvider([textResponse('Hi')]);
481
+ const loop = new AgentLoop(provider, 'My system prompt', {}, dummyTools);
482
+
483
+ await loop.run([userMessage], () => {});
484
+
485
+ expect(calls[0].systemPrompt).toBe('My system prompt');
486
+ expect(calls[0].tools).toEqual(dummyTools);
487
+ });
488
+
489
+ // 12. No tools configured — tools are not passed to provider
490
+ test('does not pass tools to provider when none are configured', async () => {
491
+ const { provider, calls } = createMockProvider([textResponse('Hi')]);
492
+ const loop = new AgentLoop(provider, 'system');
493
+
494
+ await loop.run([userMessage], () => {});
495
+
496
+ expect(calls[0].tools).toBeUndefined();
497
+ });
498
+
499
+ // 13. Parallel tool execution — multiple tool_use blocks in a single response
500
+ test('executes multiple tools in parallel', async () => {
501
+ const { provider, calls } = createMockProvider([
502
+ // Provider returns 3 tool_use blocks in a single response
503
+ {
504
+ content: [
505
+ { type: 'tool_use' as const, id: 't1', name: 'read_file', input: { path: '/a.txt' } },
506
+ { type: 'tool_use' as const, id: 't2', name: 'read_file', input: { path: '/b.txt' } },
507
+ { type: 'tool_use' as const, id: 't3', name: 'read_file', input: { path: '/c.txt' } },
508
+ ],
509
+ model: 'mock-model',
510
+ usage: { inputTokens: 10, outputTokens: 5 },
511
+ stopReason: 'tool_use' as const,
512
+ },
513
+ textResponse('Got all three files.'),
514
+ ]);
515
+
516
+ const executionLog: { path: string; start: number; end: number }[] = [];
517
+ const toolExecutor = async (_name: string, input: Record<string, unknown>) => {
518
+ const start = Date.now();
519
+ // Simulate async work — all tools should overlap in time
520
+ await new Promise(resolve => setTimeout(resolve, 50));
521
+ const end = Date.now();
522
+ executionLog.push({ path: (input as { path: string }).path, start, end });
523
+ return { content: `contents of ${(input as { path: string }).path}`, isError: false };
524
+ };
525
+
526
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
527
+ const events: AgentEvent[] = [];
528
+ const history = await loop.run([userMessage], collectEvents(events));
529
+
530
+ // All 3 tools should have been called
531
+ expect(executionLog).toHaveLength(3);
532
+
533
+ // Verify parallel execution: all tools should start before any finishes
534
+ // (with 50ms delay each, sequential would take 150ms+, parallel ~50ms)
535
+ const allStarts = executionLog.map(e => e.start);
536
+ const allEnds = executionLog.map(e => e.end);
537
+ const firstEnd = Math.min(...allEnds);
538
+ const lastStart = Math.max(...allStarts);
539
+ // In parallel execution, the last tool starts before the first tool ends
540
+ expect(lastStart).toBeLessThanOrEqual(firstEnd);
541
+
542
+ // Provider should have been called twice (tool batch + final text)
543
+ expect(calls).toHaveLength(2);
544
+
545
+ // Second call should contain 3 tool_result blocks in order
546
+ const secondCallMessages = calls[1].messages;
547
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
548
+ const toolResultBlocks = lastMsg.content.filter(
549
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
550
+ );
551
+ expect(toolResultBlocks).toHaveLength(3);
552
+ expect(toolResultBlocks[0].tool_use_id).toBe('t1');
553
+ expect(toolResultBlocks[1].tool_use_id).toBe('t2');
554
+ expect(toolResultBlocks[2].tool_use_id).toBe('t3');
555
+
556
+ // All tool_use events should be emitted before any tool_result events
557
+ let lastToolUseIdx = -1;
558
+ let firstToolResultIdx = events.length;
559
+ events.forEach((e, i) => {
560
+ if (e.type === 'tool_use') lastToolUseIdx = i;
561
+ if (e.type === 'tool_result' && i < firstToolResultIdx) firstToolResultIdx = i;
562
+ });
563
+ expect(lastToolUseIdx).toBeLessThan(firstToolResultIdx);
564
+
565
+ // Final history: user, assistant(3 tool_use), user(3 tool_result), assistant(text)
566
+ expect(history).toHaveLength(4);
567
+ });
568
+
569
+ // 14. Abort before parallel tool execution synthesizes cancelled results
570
+ test('synthesizes cancelled results when aborted before tool execution', async () => {
571
+ const controller = new AbortController();
572
+
573
+ const { provider } = createMockProvider([
574
+ {
575
+ content: [
576
+ { type: 'tool_use' as const, id: 't1', name: 'read_file', input: { path: '/a.txt' } },
577
+ { type: 'tool_use' as const, id: 't2', name: 'read_file', input: { path: '/b.txt' } },
578
+ ],
579
+ model: 'mock-model',
580
+ usage: { inputTokens: 10, outputTokens: 5 },
581
+ stopReason: 'tool_use' as const,
582
+ },
583
+ ]);
584
+
585
+ // Abort during the provider call so the signal is already aborted
586
+ // before tool execution begins
587
+ const originalSendMessage = provider.sendMessage.bind(provider);
588
+ provider.sendMessage = async (...args: Parameters<typeof provider.sendMessage>) => {
589
+ const result = await originalSendMessage(...args);
590
+ controller.abort();
591
+ return result;
592
+ };
593
+
594
+ const toolCalls: string[] = [];
595
+ const toolExecutor = async (_name: string, input: Record<string, unknown>) => {
596
+ toolCalls.push((input as { path: string }).path);
597
+ return { content: 'data', isError: false };
598
+ };
599
+
600
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
601
+ const events: AgentEvent[] = [];
602
+ const history = await loop.run([userMessage], collectEvents(events), controller.signal);
603
+
604
+ // No tools should have been executed
605
+ expect(toolCalls).toHaveLength(0);
606
+
607
+ // History should contain cancelled tool_result blocks
608
+ const lastMsg = history[history.length - 1];
609
+ expect(lastMsg.role).toBe('user');
610
+ const toolResultBlocks = lastMsg.content.filter(
611
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
612
+ );
613
+ expect(toolResultBlocks).toHaveLength(2);
614
+ expect(toolResultBlocks[0].tool_use_id).toBe('t1');
615
+ expect(toolResultBlocks[0].content).toBe('Cancelled by user');
616
+ expect(toolResultBlocks[0].is_error).toBe(true);
617
+ expect(toolResultBlocks[1].tool_use_id).toBe('t2');
618
+ expect(toolResultBlocks[1].content).toBe('Cancelled by user');
619
+ expect(toolResultBlocks[1].is_error).toBe(true);
620
+ });
621
+
622
+ // 15. Parallel tool_result events are emitted in deterministic tool_use order
623
+ test('emits tool_result events in tool_use order regardless of completion timing', async () => {
624
+ const { provider } = createMockProvider([
625
+ {
626
+ content: [
627
+ { type: 'tool_use' as const, id: 't1', name: 'read_file', input: { path: '/slow.txt' } },
628
+ { type: 'tool_use' as const, id: 't2', name: 'read_file', input: { path: '/fast.txt' } },
629
+ { type: 'tool_use' as const, id: 't3', name: 'read_file', input: { path: '/medium.txt' } },
630
+ ],
631
+ model: 'mock-model',
632
+ usage: { inputTokens: 10, outputTokens: 5 },
633
+ stopReason: 'tool_use' as const,
634
+ },
635
+ textResponse('Done'),
636
+ ]);
637
+
638
+ // Tools complete in different order than they were called: t2 first, t3 second, t1 last
639
+ const toolExecutor = async (_name: string, input: Record<string, unknown>) => {
640
+ const path = (input as { path: string }).path;
641
+ const delays: Record<string, number> = { '/slow.txt': 80, '/fast.txt': 10, '/medium.txt': 40 };
642
+ await new Promise(resolve => setTimeout(resolve, delays[path] ?? 10));
643
+ return { content: `contents of ${path}`, isError: false };
644
+ };
645
+
646
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
647
+ const events: AgentEvent[] = [];
648
+ await loop.run([userMessage], collectEvents(events));
649
+
650
+ // Collect tool_result events in order
651
+ const toolResultEvents = events.filter(
652
+ (e): e is Extract<AgentEvent, { type: 'tool_result' }> => e.type === 'tool_result',
653
+ );
654
+ expect(toolResultEvents).toHaveLength(3);
655
+
656
+ // Results must be in tool_use order (t1, t2, t3), NOT completion order (t2, t3, t1)
657
+ expect(toolResultEvents[0].toolUseId).toBe('t1');
658
+ expect(toolResultEvents[1].toolUseId).toBe('t2');
659
+ expect(toolResultEvents[2].toolUseId).toBe('t3');
660
+ });
661
+
662
+ // ---------------------------------------------------------------------------
663
+ // Checkpoint callback tests
664
+ // ---------------------------------------------------------------------------
665
+
666
+ // 16. Checkpoint callback is called after tool results with correct info
667
+ test('checkpoint callback is called after tool results with correct info', async () => {
668
+ const { provider } = createMockProvider([
669
+ toolUseResponse('t1', 'read_file', { path: '/test.txt' }),
670
+ textResponse('Done'),
671
+ ]);
672
+
673
+ const toolExecutor = async () => ({ content: 'file data', isError: false });
674
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
675
+
676
+ const checkpoints: CheckpointInfo[] = [];
677
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
678
+ checkpoints.push(checkpoint);
679
+ return 'continue';
680
+ };
681
+
682
+ await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
683
+
684
+ expect(checkpoints).toHaveLength(1);
685
+ expect(checkpoints[0]).toEqual({
686
+ turnIndex: 0,
687
+ toolCount: 1,
688
+ hasToolUse: true,
689
+ });
690
+ });
691
+
692
+ // 17. Returning 'continue' lets the loop proceed normally
693
+ test('checkpoint returning continue lets the loop proceed normally', async () => {
694
+ const { provider, calls } = createMockProvider([
695
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
696
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
697
+ textResponse('All done'),
698
+ ]);
699
+
700
+ const toolExecutor = async () => ({ content: 'data', isError: false });
701
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
702
+
703
+ const onCheckpoint = (): CheckpointDecision => 'continue';
704
+
705
+ const history = await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
706
+
707
+ // All 3 provider calls should happen (2 tool turns + final text)
708
+ expect(calls).toHaveLength(3);
709
+ // Full history: user, assistant(t1), user(result1), assistant(t2), user(result2), assistant(text)
710
+ expect(history).toHaveLength(6);
711
+ expect(history[5].content).toEqual([{ type: 'text', text: 'All done' }]);
712
+ });
713
+
714
+ // 18. Returning 'yield' causes the loop to stop after that turn
715
+ test('checkpoint returning yield causes the loop to stop', async () => {
716
+ const { provider, calls } = createMockProvider([
717
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
718
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
719
+ textResponse('Should not reach'),
720
+ ]);
721
+
722
+ const toolExecutor = async () => ({ content: 'data', isError: false });
723
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
724
+
725
+ const onCheckpoint = (): CheckpointDecision => 'yield';
726
+
727
+ const history = await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
728
+
729
+ // Only 1 provider call should happen — loop yields after first tool turn
730
+ expect(calls).toHaveLength(1);
731
+ // History: user, assistant(t1), user(result1)
732
+ expect(history).toHaveLength(3);
733
+ expect(history[1].role).toBe('assistant');
734
+ expect(history[2].role).toBe('user');
735
+ });
736
+
737
+ // 19. Without a checkpoint callback, behavior is unchanged
738
+ test('without checkpoint callback behavior is unchanged', async () => {
739
+ const { provider, calls } = createMockProvider([
740
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
741
+ textResponse('Done'),
742
+ ]);
743
+
744
+ const toolExecutor = async () => ({ content: 'data', isError: false });
745
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
746
+
747
+ const history = await loop.run([userMessage], () => {});
748
+
749
+ // Normal behavior: 2 provider calls, full history
750
+ expect(calls).toHaveLength(2);
751
+ expect(history).toHaveLength(4);
752
+ expect(history[3].content).toEqual([{ type: 'text', text: 'Done' }]);
753
+ });
754
+
755
+ // 20. turnIndex increments correctly across turns
756
+ test('turnIndex increments correctly across multiple turns', async () => {
757
+ const { provider } = createMockProvider([
758
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
759
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
760
+ toolUseResponse('t3', 'read_file', { path: '/c.txt' }),
761
+ textResponse('Done'),
762
+ ]);
763
+
764
+ const toolExecutor = async () => ({ content: 'data', isError: false });
765
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
766
+
767
+ const checkpoints: CheckpointInfo[] = [];
768
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
769
+ checkpoints.push(checkpoint);
770
+ return 'continue';
771
+ };
772
+
773
+ await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
774
+
775
+ expect(checkpoints).toHaveLength(3);
776
+ expect(checkpoints[0].turnIndex).toBe(0);
777
+ expect(checkpoints[1].turnIndex).toBe(1);
778
+ expect(checkpoints[2].turnIndex).toBe(2);
779
+ });
780
+
781
+ // 21. Checkpoint is NOT called when there's no tool use
782
+ test('checkpoint is not called when assistant responds with text only', async () => {
783
+ const { provider } = createMockProvider([textResponse('Just a text response')]);
784
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools);
785
+
786
+ const checkpoints: CheckpointInfo[] = [];
787
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
788
+ checkpoints.push(checkpoint);
789
+ return 'continue';
790
+ };
791
+
792
+ const history = await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
793
+
794
+ // Checkpoint should never be called for a text-only response
795
+ expect(checkpoints).toHaveLength(0);
796
+ // Normal response
797
+ expect(history).toHaveLength(2);
798
+ expect(history[1].content).toEqual([{ type: 'text', text: 'Just a text response' }]);
799
+ });
800
+
801
+ // 22. Checkpoint reports correct toolCount for parallel tool execution
802
+ test('checkpoint reports correct toolCount for parallel tools', async () => {
803
+ const { provider } = createMockProvider([
804
+ {
805
+ content: [
806
+ { type: 'tool_use' as const, id: 't1', name: 'read_file', input: { path: '/a.txt' } },
807
+ { type: 'tool_use' as const, id: 't2', name: 'read_file', input: { path: '/b.txt' } },
808
+ { type: 'tool_use' as const, id: 't3', name: 'read_file', input: { path: '/c.txt' } },
809
+ ],
810
+ model: 'mock-model',
811
+ usage: { inputTokens: 10, outputTokens: 5 },
812
+ stopReason: 'tool_use' as const,
813
+ },
814
+ textResponse('Got all three'),
815
+ ]);
816
+
817
+ const toolExecutor = async () => ({ content: 'data', isError: false });
818
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
819
+
820
+ const checkpoints: CheckpointInfo[] = [];
821
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
822
+ checkpoints.push(checkpoint);
823
+ return 'continue';
824
+ };
825
+
826
+ await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
827
+
828
+ expect(checkpoints).toHaveLength(1);
829
+ expect(checkpoints[0].toolCount).toBe(3);
830
+ expect(checkpoints[0].hasToolUse).toBe(true);
831
+ });
832
+
833
+ // 23. Multiple checkpoints across a multi-turn run with selective yield on turn 3
834
+ test('multiple checkpoints with selective yield — executes turns 0-2, yields at turn 3, never runs 4+', async () => {
835
+ // Mock provider to return tool_use for 5 turns, then text
836
+ const responses: ProviderResponse[] = [];
837
+ for (let i = 0; i < 5; i++) {
838
+ responses.push(toolUseResponse(`t${i}`, 'read_file', { path: `/file${i}.txt` }));
839
+ }
840
+ responses.push(textResponse('Should never reach this'));
841
+
842
+ const { provider, calls } = createMockProvider(responses);
843
+ const toolExecutor = async () => ({ content: 'data', isError: false });
844
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
845
+
846
+ const checkpoints: CheckpointInfo[] = [];
847
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
848
+ checkpoints.push(checkpoint);
849
+ // Yield on turn 3 (0-indexed)
850
+ return checkpoint.turnIndex === 3 ? 'yield' : 'continue';
851
+ };
852
+
853
+ const events: AgentEvent[] = [];
854
+ const history = await loop.run([userMessage], collectEvents(events), undefined, undefined, onCheckpoint);
855
+
856
+ // Turns 0, 1, 2, 3 execute (4 provider calls). Turn 3 yields, so turns 4+ never execute.
857
+ expect(calls).toHaveLength(4);
858
+
859
+ // Checkpoints should have been called for turns 0 through 3
860
+ expect(checkpoints).toHaveLength(4);
861
+ expect(checkpoints[0].turnIndex).toBe(0);
862
+ expect(checkpoints[1].turnIndex).toBe(1);
863
+ expect(checkpoints[2].turnIndex).toBe(2);
864
+ expect(checkpoints[3].turnIndex).toBe(3);
865
+
866
+ // History should contain results from turns 0-3:
867
+ // user, assistant(t0), user(result0), assistant(t1), user(result1),
868
+ // assistant(t2), user(result2), assistant(t3), user(result3)
869
+ // = 1 original + 4*(assistant + user) = 9
870
+ expect(history).toHaveLength(9);
871
+
872
+ // Verify the last two messages are from turn 3
873
+ expect(history[7].role).toBe('assistant');
874
+ const lastAssistantToolUse = history[7].content.find((b) => b.type === 'tool_use');
875
+ expect(lastAssistantToolUse).toBeDefined();
876
+ if (lastAssistantToolUse && lastAssistantToolUse.type === 'tool_use') {
877
+ expect(lastAssistantToolUse.id).toBe('t3');
878
+ }
879
+ expect(history[8].role).toBe('user');
880
+ const lastToolResult = history[8].content.find(
881
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
882
+ );
883
+ expect(lastToolResult).toBeDefined();
884
+ expect(lastToolResult!.tool_use_id).toBe('t3');
885
+
886
+ // Verify turns 4+ never executed — no tool_use event for t4
887
+ const toolUseEvents = events.filter(
888
+ (e): e is Extract<AgentEvent, { type: 'tool_use' }> => e.type === 'tool_use',
889
+ );
890
+ const toolUseNames = toolUseEvents.map((e) => e.id);
891
+ expect(toolUseNames).toEqual(['t0', 't1', 't2', 't3']);
892
+ expect(toolUseNames).not.toContain('t4');
893
+ });
894
+
895
+ // 24. Yield on second turn — first turn proceeds, second stops
896
+ test('yield on second turn lets first turn proceed and stops on second', async () => {
897
+ const { provider, calls } = createMockProvider([
898
+ toolUseResponse('t1', 'read_file', { path: '/a.txt' }),
899
+ toolUseResponse('t2', 'read_file', { path: '/b.txt' }),
900
+ textResponse('Should not reach'),
901
+ ]);
902
+
903
+ const toolExecutor = async () => ({ content: 'data', isError: false });
904
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, toolExecutor);
905
+
906
+ const onCheckpoint = (checkpoint: CheckpointInfo): CheckpointDecision => {
907
+ // Yield on the second turn (turnIndex 1)
908
+ return checkpoint.turnIndex === 1 ? 'yield' : 'continue';
909
+ };
910
+
911
+ const history = await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
912
+
913
+ // 2 provider calls: first tool turn + second tool turn (yield after second)
914
+ expect(calls).toHaveLength(2);
915
+ // History: user, assistant(t1), user(result1), assistant(t2), user(result2)
916
+ expect(history).toHaveLength(5);
917
+ });
918
+
919
+ // ---------------------------------------------------------------------------
920
+ // Dynamic tool resolver (resolveTools) tests
921
+ // ---------------------------------------------------------------------------
922
+
923
+ // 25. Without resolveTools, static tools are used (backward compatible)
924
+ test('without resolveTools, static tools are passed to provider', async () => {
925
+ const { provider, calls } = createMockProvider([textResponse('Hi')]);
926
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools);
927
+
928
+ await loop.run([userMessage], () => {});
929
+
930
+ expect(calls[0].tools).toEqual(dummyTools);
931
+ });
932
+
933
+ // 26. resolveTools callback is invoked before each provider call
934
+ test('resolveTools is invoked before each provider call', async () => {
935
+ const resolverCalls: Message[][] = [];
936
+ const resolvedTools: ToolDefinition[] = [
937
+ { name: 'search', description: 'Search files', input_schema: { type: 'object', properties: { query: { type: 'string' } } } },
938
+ ];
939
+
940
+ const { provider } = createMockProvider([
941
+ toolUseResponse('t1', 'search', { query: 'foo' }),
942
+ textResponse('Found it'),
943
+ ]);
944
+
945
+ const toolExecutor = async () => ({ content: 'result', isError: false });
946
+
947
+ const resolveTools = (history: Message[]): ToolDefinition[] => {
948
+ resolverCalls.push([...history]);
949
+ return resolvedTools;
950
+ };
951
+
952
+ const loop = new AgentLoop(provider, 'system', {}, [], toolExecutor, resolveTools);
953
+ await loop.run([userMessage], () => {});
954
+
955
+ // resolveTools should be called once per provider turn (2 turns total)
956
+ expect(resolverCalls).toHaveLength(2);
957
+
958
+ // First call receives just the initial user message
959
+ expect(resolverCalls[0]).toHaveLength(1);
960
+ expect(resolverCalls[0][0]).toEqual(userMessage);
961
+
962
+ // Second call receives the accumulated history (user + assistant + tool_result)
963
+ expect(resolverCalls[1].length).toBeGreaterThan(1);
964
+ });
965
+
966
+ // 27. Resolved tool list is passed to the provider
967
+ test('resolved tools are passed to the provider instead of static tools', async () => {
968
+ const dynamicTools: ToolDefinition[] = [
969
+ { name: 'dynamic_tool', description: 'Dynamic', input_schema: { type: 'object' } },
970
+ ];
971
+
972
+ const { provider, calls } = createMockProvider([textResponse('Hi')]);
973
+
974
+ const resolveTools = (): ToolDefinition[] => dynamicTools;
975
+
976
+ // Pass different static tools to verify they are overridden
977
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, undefined, resolveTools);
978
+ await loop.run([userMessage], () => {});
979
+
980
+ // Provider should receive the dynamically resolved tools, not the static ones
981
+ expect(calls[0].tools).toEqual(dynamicTools);
982
+ expect(calls[0].tools).not.toEqual(dummyTools);
983
+ });
984
+
985
+ // 28. Tool list can change between turns
986
+ test('resolveTools can return different tools on each turn', async () => {
987
+ const toolsPerTurn: ToolDefinition[][] = [
988
+ [{ name: 'tool_a', description: 'Tool A', input_schema: { type: 'object' } }],
989
+ [
990
+ { name: 'tool_a', description: 'Tool A', input_schema: { type: 'object' } },
991
+ { name: 'tool_b', description: 'Tool B', input_schema: { type: 'object' } },
992
+ ],
993
+ [{ name: 'tool_c', description: 'Tool C', input_schema: { type: 'object' } }],
994
+ ];
995
+
996
+ let turnIndex = 0;
997
+ const resolveTools = (): ToolDefinition[] => {
998
+ const tools = toolsPerTurn[turnIndex] ?? toolsPerTurn[toolsPerTurn.length - 1];
999
+ turnIndex++;
1000
+ return tools;
1001
+ };
1002
+
1003
+ const { provider, calls } = createMockProvider([
1004
+ toolUseResponse('t1', 'tool_a', {}),
1005
+ toolUseResponse('t2', 'tool_a', {}),
1006
+ textResponse('Done'),
1007
+ ]);
1008
+
1009
+ const toolExecutor = async () => ({ content: 'ok', isError: false });
1010
+ const loop = new AgentLoop(provider, 'system', {}, [], toolExecutor, resolveTools);
1011
+ await loop.run([userMessage], () => {});
1012
+
1013
+ // Provider should have been called 3 times
1014
+ expect(calls).toHaveLength(3);
1015
+
1016
+ // Each call should have received different tools
1017
+ expect(calls[0].tools).toEqual(toolsPerTurn[0]);
1018
+ expect(calls[1].tools).toEqual(toolsPerTurn[1]);
1019
+ expect(calls[2].tools).toEqual(toolsPerTurn[2]);
1020
+ });
1021
+
1022
+ // 29. resolveTools returning empty array means no tools passed to provider
1023
+ test('resolveTools returning empty array sends no tools to provider', async () => {
1024
+ const resolveTools = (): ToolDefinition[] => [];
1025
+
1026
+ const { provider, calls } = createMockProvider([textResponse('No tools available')]);
1027
+
1028
+ const loop = new AgentLoop(provider, 'system', {}, dummyTools, undefined, resolveTools);
1029
+ await loop.run([userMessage], () => {});
1030
+
1031
+ // Empty array should result in undefined tools (same as no-tools behavior)
1032
+ expect(calls[0].tools).toBeUndefined();
1033
+ });
1034
+
1035
+ // ---------------------------------------------------------------------------
1036
+ // Tool result truncation tests
1037
+ // ---------------------------------------------------------------------------
1038
+
1039
+ // 30. Oversized tool results are truncated before entering history
1040
+ test('truncates oversized tool results before adding to history', async () => {
1041
+ const toolCallId = 'tool-large';
1042
+ const largeContent = 'x'.repeat(500_000);
1043
+
1044
+ const { provider, calls } = createMockProvider([
1045
+ toolUseResponse(toolCallId, 'read_file', { path: '/huge.txt' }),
1046
+ textResponse('Got it.'),
1047
+ ]);
1048
+
1049
+ const toolExecutor = async () => {
1050
+ return { content: largeContent, isError: false };
1051
+ };
1052
+
1053
+ const loop = new AgentLoop(
1054
+ provider,
1055
+ 'system',
1056
+ { maxInputTokens: 180_000 },
1057
+ dummyTools,
1058
+ toolExecutor,
1059
+ );
1060
+ const events: AgentEvent[] = [];
1061
+ const history = await loop.run([userMessage], collectEvents(events));
1062
+
1063
+ // The tool result user message is at index 2 in history
1064
+ const toolResultMsg = history[2];
1065
+ expect(toolResultMsg.role).toBe('user');
1066
+
1067
+ const toolResultBlock = toolResultMsg.content.find(
1068
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
1069
+ );
1070
+ expect(toolResultBlock).toBeDefined();
1071
+
1072
+ // Content should have been truncated (much shorter than the original 500K)
1073
+ expect(toolResultBlock!.content.length).toBeLessThan(500_000);
1074
+
1075
+ // Content should end with the truncation suffix
1076
+ expect(toolResultBlock!.content).toContain(
1077
+ '[Content truncated',
1078
+ );
1079
+
1080
+ // The second provider call should also have the truncated content in messages
1081
+ const secondCallMessages = calls[1].messages;
1082
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
1083
+ const sentBlock = lastMsg.content.find(
1084
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
1085
+ );
1086
+ expect(sentBlock).toBeDefined();
1087
+ expect(sentBlock!.content.length).toBeLessThan(500_000);
1088
+ });
1089
+
1090
+ // 31. Non-oversized tool results pass through unchanged
1091
+ test('non-oversized tool results pass through unchanged', async () => {
1092
+ const toolCallId = 'tool-small';
1093
+ const smallContent = 'small content';
1094
+
1095
+ const { provider, calls } = createMockProvider([
1096
+ toolUseResponse(toolCallId, 'read_file', { path: '/small.txt' }),
1097
+ textResponse('Got it.'),
1098
+ ]);
1099
+
1100
+ const toolExecutor = async () => {
1101
+ return { content: smallContent, isError: false };
1102
+ };
1103
+
1104
+ const loop = new AgentLoop(
1105
+ provider,
1106
+ 'system',
1107
+ { maxInputTokens: 180_000 },
1108
+ dummyTools,
1109
+ toolExecutor,
1110
+ );
1111
+ const events: AgentEvent[] = [];
1112
+ const history = await loop.run([userMessage], collectEvents(events));
1113
+
1114
+ // The tool result user message is at index 2 in history
1115
+ const toolResultMsg = history[2];
1116
+ expect(toolResultMsg.role).toBe('user');
1117
+
1118
+ const toolResultBlock = toolResultMsg.content.find(
1119
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
1120
+ );
1121
+ expect(toolResultBlock).toBeDefined();
1122
+
1123
+ // Content should be exactly the original small content — no truncation
1124
+ expect(toolResultBlock!.content).toBe(smallContent);
1125
+
1126
+ // The second provider call should also have the unchanged content
1127
+ const secondCallMessages = calls[1].messages;
1128
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
1129
+ const sentBlock = lastMsg.content.find(
1130
+ (b): b is Extract<ContentBlock, { type: 'tool_result' }> => b.type === 'tool_result',
1131
+ );
1132
+ expect(sentBlock).toBeDefined();
1133
+ expect(sentBlock!.content).toBe(smallContent);
1134
+ });
1135
+ });