@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,288 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import type { SwarmTaskNode } from "../swarm/types.js";
4
- import type {
5
- SwarmWorkerBackend,
6
- SwarmWorkerBackendInput,
7
- } from "../swarm/worker-backend.js";
8
- import {
9
- buildWorkerPrompt,
10
- parseWorkerOutput,
11
- } from "../swarm/worker-prompts.js";
12
- import type { WorkerStatusKind } from "../swarm/worker-runner.js";
13
- import { runWorkerTask } from "../swarm/worker-runner.js";
14
-
15
- function makeTask(overrides?: Partial<SwarmTaskNode>): SwarmTaskNode {
16
- return {
17
- id: "test-task",
18
- role: "coder",
19
- objective: "Write a function",
20
- dependencies: [],
21
- ...overrides,
22
- };
23
- }
24
-
25
- function makeBackend(
26
- overrides?: Partial<SwarmWorkerBackend>,
27
- ): SwarmWorkerBackend {
28
- return {
29
- name: "test-backend",
30
- isAvailable: () => true,
31
- runTask: async () => ({
32
- success: true,
33
- output:
34
- '```json\n{"summary":"Done","artifacts":[],"issues":[],"nextSteps":[]}\n```',
35
- durationMs: 100,
36
- }),
37
- ...overrides,
38
- };
39
- }
40
-
41
- describe("runWorkerTask", () => {
42
- test("returns completed result on success", async () => {
43
- const result = await runWorkerTask({
44
- task: makeTask(),
45
- backend: makeBackend(),
46
- workingDir: "/tmp",
47
- timeoutMs: 5000,
48
- });
49
- expect(result.status).toBe("completed");
50
- expect(result.taskId).toBe("test-task");
51
- expect(result.summary).toBe("Done");
52
- expect(result.durationMs).toBe(100);
53
- });
54
-
55
- test("returns failed result when backend is unavailable", async () => {
56
- const result = await runWorkerTask({
57
- task: makeTask(),
58
- backend: makeBackend({ isAvailable: () => false }),
59
- workingDir: "/tmp",
60
- timeoutMs: 5000,
61
- });
62
- expect(result.status).toBe("failed");
63
- expect(result.issues[0]).toContain("unavailable");
64
- });
65
-
66
- test("returns failed result when backend returns failure", async () => {
67
- const result = await runWorkerTask({
68
- task: makeTask(),
69
- backend: makeBackend({
70
- runTask: async () => ({
71
- success: false,
72
- output: "Something went wrong",
73
- failureReason: "timeout",
74
- durationMs: 900,
75
- }),
76
- }),
77
- workingDir: "/tmp",
78
- timeoutMs: 5000,
79
- });
80
- expect(result.status).toBe("failed");
81
- expect(result.issues).toContain("timeout");
82
- expect(result.durationMs).toBe(900);
83
- });
84
-
85
- test("returns failed result when backend throws", async () => {
86
- const result = await runWorkerTask({
87
- task: makeTask(),
88
- backend: makeBackend({
89
- runTask: async () => {
90
- throw new Error("Boom");
91
- },
92
- }),
93
- workingDir: "/tmp",
94
- timeoutMs: 5000,
95
- });
96
- expect(result.status).toBe("failed");
97
- expect(result.summary).toContain("Boom");
98
- });
99
-
100
- test("emits status callbacks in order", async () => {
101
- const statuses: WorkerStatusKind[] = [];
102
- await runWorkerTask({
103
- task: makeTask(),
104
- backend: makeBackend(),
105
- workingDir: "/tmp",
106
- timeoutMs: 5000,
107
- onStatus: (_taskId, status) => statuses.push(status),
108
- });
109
- expect(statuses).toEqual(["queued", "running", "completed"]);
110
- });
111
-
112
- test("emits queued then failed when backend unavailable", async () => {
113
- const statuses: WorkerStatusKind[] = [];
114
- await runWorkerTask({
115
- task: makeTask(),
116
- backend: makeBackend({ isAvailable: () => false }),
117
- workingDir: "/tmp",
118
- timeoutMs: 5000,
119
- onStatus: (_taskId, status) => statuses.push(status),
120
- });
121
- expect(statuses).toEqual(["queued", "failed"]);
122
- });
123
-
124
- test("continues execution when onStatus callback throws", async () => {
125
- const result = await runWorkerTask({
126
- task: makeTask(),
127
- backend: makeBackend(),
128
- workingDir: "/tmp",
129
- timeoutMs: 5000,
130
- onStatus: () => {
131
- throw new Error("status callback failed");
132
- },
133
- });
134
- expect(result.status).toBe("completed");
135
- expect(result.summary).toBe("Done");
136
- });
137
-
138
- test("maps role to correct profile in prompt", async () => {
139
- let capturedInput: SwarmWorkerBackendInput | null = null;
140
- await runWorkerTask({
141
- task: makeTask({ role: "researcher" }),
142
- backend: makeBackend({
143
- runTask: async (input) => {
144
- capturedInput = input;
145
- return { success: true, output: "ok", durationMs: 50 };
146
- },
147
- }),
148
- workingDir: "/tmp",
149
- timeoutMs: 5000,
150
- });
151
- expect(capturedInput!.profile).toBe("researcher");
152
- });
153
-
154
- test("includes dependency outputs in prompt", async () => {
155
- let capturedInput: SwarmWorkerBackendInput | null = null;
156
- await runWorkerTask({
157
- task: makeTask(),
158
- dependencyOutputs: [{ taskId: "dep-1", summary: "Research complete" }],
159
- backend: makeBackend({
160
- runTask: async (input) => {
161
- capturedInput = input;
162
- return { success: true, output: "ok", durationMs: 50 };
163
- },
164
- }),
165
- workingDir: "/tmp",
166
- timeoutMs: 5000,
167
- });
168
- expect(capturedInput!.prompt).toContain("dep-1");
169
- expect(capturedInput!.prompt).toContain("Research complete");
170
- });
171
- });
172
-
173
- describe("parseWorkerOutput", () => {
174
- test("parses valid fenced JSON", () => {
175
- const raw =
176
- 'Some preamble\n```json\n{"summary":"Done","artifacts":["file.ts"],"issues":[],"nextSteps":["test"]}\n```\nSome epilogue';
177
- const result = parseWorkerOutput(raw);
178
- expect(result.summary).toBe("Done");
179
- expect(result.artifacts).toEqual(["file.ts"]);
180
- expect(result.nextSteps).toEqual(["test"]);
181
- });
182
-
183
- test("falls back to raw summary on invalid JSON", () => {
184
- const raw = "```json\n{invalid json}\n```";
185
- const result = parseWorkerOutput(raw);
186
- expect(result.summary).toBe(raw.slice(0, 500));
187
- expect(result.artifacts).toEqual([]);
188
- });
189
-
190
- test("falls back to raw summary when no JSON block", () => {
191
- const raw = "Just a plain text output without any JSON.";
192
- const result = parseWorkerOutput(raw);
193
- expect(result.summary).toBe(raw);
194
- expect(result.artifacts).toEqual([]);
195
- });
196
-
197
- test("truncates long raw output to 500 chars", () => {
198
- const raw = "x".repeat(1000);
199
- const result = parseWorkerOutput(raw);
200
- expect(result.summary.length).toBe(500);
201
- });
202
-
203
- test("uses the final fenced JSON block when multiple are present", () => {
204
- const raw = [
205
- "```json",
206
- '{"summary":"intermediate","artifacts":["a.ts"],"issues":[],"nextSteps":[]}',
207
- "```",
208
- "",
209
- "```json",
210
- '{"summary":"final","artifacts":["b.ts"],"issues":["warn"],"nextSteps":["ship"]}',
211
- "```",
212
- ].join("\n");
213
- const result = parseWorkerOutput(raw);
214
- expect(result.summary).toBe("final");
215
- expect(result.artifacts).toEqual(["b.ts"]);
216
- expect(result.issues).toEqual(["warn"]);
217
- expect(result.nextSteps).toEqual(["ship"]);
218
- });
219
-
220
- test("skips trailing non-contract JSON and picks the last valid block", () => {
221
- const raw = [
222
- "```json",
223
- '{"summary":"real result","artifacts":["out.ts"],"issues":[],"nextSteps":["deploy"]}',
224
- "```",
225
- "",
226
- "Here is an example config:",
227
- "```json",
228
- '{"port":3000,"debug":true}',
229
- "```",
230
- ].join("\n");
231
- const result = parseWorkerOutput(raw);
232
- expect(result.summary).toBe("real result");
233
- expect(result.artifacts).toEqual(["out.ts"]);
234
- expect(result.nextSteps).toEqual(["deploy"]);
235
- });
236
-
237
- test("skips trailing malformed JSON and picks an earlier valid block", () => {
238
- const raw = [
239
- "```json",
240
- '{"summary":"good","artifacts":[],"issues":[],"nextSteps":[]}',
241
- "```",
242
- "",
243
- "```json",
244
- "{this is not valid json}",
245
- "```",
246
- ].join("\n");
247
- const result = parseWorkerOutput(raw);
248
- expect(result.summary).toBe("good");
249
- });
250
- });
251
-
252
- describe("buildWorkerPrompt", () => {
253
- test("includes role and objective", () => {
254
- const prompt = buildWorkerPrompt({
255
- role: "coder",
256
- objective: "Build feature X",
257
- });
258
- expect(prompt).toContain("coder");
259
- expect(prompt).toContain("Build feature X");
260
- });
261
-
262
- test("includes upstream context when provided", () => {
263
- const prompt = buildWorkerPrompt({
264
- role: "coder",
265
- objective: "Build it",
266
- upstreamContext: "This is a React project",
267
- });
268
- expect(prompt).toContain("This is a React project");
269
- });
270
-
271
- test("includes dependency outputs when provided", () => {
272
- const prompt = buildWorkerPrompt({
273
- role: "coder",
274
- objective: "Build it",
275
- dependencyOutputs: [
276
- { taskId: "research", summary: "Found the API docs" },
277
- ],
278
- });
279
- expect(prompt).toContain("research");
280
- expect(prompt).toContain("Found the API docs");
281
- });
282
-
283
- test("includes output contract instructions", () => {
284
- const prompt = buildWorkerPrompt({ role: "coder", objective: "Test" });
285
- expect(prompt).toContain("```json");
286
- expect(prompt).toContain("summary");
287
- });
288
- });
@@ -1,396 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import {
7
- discoverCCCommands,
8
- getCCCommand,
9
- invalidateCCCommandCache,
10
- loadCCCommandTemplate,
11
- } from "../cc-command-registry.js";
12
-
13
- let tmpDir: string;
14
-
15
- beforeEach(() => {
16
- tmpDir = mkdtempSync(join(tmpdir(), "cc-cmd-test-"));
17
- invalidateCCCommandCache();
18
- });
19
-
20
- afterEach(() => {
21
- rmSync(tmpDir, { recursive: true, force: true });
22
- invalidateCCCommandCache();
23
- });
24
-
25
- /** Helper to create a .claude/commands/ directory with markdown files. */
26
- function createCommandsDir(base: string, files: Record<string, string>): void {
27
- const commandsDir = join(base, ".claude", "commands");
28
- mkdirSync(commandsDir, { recursive: true });
29
- for (const [name, content] of Object.entries(files)) {
30
- writeFileSync(join(commandsDir, name), content, "utf-8");
31
- }
32
- }
33
-
34
- describe("discoverCCCommands", () => {
35
- test("discovers commands in .claude/commands/", () => {
36
- createCommandsDir(tmpDir, {
37
- "hello.md": "# Hello World\nThis is the hello command.",
38
- "deploy.md": "Deploy the application to production.",
39
- });
40
-
41
- const registry = discoverCCCommands(tmpDir);
42
- expect(registry.entries.size).toBe(2);
43
-
44
- const hello = registry.entries.get("hello");
45
- expect(hello).toBeDefined();
46
- expect(hello!.name).toBe("hello");
47
- expect(hello!.summary).toBe("Hello World");
48
- expect(hello!.source).toBe(tmpDir);
49
-
50
- const deploy = registry.entries.get("deploy");
51
- expect(deploy).toBeDefined();
52
- expect(deploy!.name).toBe("deploy");
53
- expect(deploy!.summary).toBe("Deploy the application to production.");
54
- });
55
-
56
- test("child directory commands override parent on name collisions", () => {
57
- // Create parent commands
58
- createCommandsDir(tmpDir, {
59
- "shared.md": "Parent version of shared command.",
60
- });
61
-
62
- // Create child directory with overriding command
63
- const childDir = join(tmpDir, "project");
64
- mkdirSync(childDir, { recursive: true });
65
- createCommandsDir(childDir, {
66
- "shared.md": "Child version of shared command.",
67
- });
68
-
69
- const registry = discoverCCCommands(childDir);
70
- const shared = registry.entries.get("shared");
71
- expect(shared).toBeDefined();
72
- expect(shared!.summary).toBe("Child version of shared command.");
73
- expect(shared!.source).toBe(childDir);
74
- });
75
-
76
- test("invalid filenames are skipped", () => {
77
- createCommandsDir(tmpDir, {
78
- "valid-name.md": "A valid command.",
79
- ".hidden.md": "Hidden file should be skipped.",
80
- "-starts-with-dash.md": "Invalid start character.",
81
- });
82
-
83
- const registry = discoverCCCommands(tmpDir);
84
- expect(registry.entries.size).toBe(1);
85
- expect(registry.entries.has("valid-name")).toBe(true);
86
- expect(registry.entries.has(".hidden")).toBe(false);
87
- expect(registry.entries.has("-starts-with-dash")).toBe(false);
88
- });
89
-
90
- test("non-.md files are ignored", () => {
91
- createCommandsDir(tmpDir, {
92
- "readme.txt": "Not a markdown file.",
93
- "command.md": "A real command.",
94
- "notes.json": "{}",
95
- });
96
-
97
- const registry = discoverCCCommands(tmpDir);
98
- expect(registry.entries.size).toBe(1);
99
- expect(registry.entries.has("command")).toBe(true);
100
- });
101
-
102
- test("empty directory returns empty registry", () => {
103
- const commandsDir = join(tmpDir, ".claude", "commands");
104
- mkdirSync(commandsDir, { recursive: true });
105
-
106
- const registry = discoverCCCommands(tmpDir);
107
- expect(registry.entries.size).toBe(0);
108
- });
109
-
110
- test("no .claude/commands/ directory returns empty registry", () => {
111
- const registry = discoverCCCommands(tmpDir);
112
- expect(registry.entries.size).toBe(0);
113
- });
114
-
115
- test("commands from multiple ancestor levels are merged", () => {
116
- // Parent has a unique command
117
- createCommandsDir(tmpDir, {
118
- "parent-only.md": "Only in parent.",
119
- });
120
-
121
- // Child has a different command
122
- const childDir = join(tmpDir, "child");
123
- mkdirSync(childDir, { recursive: true });
124
- createCommandsDir(childDir, {
125
- "child-only.md": "Only in child.",
126
- });
127
-
128
- const registry = discoverCCCommands(childDir);
129
- expect(registry.entries.size).toBe(2);
130
- expect(registry.entries.has("parent-only")).toBe(true);
131
- expect(registry.entries.has("child-only")).toBe(true);
132
- });
133
- });
134
-
135
- describe("caching", () => {
136
- test("cache returns same instance within TTL", () => {
137
- createCommandsDir(tmpDir, {
138
- "test.md": "Test command.",
139
- });
140
-
141
- const first = discoverCCCommands(tmpDir);
142
- const second = discoverCCCommands(tmpDir);
143
- expect(first).toBe(second); // same object reference
144
- });
145
-
146
- test("invalidateCCCommandCache forces re-discovery", () => {
147
- createCommandsDir(tmpDir, {
148
- "test.md": "Test command.",
149
- });
150
-
151
- const first = discoverCCCommands(tmpDir);
152
-
153
- invalidateCCCommandCache();
154
-
155
- const second = discoverCCCommands(tmpDir);
156
- expect(first).not.toBe(second); // different object reference
157
- expect(second.entries.size).toBe(1);
158
- });
159
-
160
- test("expired TTL forces re-discovery", () => {
161
- createCommandsDir(tmpDir, {
162
- "test.md": "Test command.",
163
- });
164
-
165
- // Use a very short TTL
166
- const first = discoverCCCommands(tmpDir, 0);
167
- const second = discoverCCCommands(tmpDir, 0);
168
- expect(first).not.toBe(second); // different object reference due to expired TTL
169
- });
170
- });
171
-
172
- describe("getCCCommand", () => {
173
- test("looks up command by name (case-insensitive)", () => {
174
- createCommandsDir(tmpDir, {
175
- "MyCommand.md": "My command description.",
176
- });
177
-
178
- const entry = getCCCommand(tmpDir, "mycommand");
179
- expect(entry).toBeDefined();
180
- expect(entry!.name).toBe("MyCommand");
181
-
182
- const entryUpper = getCCCommand(tmpDir, "MYCOMMAND");
183
- expect(entryUpper).toBeDefined();
184
- expect(entryUpper!.name).toBe("MyCommand");
185
- });
186
-
187
- test("returns undefined for non-existent command", () => {
188
- createCommandsDir(tmpDir, {
189
- "exists.md": "I exist.",
190
- });
191
-
192
- const entry = getCCCommand(tmpDir, "nonexistent");
193
- expect(entry).toBeUndefined();
194
- });
195
- });
196
-
197
- describe("loadCCCommandTemplate", () => {
198
- test("reads full file content at execution time", () => {
199
- const fullContent =
200
- "---\ntitle: Test\n---\n\n# Test Command\n\nThis is the full template body.\n\n## Arguments\n- arg1: required\n- arg2: optional\n";
201
- createCommandsDir(tmpDir, {
202
- "test.md": fullContent,
203
- });
204
-
205
- const registry = discoverCCCommands(tmpDir);
206
- const entry = registry.entries.get("test")!;
207
- expect(entry).toBeDefined();
208
-
209
- const template = loadCCCommandTemplate(entry);
210
- expect(template).toBe(fullContent);
211
- });
212
- });
213
-
214
- describe("summary extraction", () => {
215
- test("skips YAML frontmatter", () => {
216
- createCommandsDir(tmpDir, {
217
- "with-frontmatter.md":
218
- "---\ntitle: My Command\nauthor: test\n---\n\nActual summary line.",
219
- });
220
-
221
- const registry = discoverCCCommands(tmpDir);
222
- const entry = registry.entries.get("with-frontmatter");
223
- expect(entry).toBeDefined();
224
- expect(entry!.summary).toBe("Actual summary line.");
225
- });
226
-
227
- test("strips heading markers", () => {
228
- createCommandsDir(tmpDir, {
229
- "heading.md": "## This is a heading",
230
- });
231
-
232
- const registry = discoverCCCommands(tmpDir);
233
- const entry = registry.entries.get("heading");
234
- expect(entry).toBeDefined();
235
- expect(entry!.summary).toBe("This is a heading");
236
- });
237
-
238
- test("strips multiple heading levels", () => {
239
- createCommandsDir(tmpDir, {
240
- "h1.md": "# H1 Heading",
241
- "h3.md": "### H3 Heading",
242
- });
243
-
244
- const registry = discoverCCCommands(tmpDir);
245
- expect(registry.entries.get("h1")!.summary).toBe("H1 Heading");
246
- expect(registry.entries.get("h3")!.summary).toBe("H3 Heading");
247
- });
248
-
249
- test("skips empty lines before summary", () => {
250
- createCommandsDir(tmpDir, {
251
- "empty-lines.md": "\n\n\nFirst real line.",
252
- });
253
-
254
- const registry = discoverCCCommands(tmpDir);
255
- expect(registry.entries.get("empty-lines")!.summary).toBe(
256
- "First real line.",
257
- );
258
- });
259
-
260
- test("truncates summary to 100 characters", () => {
261
- const longLine = "A".repeat(150);
262
- createCommandsDir(tmpDir, {
263
- "long.md": longLine,
264
- });
265
-
266
- const registry = discoverCCCommands(tmpDir);
267
- const entry = registry.entries.get("long");
268
- expect(entry).toBeDefined();
269
- expect(entry!.summary.length).toBe(100);
270
- expect(entry!.summary).toBe("A".repeat(100));
271
- });
272
-
273
- test("handles file with only frontmatter and no content", () => {
274
- createCommandsDir(tmpDir, {
275
- "empty-body.md": "---\ntitle: Empty\n---\n",
276
- });
277
-
278
- const registry = discoverCCCommands(tmpDir);
279
- const entry = registry.entries.get("empty-body");
280
- expect(entry).toBeDefined();
281
- expect(entry!.summary).toBe("");
282
- });
283
-
284
- test("returns empty summary when frontmatter is truncated by partial read", () => {
285
- // Simulate frontmatter that exceeds SUMMARY_READ_BYTES (1024).
286
- // The closing --- delimiter will be cut off, causing FRONTMATTER_REGEX to
287
- // fail. extractSummary should return '' instead of '---'.
288
- const largeFrontmatter =
289
- "---\n" + "key: " + "x".repeat(1100) + "\n---\n\nActual summary.";
290
- createCommandsDir(tmpDir, {
291
- "big-frontmatter.md": largeFrontmatter,
292
- });
293
-
294
- const registry = discoverCCCommands(tmpDir);
295
- const entry = registry.entries.get("big-frontmatter");
296
- expect(entry).toBeDefined();
297
- expect(entry!.summary).toBe("");
298
- });
299
-
300
- test("returns empty summary when frontmatter is truncated (CRLF)", () => {
301
- const largeFrontmatter =
302
- "---\r\n" + "key: " + "x".repeat(1100) + "\r\n---\r\n\r\nActual summary.";
303
- createCommandsDir(tmpDir, {
304
- "big-frontmatter-crlf.md": largeFrontmatter,
305
- });
306
-
307
- const registry = discoverCCCommands(tmpDir);
308
- const entry = registry.entries.get("big-frontmatter-crlf");
309
- expect(entry).toBeDefined();
310
- expect(entry!.summary).toBe("");
311
- });
312
-
313
- test("returns empty summary when frontmatter is truncated with multibyte UTF-8 characters", () => {
314
- // When frontmatter contains multibyte UTF-8 characters (e.g., CJK text),
315
- // the JavaScript string length (UTF-16 code units) is smaller than the
316
- // byte length. The truncation guard must compare byte length, not
317
- // string length, against SUMMARY_READ_BYTES (1024).
318
- //
319
- // Each CJK character is 3 bytes in UTF-8 but 1 code unit in UTF-16.
320
- // We need the total byte count to reach 1024 while string length stays
321
- // well below 1024 to exercise the bug.
322
- const cjkChars = "\u4e00".repeat(340); // 340 chars * 3 bytes = 1020 bytes
323
- // '---\n' is 4 bytes, so total = 4 + 1020 = 1024 bytes, but string
324
- // length = 4 + 340 = 344 chars — well under 1024.
325
- const truncatedContent = "---\n" + cjkChars;
326
- createCommandsDir(tmpDir, {
327
- "multibyte-frontmatter.md": truncatedContent,
328
- });
329
-
330
- const registry = discoverCCCommands(tmpDir);
331
- const entry = registry.entries.get("multibyte-frontmatter");
332
- expect(entry).toBeDefined();
333
- // Should return '' because the frontmatter opening delimiter is present
334
- // but the closing delimiter is missing and the byte length reached the
335
- // read limit — indicating truncation.
336
- expect(entry!.summary).toBe("");
337
- });
338
-
339
- test("returns summary for small file starting with thematic break ---", () => {
340
- // A small markdown file that starts with "---" as a thematic break (not
341
- // frontmatter) should still have its first content line extracted as a
342
- // summary, rather than being treated as truncated frontmatter.
343
- createCommandsDir(tmpDir, {
344
- "thematic-break.md":
345
- "---\nThis is a valid summary after a thematic break.",
346
- });
347
-
348
- const registry = discoverCCCommands(tmpDir);
349
- const entry = registry.entries.get("thematic-break");
350
- expect(entry).toBeDefined();
351
- expect(entry!.summary).toBe(
352
- "This is a valid summary after a thematic break.",
353
- );
354
- });
355
-
356
- test("handles frontmatter with Windows-style line endings", () => {
357
- createCommandsDir(tmpDir, {
358
- "crlf.md": "---\r\ntitle: Test\r\n---\r\n\r\nSummary with CRLF.",
359
- });
360
-
361
- const registry = discoverCCCommands(tmpDir);
362
- const entry = registry.entries.get("crlf");
363
- expect(entry).toBeDefined();
364
- expect(entry!.summary).toBe("Summary with CRLF.");
365
- });
366
- });
367
-
368
- describe("command name validation", () => {
369
- test("accepts valid names with dots, dashes, underscores", () => {
370
- createCommandsDir(tmpDir, {
371
- "my-command.md": "Dashed name.",
372
- "my_command.md": "Underscored name.",
373
- "my.command.md": "Dotted name.",
374
- "Command123.md": "Alphanumeric.",
375
- "a.md": "Single char.",
376
- });
377
-
378
- const registry = discoverCCCommands(tmpDir);
379
- expect(registry.entries.has("my-command")).toBe(true);
380
- expect(registry.entries.has("my_command")).toBe(true);
381
- expect(registry.entries.has("my.command")).toBe(true);
382
- expect(registry.entries.has("command123")).toBe(true);
383
- expect(registry.entries.has("a")).toBe(true);
384
- });
385
-
386
- test("rejects names starting with special characters", () => {
387
- createCommandsDir(tmpDir, {
388
- "_start.md": "Starts with underscore.",
389
- ".start.md": "Starts with dot.",
390
- "-start.md": "Starts with dash.",
391
- });
392
-
393
- const registry = discoverCCCommands(tmpDir);
394
- expect(registry.entries.size).toBe(0);
395
- });
396
- });