@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,704 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- import type { Provider, ProviderResponse } from "../providers/types.js";
4
-
5
- // ---------------------------------------------------------------------------
6
- // Mocks — declared before imports that depend on them
7
- // ---------------------------------------------------------------------------
8
-
9
- function makeLoggerStub(): Record<string, unknown> {
10
- const stub: Record<string, unknown> = {};
11
- for (const m of [
12
- "info",
13
- "warn",
14
- "error",
15
- "debug",
16
- "trace",
17
- "fatal",
18
- "silent",
19
- "child",
20
- ]) {
21
- stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
22
- }
23
- return stub;
24
- }
25
-
26
- mock.module("../util/logger.js", () => ({
27
- getLogger: () => makeLoggerStub(),
28
- }));
29
-
30
- // ---------------------------------------------------------------------------
31
- // Mock provider
32
- // ---------------------------------------------------------------------------
33
-
34
- const mockSendMessage = mock<Provider["sendMessage"]>();
35
- const mockProvider: Provider = {
36
- name: "mock-reducer-provider",
37
- sendMessage: mockSendMessage,
38
- };
39
-
40
- let providerAvailable = true;
41
-
42
- mock.module("../providers/provider-send-message.js", () => ({
43
- getConfiguredProvider: async () => (providerAvailable ? mockProvider : null),
44
- createTimeout: (ms: number) => {
45
- const controller = new AbortController();
46
- const timer = setTimeout(() => controller.abort(), ms);
47
- return {
48
- signal: controller.signal,
49
- cleanup: () => clearTimeout(timer),
50
- };
51
- },
52
- extractText: (response: ProviderResponse) => {
53
- const block = response.content.find(
54
- (b): b is Extract<(typeof response.content)[number], { type: "text" }> =>
55
- b.type === "text",
56
- );
57
- return block?.text?.trim() ?? "";
58
- },
59
- }));
60
-
61
- // ---------------------------------------------------------------------------
62
- // Import module under test AFTER mocks
63
- // ---------------------------------------------------------------------------
64
-
65
- import {
66
- buildReducerSystemPrompt,
67
- buildReducerUserMessage,
68
- type ReducerPromptInput,
69
- runReducer,
70
- } from "../memory/reducer.js";
71
- import { EMPTY_REDUCER_RESULT } from "../memory/reducer-types.js";
72
-
73
- // ---------------------------------------------------------------------------
74
- // Helpers
75
- // ---------------------------------------------------------------------------
76
-
77
- function makeInput(
78
- overrides: Partial<ReducerPromptInput> = {},
79
- ): ReducerPromptInput {
80
- return {
81
- conversationId: "conv-test-1",
82
- newMessages: [
83
- { role: "user", content: "I'm traveling to Paris next week." },
84
- {
85
- role: "assistant",
86
- content: "That sounds exciting! Do you need help planning?",
87
- },
88
- ],
89
- existingTimeContexts: [],
90
- existingOpenLoops: [],
91
- nowMs: 1700000000000,
92
- scopeId: "scope-default",
93
- ...overrides,
94
- };
95
- }
96
-
97
- function makeProviderResponse(jsonOutput: unknown): ProviderResponse {
98
- return {
99
- content: [{ type: "text", text: JSON.stringify(jsonOutput) }],
100
- model: "mock-model",
101
- usage: { inputTokens: 100, outputTokens: 50 },
102
- stopReason: "end_turn",
103
- };
104
- }
105
-
106
- // ---------------------------------------------------------------------------
107
- // Tests: buildReducerSystemPrompt
108
- // ---------------------------------------------------------------------------
109
-
110
- describe("buildReducerSystemPrompt", () => {
111
- test("contains key structural instructions", () => {
112
- const prompt = buildReducerSystemPrompt();
113
- expect(prompt).toContain("timeContexts");
114
- expect(prompt).toContain("openLoops");
115
- expect(prompt).toContain("archiveObservations");
116
- expect(prompt).toContain("archiveEpisodes");
117
- expect(prompt).toContain("JSON");
118
- });
119
- });
120
-
121
- // ---------------------------------------------------------------------------
122
- // Tests: buildReducerUserMessage
123
- // ---------------------------------------------------------------------------
124
-
125
- describe("buildReducerUserMessage", () => {
126
- test("includes current time, conversation ID, and scope", () => {
127
- const input = makeInput();
128
- const msg = buildReducerUserMessage(input);
129
- expect(msg).toContain("conv-test-1");
130
- expect(msg).toContain("scope-default");
131
- expect(msg).toContain("1700000000000");
132
- });
133
-
134
- test("includes new messages", () => {
135
- const input = makeInput();
136
- const msg = buildReducerUserMessage(input);
137
- expect(msg).toContain("[user]: I'm traveling to Paris next week.");
138
- expect(msg).toContain("[assistant]: That sounds exciting!");
139
- });
140
-
141
- test("includes existing time contexts when provided", () => {
142
- const input = makeInput({
143
- existingTimeContexts: [
144
- { id: "tc-1", summary: "User on vacation until Friday" },
145
- ],
146
- });
147
- const msg = buildReducerUserMessage(input);
148
- expect(msg).toContain("Active time contexts");
149
- expect(msg).toContain("[tc-1] User on vacation until Friday");
150
- });
151
-
152
- test("includes existing open loops when provided", () => {
153
- const input = makeInput({
154
- existingOpenLoops: [
155
- { id: "ol-1", summary: "Follow up with Bob", status: "open" },
156
- ],
157
- });
158
- const msg = buildReducerUserMessage(input);
159
- expect(msg).toContain("Active open loops");
160
- expect(msg).toContain("[ol-1] (open) Follow up with Bob");
161
- });
162
-
163
- test("omits time context section when none exist", () => {
164
- const input = makeInput({ existingTimeContexts: [] });
165
- const msg = buildReducerUserMessage(input);
166
- expect(msg).not.toContain("Active time contexts");
167
- });
168
-
169
- test("omits open loop section when none exist", () => {
170
- const input = makeInput({ existingOpenLoops: [] });
171
- const msg = buildReducerUserMessage(input);
172
- expect(msg).not.toContain("Active open loops");
173
- });
174
- });
175
-
176
- // ---------------------------------------------------------------------------
177
- // Tests: runReducer — event extraction
178
- // ---------------------------------------------------------------------------
179
-
180
- describe("runReducer — event extraction", () => {
181
- beforeEach(() => {
182
- mockSendMessage.mockClear();
183
- providerAvailable = true;
184
- });
185
-
186
- test("extracts archive observations from provider response", async () => {
187
- mockSendMessage.mockResolvedValueOnce(
188
- makeProviderResponse({
189
- archiveObservations: [
190
- {
191
- content: "User is planning a trip to Paris",
192
- role: "user",
193
- modality: "text",
194
- source: "vellum",
195
- },
196
- {
197
- content: "User prefers window seats on flights",
198
- role: "user",
199
- },
200
- ],
201
- }),
202
- );
203
-
204
- const result = await runReducer(makeInput());
205
-
206
- expect(result.archiveObservations).toHaveLength(2);
207
- expect(result.archiveObservations[0]).toEqual({
208
- content: "User is planning a trip to Paris",
209
- role: "user",
210
- modality: "text",
211
- source: "vellum",
212
- });
213
- expect(result.archiveObservations[1]).toEqual({
214
- content: "User prefers window seats on flights",
215
- role: "user",
216
- });
217
- });
218
-
219
- test("extracts archive episodes from provider response", async () => {
220
- mockSendMessage.mockResolvedValueOnce(
221
- makeProviderResponse({
222
- archiveEpisodes: [
223
- {
224
- title: "Paris trip planning",
225
- summary:
226
- "User discussed upcoming trip to Paris, mentioned interest in museums.",
227
- source: "vellum",
228
- },
229
- ],
230
- }),
231
- );
232
-
233
- const result = await runReducer(makeInput());
234
-
235
- expect(result.archiveEpisodes).toHaveLength(1);
236
- expect(result.archiveEpisodes[0]).toEqual({
237
- title: "Paris trip planning",
238
- summary:
239
- "User discussed upcoming trip to Paris, mentioned interest in museums.",
240
- source: "vellum",
241
- });
242
- });
243
- });
244
-
245
- // ---------------------------------------------------------------------------
246
- // Tests: runReducer — temporary situation extraction
247
- // ---------------------------------------------------------------------------
248
-
249
- describe("runReducer — temporary situation extraction", () => {
250
- beforeEach(() => {
251
- mockSendMessage.mockClear();
252
- providerAvailable = true;
253
- });
254
-
255
- test("extracts time context create operations", async () => {
256
- mockSendMessage.mockResolvedValueOnce(
257
- makeProviderResponse({
258
- timeContexts: [
259
- {
260
- action: "create",
261
- summary: "User traveling to Paris next week",
262
- source: "conversation",
263
- activeFrom: 1700000000000,
264
- activeUntil: 1700604800000,
265
- },
266
- ],
267
- }),
268
- );
269
-
270
- const result = await runReducer(makeInput());
271
-
272
- expect(result.timeContexts).toHaveLength(1);
273
- expect(result.timeContexts[0]).toEqual({
274
- action: "create",
275
- summary: "User traveling to Paris next week",
276
- source: "conversation",
277
- activeFrom: 1700000000000,
278
- activeUntil: 1700604800000,
279
- });
280
- });
281
-
282
- test("extracts time context update operations referencing existing IDs", async () => {
283
- mockSendMessage.mockResolvedValueOnce(
284
- makeProviderResponse({
285
- timeContexts: [
286
- {
287
- action: "update",
288
- id: "tc-existing-1",
289
- summary: "Trip extended to two weeks",
290
- activeUntil: 1701209600000,
291
- },
292
- ],
293
- }),
294
- );
295
-
296
- const result = await runReducer(
297
- makeInput({
298
- existingTimeContexts: [
299
- { id: "tc-existing-1", summary: "User traveling next week" },
300
- ],
301
- }),
302
- );
303
-
304
- expect(result.timeContexts).toHaveLength(1);
305
- expect(result.timeContexts[0]).toEqual({
306
- action: "update",
307
- id: "tc-existing-1",
308
- summary: "Trip extended to two weeks",
309
- activeUntil: 1701209600000,
310
- });
311
- });
312
- });
313
-
314
- // ---------------------------------------------------------------------------
315
- // Tests: runReducer — open-loop creation
316
- // ---------------------------------------------------------------------------
317
-
318
- describe("runReducer — open-loop creation", () => {
319
- beforeEach(() => {
320
- mockSendMessage.mockClear();
321
- providerAvailable = true;
322
- });
323
-
324
- test("extracts open loop create operations", async () => {
325
- mockSendMessage.mockResolvedValueOnce(
326
- makeProviderResponse({
327
- openLoops: [
328
- {
329
- action: "create",
330
- summary: "Book hotel in Paris",
331
- source: "conversation",
332
- dueAt: 1700172800000,
333
- },
334
- ],
335
- }),
336
- );
337
-
338
- const result = await runReducer(makeInput());
339
-
340
- expect(result.openLoops).toHaveLength(1);
341
- expect(result.openLoops[0]).toEqual({
342
- action: "create",
343
- summary: "Book hotel in Paris",
344
- source: "conversation",
345
- dueAt: 1700172800000,
346
- });
347
- });
348
-
349
- test("extracts open loop create without optional dueAt", async () => {
350
- mockSendMessage.mockResolvedValueOnce(
351
- makeProviderResponse({
352
- openLoops: [
353
- {
354
- action: "create",
355
- summary: "Research museums in Paris",
356
- source: "conversation",
357
- },
358
- ],
359
- }),
360
- );
361
-
362
- const result = await runReducer(makeInput());
363
-
364
- expect(result.openLoops).toHaveLength(1);
365
- const op = result.openLoops[0];
366
- expect(op.action).toBe("create");
367
- if (op.action === "create") {
368
- expect(op.summary).toBe("Research museums in Paris");
369
- expect(op.dueAt).toBeUndefined();
370
- }
371
- });
372
- });
373
-
374
- // ---------------------------------------------------------------------------
375
- // Tests: runReducer — explicit resolution
376
- // ---------------------------------------------------------------------------
377
-
378
- describe("runReducer — explicit resolution", () => {
379
- beforeEach(() => {
380
- mockSendMessage.mockClear();
381
- providerAvailable = true;
382
- });
383
-
384
- test("extracts time context resolve operations", async () => {
385
- mockSendMessage.mockResolvedValueOnce(
386
- makeProviderResponse({
387
- timeContexts: [
388
- {
389
- action: "resolve",
390
- id: "tc-old-1",
391
- },
392
- ],
393
- }),
394
- );
395
-
396
- const result = await runReducer(
397
- makeInput({
398
- existingTimeContexts: [
399
- { id: "tc-old-1", summary: "Conference this week" },
400
- ],
401
- }),
402
- );
403
-
404
- expect(result.timeContexts).toHaveLength(1);
405
- expect(result.timeContexts[0]).toEqual({
406
- action: "resolve",
407
- id: "tc-old-1",
408
- });
409
- });
410
-
411
- test("extracts open loop resolve with 'resolved' status", async () => {
412
- mockSendMessage.mockResolvedValueOnce(
413
- makeProviderResponse({
414
- openLoops: [
415
- {
416
- action: "resolve",
417
- id: "ol-done-1",
418
- status: "resolved",
419
- },
420
- ],
421
- }),
422
- );
423
-
424
- const result = await runReducer(
425
- makeInput({
426
- existingOpenLoops: [
427
- { id: "ol-done-1", summary: "Send report to Alice", status: "open" },
428
- ],
429
- }),
430
- );
431
-
432
- expect(result.openLoops).toHaveLength(1);
433
- expect(result.openLoops[0]).toEqual({
434
- action: "resolve",
435
- id: "ol-done-1",
436
- status: "resolved",
437
- });
438
- });
439
-
440
- test("extracts open loop resolve with 'expired' status", async () => {
441
- mockSendMessage.mockResolvedValueOnce(
442
- makeProviderResponse({
443
- openLoops: [
444
- {
445
- action: "resolve",
446
- id: "ol-expired-1",
447
- status: "expired",
448
- },
449
- ],
450
- }),
451
- );
452
-
453
- const result = await runReducer(
454
- makeInput({
455
- existingOpenLoops: [
456
- {
457
- id: "ol-expired-1",
458
- summary: "RSVP deadline passed",
459
- status: "open",
460
- },
461
- ],
462
- }),
463
- );
464
-
465
- expect(result.openLoops).toHaveLength(1);
466
- expect(result.openLoops[0]).toEqual({
467
- action: "resolve",
468
- id: "ol-expired-1",
469
- status: "expired",
470
- });
471
- });
472
- });
473
-
474
- // ---------------------------------------------------------------------------
475
- // Tests: runReducer — provider unavailable / error handling
476
- // ---------------------------------------------------------------------------
477
-
478
- describe("runReducer — error handling", () => {
479
- beforeEach(() => {
480
- mockSendMessage.mockClear();
481
- providerAvailable = true;
482
- });
483
-
484
- test("returns EMPTY_REDUCER_RESULT when no provider is available", async () => {
485
- providerAvailable = false;
486
-
487
- const result = await runReducer(makeInput());
488
-
489
- expect(result).toBe(EMPTY_REDUCER_RESULT);
490
- expect(mockSendMessage).not.toHaveBeenCalled();
491
- });
492
-
493
- test("returns EMPTY_REDUCER_RESULT when provider throws", async () => {
494
- mockSendMessage.mockRejectedValueOnce(new Error("Provider error"));
495
-
496
- const result = await runReducer(makeInput());
497
-
498
- expect(result).toBe(EMPTY_REDUCER_RESULT);
499
- });
500
-
501
- test("returns EMPTY_REDUCER_RESULT when provider returns empty text", async () => {
502
- mockSendMessage.mockResolvedValueOnce({
503
- content: [{ type: "text", text: "" }],
504
- model: "mock-model",
505
- usage: { inputTokens: 10, outputTokens: 0 },
506
- stopReason: "end_turn",
507
- });
508
-
509
- const result = await runReducer(makeInput());
510
-
511
- expect(result).toBe(EMPTY_REDUCER_RESULT);
512
- });
513
-
514
- test("returns EMPTY_REDUCER_RESULT when provider returns invalid JSON", async () => {
515
- mockSendMessage.mockResolvedValueOnce({
516
- content: [{ type: "text", text: "not valid json at all" }],
517
- model: "mock-model",
518
- usage: { inputTokens: 10, outputTokens: 5 },
519
- stopReason: "end_turn",
520
- });
521
-
522
- const result = await runReducer(makeInput());
523
-
524
- expect(result).toBe(EMPTY_REDUCER_RESULT);
525
- });
526
-
527
- test("returns EMPTY_REDUCER_RESULT when provider returns empty object", async () => {
528
- mockSendMessage.mockResolvedValueOnce(makeProviderResponse({}));
529
-
530
- const result = await runReducer(makeInput());
531
-
532
- expect(result).toEqual({
533
- timeContexts: [],
534
- openLoops: [],
535
- archiveObservations: [],
536
- archiveEpisodes: [],
537
- });
538
- expect(result).not.toBe(EMPTY_REDUCER_RESULT);
539
- });
540
- });
541
-
542
- // ---------------------------------------------------------------------------
543
- // Tests: runReducer — provider call arguments
544
- // ---------------------------------------------------------------------------
545
-
546
- describe("runReducer — provider call arguments", () => {
547
- beforeEach(() => {
548
- mockSendMessage.mockClear();
549
- providerAvailable = true;
550
- mockSendMessage.mockResolvedValue(makeProviderResponse({}));
551
- });
552
-
553
- test("sends messages with correct structure", async () => {
554
- await runReducer(makeInput());
555
-
556
- expect(mockSendMessage).toHaveBeenCalledTimes(1);
557
- const [messages] = mockSendMessage.mock.calls[0] as Parameters<
558
- Provider["sendMessage"]
559
- >;
560
-
561
- // Should be a single user message
562
- expect(messages).toHaveLength(1);
563
- expect(messages[0].role).toBe("user");
564
- expect(messages[0].content).toHaveLength(1);
565
- expect(messages[0].content[0].type).toBe("text");
566
- });
567
-
568
- test("does not send any tool definitions", async () => {
569
- await runReducer(makeInput());
570
-
571
- const [, tools] = mockSendMessage.mock.calls[0] as Parameters<
572
- Provider["sendMessage"]
573
- >;
574
- expect(tools).toBeUndefined();
575
- });
576
-
577
- test("sends a system prompt", async () => {
578
- await runReducer(makeInput());
579
-
580
- const [, , systemPrompt] = mockSendMessage.mock.calls[0] as Parameters<
581
- Provider["sendMessage"]
582
- >;
583
- expect(systemPrompt).toBeTruthy();
584
- expect(typeof systemPrompt).toBe("string");
585
- });
586
-
587
- test("uses latency-optimized model intent", async () => {
588
- await runReducer(makeInput());
589
-
590
- const [, , , options] = mockSendMessage.mock.calls[0] as Parameters<
591
- Provider["sendMessage"]
592
- >;
593
- expect(options?.config?.modelIntent).toBe("latency-optimized");
594
- });
595
-
596
- test("passes abort signal to provider", async () => {
597
- await runReducer(makeInput());
598
-
599
- const [, , , options] = mockSendMessage.mock.calls[0] as Parameters<
600
- Provider["sendMessage"]
601
- >;
602
- expect(options?.signal).toBeDefined();
603
- expect(options?.signal).toBeInstanceOf(AbortSignal);
604
- });
605
- });
606
-
607
- // ---------------------------------------------------------------------------
608
- // Tests: runReducer — side-effect-free guarantee
609
- // ---------------------------------------------------------------------------
610
-
611
- describe("runReducer — side-effect-free", () => {
612
- beforeEach(() => {
613
- mockSendMessage.mockClear();
614
- providerAvailable = true;
615
- });
616
-
617
- test("returns typed result without performing any writes", async () => {
618
- const fullResponse = {
619
- timeContexts: [
620
- {
621
- action: "create",
622
- summary: "Paris trip",
623
- source: "conversation",
624
- activeFrom: 1700000000000,
625
- activeUntil: 1700604800000,
626
- },
627
- ],
628
- openLoops: [
629
- {
630
- action: "create",
631
- summary: "Book hotel",
632
- source: "conversation",
633
- },
634
- ],
635
- archiveObservations: [
636
- {
637
- content: "User likes museums",
638
- role: "user",
639
- },
640
- ],
641
- archiveEpisodes: [
642
- {
643
- title: "Trip planning",
644
- summary: "Discussed Paris trip",
645
- },
646
- ],
647
- };
648
-
649
- mockSendMessage.mockResolvedValueOnce(makeProviderResponse(fullResponse));
650
-
651
- const result = await runReducer(makeInput());
652
-
653
- // Verify the result is correctly typed and populated
654
- expect(result.timeContexts).toHaveLength(1);
655
- expect(result.openLoops).toHaveLength(1);
656
- expect(result.archiveObservations).toHaveLength(1);
657
- expect(result.archiveEpisodes).toHaveLength(1);
658
-
659
- // The function only called sendMessage — no other side effects
660
- expect(mockSendMessage).toHaveBeenCalledTimes(1);
661
- });
662
-
663
- test("handles mixed valid and invalid operations gracefully", async () => {
664
- mockSendMessage.mockResolvedValueOnce(
665
- makeProviderResponse({
666
- timeContexts: [
667
- // valid
668
- {
669
- action: "create",
670
- summary: "Valid context",
671
- source: "conversation",
672
- activeFrom: 1000,
673
- activeUntil: 2000,
674
- },
675
- // invalid — missing summary
676
- {
677
- action: "create",
678
- source: "conversation",
679
- activeFrom: 1000,
680
- activeUntil: 2000,
681
- },
682
- ],
683
- openLoops: [
684
- // valid
685
- {
686
- action: "create",
687
- summary: "Valid loop",
688
- source: "conversation",
689
- },
690
- // invalid — missing source
691
- { action: "create", summary: "Bad loop" },
692
- ],
693
- }),
694
- );
695
-
696
- const result = await runReducer(makeInput());
697
-
698
- // Only valid operations should be present
699
- expect(result.timeContexts).toHaveLength(1);
700
- expect(result.timeContexts[0].action).toBe("create");
701
- expect(result.openLoops).toHaveLength(1);
702
- expect(result.openLoops[0].action).toBe("create");
703
- });
704
- });