@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Tests for the streaming validator primitives:
3
+ * - `readAndValidateManifest` consumes the first tar entry and runs the
4
+ * full manifest validation pipeline.
5
+ * - `createHashVerifier` is a passthrough Transform that aborts with a
6
+ * typed error on digest/size mismatch.
7
+ *
8
+ * Happy-path fixtures are built with the existing `buildVBundle` helper.
9
+ * Negative-path fixtures (non-manifest first, oversize manifest, malformed
10
+ * JSON, schema fail, sha mismatch) are hand-constructed because
11
+ * `buildVBundle` always produces valid, manifest-first archives.
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+ import { Readable } from "node:stream";
16
+ import { pipeline } from "node:stream/promises";
17
+ import { gzipSync } from "node:zlib";
18
+ import { describe, expect, test } from "bun:test";
19
+
20
+ import { buildVBundle } from "../vbundle-builder.js";
21
+ import {
22
+ createHashVerifier,
23
+ readAndValidateManifest,
24
+ StreamingValidationError,
25
+ } from "../vbundle-streaming-validator.js";
26
+ import {
27
+ parseVBundleStream,
28
+ type StreamedTarEntry,
29
+ } from "../vbundle-tar-stream.js";
30
+ import { computeManifestSha256 } from "../vbundle-validator.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const BLOCK_SIZE = 512;
37
+
38
+ function readableFromBuffer(buf: Uint8Array): Readable {
39
+ return Readable.from([Buffer.from(buf)]);
40
+ }
41
+
42
+ /** Minimal hand-rolled tar entry builder (ustar regular file). */
43
+ function buildTarEntry(name: string, data: Uint8Array): Uint8Array {
44
+ const encoder = new TextEncoder();
45
+ const nameBytes = encoder.encode(name);
46
+ if (nameBytes.length > 100) {
47
+ throw new Error(`test helper: name too long (${nameBytes.length} bytes)`);
48
+ }
49
+
50
+ const header = new Uint8Array(BLOCK_SIZE);
51
+ header.set(nameBytes, 0);
52
+
53
+ const writeOctal = (offset: number, length: number, value: number) => {
54
+ const str = value.toString(8).padStart(length - 1, "0");
55
+ for (let i = 0; i < str.length; i++) {
56
+ header[offset + i] = str.charCodeAt(i);
57
+ }
58
+ header[offset + length - 1] = 0;
59
+ };
60
+
61
+ writeOctal(100, 8, 0o644); // mode
62
+ writeOctal(108, 8, 0); // uid
63
+ writeOctal(116, 8, 0); // gid
64
+ writeOctal(124, 12, data.length); // size
65
+ writeOctal(136, 12, Math.floor(Date.now() / 1000)); // mtime
66
+ header[156] = "0".charCodeAt(0); // typeflag = regular file
67
+
68
+ const magic = encoder.encode("ustar\0");
69
+ header.set(magic, 257);
70
+ header[263] = "0".charCodeAt(0);
71
+ header[264] = "0".charCodeAt(0);
72
+
73
+ // Header checksum: sum of all bytes with the checksum field treated
74
+ // as 8 ASCII spaces.
75
+ let sum = 0;
76
+ for (let i = 0; i < BLOCK_SIZE; i++) {
77
+ sum += i >= 148 && i < 156 ? 0x20 : header[i];
78
+ }
79
+ writeOctal(148, 7, sum);
80
+ header[155] = 0x20;
81
+
82
+ // Pad data out to the next 512-byte boundary.
83
+ const remainder = data.length % BLOCK_SIZE;
84
+ const padded =
85
+ remainder === 0
86
+ ? data
87
+ : (() => {
88
+ const out = new Uint8Array(data.length + (BLOCK_SIZE - remainder));
89
+ out.set(data, 0);
90
+ return out;
91
+ })();
92
+
93
+ const entry = new Uint8Array(header.length + padded.length);
94
+ entry.set(header, 0);
95
+ entry.set(padded, header.length);
96
+ return entry;
97
+ }
98
+
99
+ /** Hand-build a gzipped tar archive from the given entries (no manifest injected). */
100
+ function buildRawVBundle(
101
+ entries: Array<{ name: string; data: Uint8Array }>,
102
+ ): Uint8Array {
103
+ const parts: Uint8Array[] = entries.map((e) => buildTarEntry(e.name, e.data));
104
+ // End-of-archive marker: two zero blocks.
105
+ parts.push(new Uint8Array(BLOCK_SIZE * 2));
106
+
107
+ const total = parts.reduce((n, p) => n + p.length, 0);
108
+ const tar = new Uint8Array(total);
109
+ let offset = 0;
110
+ for (const p of parts) {
111
+ tar.set(p, offset);
112
+ offset += p.length;
113
+ }
114
+ return gzipSync(tar);
115
+ }
116
+
117
+ /** Fetch the first entry of a streaming archive; drain+close the iterator after. */
118
+ async function firstEntryOf(
119
+ archive: Uint8Array,
120
+ ): Promise<{ entry: StreamedTarEntry; drainRest: () => Promise<void> }> {
121
+ const iter = parseVBundleStream(readableFromBuffer(archive));
122
+ const first = await iter.next();
123
+ if (first.done || !first.value) {
124
+ throw new Error("archive contained no entries");
125
+ }
126
+ const drainRest = async () => {
127
+ for await (const rest of iter) {
128
+ rest.body.resume();
129
+ }
130
+ };
131
+ return { entry: first.value, drainRest };
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // readAndValidateManifest — happy path
136
+ // ---------------------------------------------------------------------------
137
+
138
+ describe("readAndValidateManifest — happy path", () => {
139
+ test("parses manifest and populates expected map from manifest.files", async () => {
140
+ const fileA = new TextEncoder().encode("alpha payload\n");
141
+ const fileB = new TextEncoder().encode("beta payload\n");
142
+ const { archive, manifest } = buildVBundle({
143
+ files: [
144
+ { path: "workspace/a.txt", data: fileA },
145
+ { path: "workspace/b.txt", data: fileB },
146
+ ],
147
+ source: "test",
148
+ });
149
+
150
+ const { entry, drainRest } = await firstEntryOf(archive);
151
+ const result = await readAndValidateManifest(entry);
152
+ await drainRest();
153
+
154
+ expect(result.manifest.schema_version).toBe(manifest.schema_version);
155
+ expect(result.manifest.files).toHaveLength(2);
156
+ expect(result.manifest.manifest_sha256).toBe(manifest.manifest_sha256);
157
+
158
+ expect(result.expected.size).toBe(2);
159
+ const expectA = result.expected.get("workspace/a.txt");
160
+ expect(expectA?.size).toBe(fileA.length);
161
+ expect(expectA?.sha256).toBe(
162
+ manifest.files.find((f) => f.path === "workspace/a.txt")?.sha256,
163
+ );
164
+ const expectB = result.expected.get("workspace/b.txt");
165
+ expect(expectB?.size).toBe(fileB.length);
166
+ expect(expectB?.sha256).toBe(
167
+ manifest.files.find((f) => f.path === "workspace/b.txt")?.sha256,
168
+ );
169
+ });
170
+ });
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // readAndValidateManifest — negative paths
174
+ // ---------------------------------------------------------------------------
175
+
176
+ describe("readAndValidateManifest — negative paths", () => {
177
+ test("throws manifest_not_first when first entry is not manifest.json", async () => {
178
+ const archive = buildRawVBundle([
179
+ { name: "workspace/a.txt", data: new TextEncoder().encode("hello") },
180
+ ]);
181
+ const { entry, drainRest } = await firstEntryOf(archive);
182
+
183
+ let err: StreamingValidationError | null = null;
184
+ try {
185
+ await readAndValidateManifest(entry);
186
+ } catch (e) {
187
+ err = e as StreamingValidationError;
188
+ }
189
+ await drainRest();
190
+
191
+ expect(err).toBeInstanceOf(StreamingValidationError);
192
+ expect(err?.code).toBe("manifest_not_first");
193
+ });
194
+
195
+ test("throws manifest_too_large and fails fast before draining the whole body", async () => {
196
+ // Fake a tar entry whose body would emit 5 MiB if fully drained. The
197
+ // validator must destroy() the stream and throw the moment the running
198
+ // byte count crosses the 1 MiB cap — it must NOT keep pulling chunks.
199
+ //
200
+ // We count bytes emitted via a _read implementation, and after the
201
+ // throw assert both that destroy() fired and that far fewer than 5 MiB
202
+ // were ever pulled out of the stream.
203
+ const CHUNK = 512 * 1024; // 512 KiB
204
+ const TOTAL_CHUNKS = 10; // 5 MiB worth — way past the 1 MiB cap
205
+ let chunksEmitted = 0;
206
+ let bytesEmitted = 0;
207
+ const body = new Readable({
208
+ read() {
209
+ if (chunksEmitted >= TOTAL_CHUNKS) {
210
+ this.push(null);
211
+ return;
212
+ }
213
+ const buf = Buffer.alloc(CHUNK, 0x20);
214
+ chunksEmitted += 1;
215
+ bytesEmitted += buf.length;
216
+ this.push(buf);
217
+ },
218
+ });
219
+
220
+ const entry: StreamedTarEntry = {
221
+ header: {
222
+ name: "manifest.json",
223
+ size: CHUNK * TOTAL_CHUNKS,
224
+ type: "file",
225
+ },
226
+ body,
227
+ };
228
+
229
+ let err: StreamingValidationError | null = null;
230
+ try {
231
+ await readAndValidateManifest(entry);
232
+ } catch (e) {
233
+ err = e as StreamingValidationError;
234
+ }
235
+
236
+ expect(err).toBeInstanceOf(StreamingValidationError);
237
+ expect(err?.code).toBe("manifest_too_large");
238
+ // Fail-fast assertions: destroy() was called, and we didn't drain past
239
+ // ~1 MiB + one chunk. If the validator had drained to EOF we'd see the
240
+ // full 5 MiB / 10 chunks here.
241
+ expect(body.destroyed).toBe(true);
242
+ expect(chunksEmitted).toBeLessThanOrEqual(3);
243
+ expect(bytesEmitted).toBeLessThan(2 * 1024 * 1024);
244
+ });
245
+
246
+ test("throws manifest_malformed when manifest body is not valid JSON", async () => {
247
+ const archive = buildRawVBundle([
248
+ { name: "manifest.json", data: new TextEncoder().encode("{not-json") },
249
+ ]);
250
+ const { entry, drainRest } = await firstEntryOf(archive);
251
+
252
+ let err: StreamingValidationError | null = null;
253
+ try {
254
+ await readAndValidateManifest(entry);
255
+ } catch (e) {
256
+ err = e as StreamingValidationError;
257
+ }
258
+ await drainRest();
259
+
260
+ expect(err).toBeInstanceOf(StreamingValidationError);
261
+ expect(err?.code).toBe("manifest_malformed");
262
+ });
263
+
264
+ test("throws manifest_schema when a required field is missing", async () => {
265
+ // Valid JSON but missing `files`, `manifest_sha256`, etc.
266
+ const bogus = new TextEncoder().encode(
267
+ JSON.stringify({ schema_version: "1.0", created_at: "now" }),
268
+ );
269
+ const archive = buildRawVBundle([{ name: "manifest.json", data: bogus }]);
270
+ const { entry, drainRest } = await firstEntryOf(archive);
271
+
272
+ let err: StreamingValidationError | null = null;
273
+ try {
274
+ await readAndValidateManifest(entry);
275
+ } catch (e) {
276
+ err = e as StreamingValidationError;
277
+ }
278
+ await drainRest();
279
+
280
+ expect(err).toBeInstanceOf(StreamingValidationError);
281
+ expect(err?.code).toBe("manifest_schema");
282
+ });
283
+
284
+ test("throws manifest_sha256 when the declared digest doesn't match canonical JSON", async () => {
285
+ const badManifest = {
286
+ schema_version: "1.0",
287
+ created_at: new Date().toISOString(),
288
+ files: [],
289
+ // Deliberately wrong digest. The canonical hash of a 4-field manifest
290
+ // won't match this, regardless of ordering.
291
+ manifest_sha256:
292
+ "0000000000000000000000000000000000000000000000000000000000000000",
293
+ };
294
+ const archive = buildRawVBundle([
295
+ {
296
+ name: "manifest.json",
297
+ data: new TextEncoder().encode(JSON.stringify(badManifest)),
298
+ },
299
+ ]);
300
+ const { entry, drainRest } = await firstEntryOf(archive);
301
+
302
+ let err: StreamingValidationError | null = null;
303
+ try {
304
+ await readAndValidateManifest(entry);
305
+ } catch (e) {
306
+ err = e as StreamingValidationError;
307
+ }
308
+ await drainRest();
309
+
310
+ expect(err).toBeInstanceOf(StreamingValidationError);
311
+ expect(err?.code).toBe("manifest_sha256");
312
+ });
313
+
314
+ test("throws manifest_duplicate_path when the same archive path appears twice", async () => {
315
+ const baseManifest = {
316
+ schema_version: "1.0",
317
+ created_at: new Date().toISOString(),
318
+ files: [
319
+ {
320
+ path: "workspace/a.txt",
321
+ sha256:
322
+ "1111111111111111111111111111111111111111111111111111111111111111",
323
+ size: 10,
324
+ },
325
+ {
326
+ // Deliberately duplicate path — malicious bundle could exploit this
327
+ // to bypass per-entry integrity checks if we silently collapsed.
328
+ path: "workspace/a.txt",
329
+ sha256:
330
+ "2222222222222222222222222222222222222222222222222222222222222222",
331
+ size: 20,
332
+ },
333
+ ],
334
+ manifest_sha256: "",
335
+ };
336
+ baseManifest.manifest_sha256 = computeManifestSha256(baseManifest);
337
+
338
+ const archive = buildRawVBundle([
339
+ {
340
+ name: "manifest.json",
341
+ data: new TextEncoder().encode(JSON.stringify(baseManifest)),
342
+ },
343
+ ]);
344
+ const { entry, drainRest } = await firstEntryOf(archive);
345
+
346
+ let err: StreamingValidationError | null = null;
347
+ try {
348
+ await readAndValidateManifest(entry);
349
+ } catch (e) {
350
+ err = e as StreamingValidationError;
351
+ }
352
+ await drainRest();
353
+
354
+ expect(err).toBeInstanceOf(StreamingValidationError);
355
+ expect(err?.code).toBe("manifest_duplicate_path");
356
+ expect(err?.message).toContain("workspace/a.txt");
357
+ });
358
+ });
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // createHashVerifier
362
+ // ---------------------------------------------------------------------------
363
+
364
+ describe("createHashVerifier — identity + integrity", () => {
365
+ const payload = Buffer.from(
366
+ "the quick brown fox jumps over the lazy dog".repeat(100),
367
+ "utf8",
368
+ );
369
+ // Precomputed digest for the payload above.
370
+ const payloadSha = createHash("sha256").update(payload).digest("hex");
371
+
372
+ test("is an identity Transform for correct inputs", async () => {
373
+ const verifier = createHashVerifier({
374
+ sha256: payloadSha,
375
+ size: payload.length,
376
+ archivePath: "workspace/ok.txt",
377
+ });
378
+
379
+ // Feed the payload in two chunks to exercise multi-call _transform.
380
+ const half = payload.length >>> 1;
381
+ const source = Readable.from([
382
+ payload.subarray(0, half),
383
+ payload.subarray(half),
384
+ ]);
385
+
386
+ const collected: Buffer[] = [];
387
+ verifier.on("data", (chunk: Buffer) => collected.push(chunk));
388
+
389
+ await pipeline(source, verifier);
390
+
391
+ const out = Buffer.concat(collected);
392
+ expect(out.length).toBe(payload.length);
393
+ expect(out.equals(payload)).toBe(true);
394
+ });
395
+
396
+ test("errors with code entry_hash on digest mismatch", async () => {
397
+ const verifier = createHashVerifier({
398
+ sha256:
399
+ // Wrong digest.
400
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
401
+ size: payload.length,
402
+ archivePath: "workspace/bad-hash.txt",
403
+ });
404
+
405
+ let err: StreamingValidationError | null = null;
406
+ try {
407
+ await pipeline(
408
+ Readable.from([payload]),
409
+ verifier,
410
+ async function* (source: AsyncIterable<Buffer>) {
411
+ // Drain the transform so _flush runs.
412
+ for await (const _chunk of source) {
413
+ // discard
414
+ }
415
+ },
416
+ );
417
+ } catch (e) {
418
+ err = e as StreamingValidationError;
419
+ }
420
+
421
+ expect(err).toBeInstanceOf(StreamingValidationError);
422
+ expect(err?.code).toBe("entry_hash");
423
+ expect(err?.archivePath).toBe("workspace/bad-hash.txt");
424
+ });
425
+
426
+ test("errors with code entry_size on byte-count mismatch", async () => {
427
+ // Right digest, wrong declared size.
428
+ const verifier = createHashVerifier({
429
+ sha256: payloadSha,
430
+ size: payload.length + 1,
431
+ archivePath: "workspace/bad-size.txt",
432
+ });
433
+
434
+ let err: StreamingValidationError | null = null;
435
+ try {
436
+ await pipeline(
437
+ Readable.from([payload]),
438
+ verifier,
439
+ async function* (source: AsyncIterable<Buffer>) {
440
+ for await (const _chunk of source) {
441
+ // discard
442
+ }
443
+ },
444
+ );
445
+ } catch (e) {
446
+ err = e as StreamingValidationError;
447
+ }
448
+
449
+ expect(err).toBeInstanceOf(StreamingValidationError);
450
+ expect(err?.code).toBe("entry_size");
451
+ expect(err?.archivePath).toBe("workspace/bad-size.txt");
452
+ });
453
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Tests for `parseVBundleStream` — the streaming tar reader for `.vbundle`
3
+ * archives.
4
+ *
5
+ * Covered:
6
+ * - Happy path: 3-file archive yields entries in order with correct names/sizes.
7
+ * - Manifest-first invariant: first entry is `manifest.json`.
8
+ * - Truncated gzip mid-stream: generator throws.
9
+ * - Valid gzip but malformed tar payload: generator throws.
10
+ * - Early termination (break in for-await loop): upstream source is destroyed.
11
+ */
12
+
13
+ import { Readable } from "node:stream";
14
+ import { gzipSync } from "node:zlib";
15
+ import { describe, expect, test } from "bun:test";
16
+
17
+ import { buildVBundle } from "../vbundle-builder.js";
18
+ import {
19
+ parseVBundleStream,
20
+ type StreamedTarEntry,
21
+ } from "../vbundle-tar-stream.js";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ async function collectBody(entry: StreamedTarEntry): Promise<Uint8Array> {
28
+ const chunks: Buffer[] = [];
29
+ for await (const chunk of entry.body) {
30
+ chunks.push(chunk as Buffer);
31
+ }
32
+ return new Uint8Array(Buffer.concat(chunks));
33
+ }
34
+
35
+ function readableFromBuffer(buf: Uint8Array): Readable {
36
+ // `Readable.from(Uint8Array)` iterates byte-by-byte (each element becomes
37
+ // a chunk), which is not what we want here. Wrap in an array so the whole
38
+ // buffer arrives as a single chunk — closer to how the HTTP client will
39
+ // feed bytes in production.
40
+ return Readable.from([Buffer.from(buf)]);
41
+ }
42
+
43
+ /** Build a minimal vbundle archive with the given extra files (plus manifest). */
44
+ function buildMinimalVBundle(
45
+ extraFiles: { path: string; data: Uint8Array }[],
46
+ ): Uint8Array {
47
+ const { archive } = buildVBundle({
48
+ files: extraFiles,
49
+ source: "test",
50
+ description: "test bundle",
51
+ });
52
+ return archive;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Happy path
57
+ // ---------------------------------------------------------------------------
58
+
59
+ describe("parseVBundleStream — happy path", () => {
60
+ test("yields entries in order with correct names and sizes", async () => {
61
+ const fileA = new TextEncoder().encode("alpha\n");
62
+ const fileB = new TextEncoder().encode("beta beta\n");
63
+ const fileC = new TextEncoder().encode("gamma gamma gamma\n");
64
+
65
+ const archive = buildMinimalVBundle([
66
+ { path: "workspace/a.txt", data: fileA },
67
+ { path: "workspace/b.txt", data: fileB },
68
+ { path: "workspace/c.txt", data: fileC },
69
+ ]);
70
+
71
+ const source = readableFromBuffer(archive);
72
+ const seen: { name: string; size: number; body: Uint8Array }[] = [];
73
+
74
+ for await (const entry of parseVBundleStream(source)) {
75
+ const body = await collectBody(entry);
76
+ seen.push({
77
+ name: entry.header.name,
78
+ size: entry.header.size,
79
+ body,
80
+ });
81
+ }
82
+
83
+ // manifest.json is emitted first, then the 3 files in insertion order.
84
+ expect(seen.length).toBe(4);
85
+ expect(seen[0]?.name).toBe("manifest.json");
86
+ expect(seen[1]?.name).toBe("workspace/a.txt");
87
+ expect(seen[2]?.name).toBe("workspace/b.txt");
88
+ expect(seen[3]?.name).toBe("workspace/c.txt");
89
+
90
+ // Sizes in the header match the body lengths.
91
+ expect(seen[1]?.size).toBe(fileA.length);
92
+ expect(seen[1]?.body.length).toBe(fileA.length);
93
+ expect(seen[2]?.size).toBe(fileB.length);
94
+ expect(seen[2]?.body.length).toBe(fileB.length);
95
+ expect(seen[3]?.size).toBe(fileC.length);
96
+ expect(seen[3]?.body.length).toBe(fileC.length);
97
+
98
+ // Body contents round-trip correctly.
99
+ expect(new TextDecoder().decode(seen[1]?.body)).toBe("alpha\n");
100
+ expect(new TextDecoder().decode(seen[2]?.body)).toBe("beta beta\n");
101
+ expect(new TextDecoder().decode(seen[3]?.body)).toBe("gamma gamma gamma\n");
102
+ });
103
+ });
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Manifest-first invariant
107
+ // ---------------------------------------------------------------------------
108
+
109
+ describe("parseVBundleStream — manifest-first", () => {
110
+ test("first entry is manifest.json", async () => {
111
+ const archive = buildMinimalVBundle([
112
+ { path: "z-last.txt", data: new TextEncoder().encode("zzz") },
113
+ ]);
114
+
115
+ const iter = parseVBundleStream(readableFromBuffer(archive));
116
+ const first = await iter.next();
117
+ expect(first.done).toBe(false);
118
+ expect(first.value?.header.name).toBe("manifest.json");
119
+ expect(first.value?.header.type).toBe("file");
120
+
121
+ // Drain the manifest body so the iterator can advance, then finish.
122
+ if (first.value) await collectBody(first.value);
123
+ // Exhaust the iterator to release resources. Drain each body so the
124
+ // extractor can advance. (We don't assert on the remaining entries here.)
125
+ for await (const entry of iter) {
126
+ await collectBody(entry);
127
+ }
128
+ });
129
+ });
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Error handling
133
+ // ---------------------------------------------------------------------------
134
+
135
+ describe("parseVBundleStream — errors", () => {
136
+ test("throws on truncated gzip mid-stream", async () => {
137
+ const archive = buildMinimalVBundle([
138
+ { path: "workspace/a.txt", data: new TextEncoder().encode("hello") },
139
+ ]);
140
+
141
+ // Lop off the trailing bytes of the gzip member so the decoder fails
142
+ // partway through.
143
+ const truncated = archive.subarray(0, Math.max(32, archive.length - 20));
144
+ const source = readableFromBuffer(new Uint8Array(truncated));
145
+
146
+ let threw = false;
147
+ try {
148
+ for await (const entry of parseVBundleStream(source)) {
149
+ // Drain whatever the decoder can produce before the error hits.
150
+ try {
151
+ await collectBody(entry);
152
+ } catch {
153
+ // Body streams may error mid-drain; that also counts as a failure
154
+ // in the outer iterator on the next advance.
155
+ threw = true;
156
+ break;
157
+ }
158
+ }
159
+ } catch {
160
+ threw = true;
161
+ }
162
+
163
+ expect(threw).toBe(true);
164
+ });
165
+
166
+ test("throws on valid gzip wrapping malformed tar bytes", async () => {
167
+ // Gzipped junk — valid gzip member, but the inflated bytes aren't a tar.
168
+ const junk = new TextEncoder().encode("this is not a tar archive payload");
169
+ // Pad out so the extractor has enough bytes to attempt a header parse.
170
+ const padded = new Uint8Array(1024);
171
+ padded.set(junk, 0);
172
+ const gz = gzipSync(padded);
173
+ const source = readableFromBuffer(new Uint8Array(gz));
174
+
175
+ let threw = false;
176
+ try {
177
+ for await (const entry of parseVBundleStream(source)) {
178
+ try {
179
+ await collectBody(entry);
180
+ } catch {
181
+ threw = true;
182
+ break;
183
+ }
184
+ }
185
+ } catch {
186
+ threw = true;
187
+ }
188
+
189
+ expect(threw).toBe(true);
190
+ });
191
+ });
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Early-termination cleanup
195
+ // ---------------------------------------------------------------------------
196
+
197
+ describe("parseVBundleStream — cleanup", () => {
198
+ test("destroys upstream source when caller breaks out of the loop", async () => {
199
+ const archive = buildMinimalVBundle([
200
+ { path: "workspace/a.txt", data: new TextEncoder().encode("hello") },
201
+ { path: "workspace/b.txt", data: new TextEncoder().encode("world") },
202
+ ]);
203
+
204
+ const source = readableFromBuffer(archive);
205
+ let destroyCalls = 0;
206
+ const originalDestroy = source.destroy.bind(source);
207
+ source.destroy = ((err?: Error) => {
208
+ destroyCalls += 1;
209
+ return originalDestroy(err);
210
+ }) as typeof source.destroy;
211
+
212
+ for await (const entry of parseVBundleStream(source)) {
213
+ // Consume just the first entry (manifest.json), then bail.
214
+ await collectBody(entry);
215
+ break;
216
+ }
217
+
218
+ expect(destroyCalls).toBeGreaterThan(0);
219
+ // Source should now be destroyed (no dangling listeners keeping it alive).
220
+ expect(source.destroyed).toBe(true);
221
+ });
222
+ });