@vellumai/assistant 0.4.53 → 0.4.55

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 (255) hide show
  1. package/bun.lock +62 -349
  2. package/docs/architecture/integrations.md +1 -1
  3. package/docs/architecture/keychain-broker.md +94 -29
  4. package/docs/architecture/security.md +2 -2
  5. package/knip.json +7 -29
  6. package/package.json +2 -9
  7. package/src/__tests__/agent-loop.test.ts +1 -1
  8. package/src/__tests__/app-git-history.test.ts +0 -2
  9. package/src/__tests__/app-git-service.test.ts +1 -6
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -6
  13. package/src/__tests__/call-domain.test.ts +0 -1
  14. package/src/__tests__/call-routes-http.test.ts +0 -1
  15. package/src/__tests__/channel-guardian.test.ts +4 -4
  16. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  17. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  18. package/src/__tests__/checker.test.ts +13 -11
  19. package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +0 -3
  22. package/src/__tests__/config-schema.test.ts +3 -9
  23. package/src/__tests__/config-watcher.test.ts +11 -3
  24. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  25. package/src/__tests__/credential-broker-server-use.test.ts +60 -24
  26. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  27. package/src/__tests__/credential-security-invariants.test.ts +13 -8
  28. package/src/__tests__/credential-vault-unit.test.ts +28 -12
  29. package/src/__tests__/credential-vault.test.ts +40 -28
  30. package/src/__tests__/credentials-cli.test.ts +1 -21
  31. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  32. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  34. package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
  35. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  36. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  37. package/src/__tests__/guardian-action-store.test.ts +0 -57
  38. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  39. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  40. package/src/__tests__/hooks-blocking.test.ts +1 -1
  41. package/src/__tests__/hooks-config.test.ts +5 -29
  42. package/src/__tests__/hooks-discovery.test.ts +1 -1
  43. package/src/__tests__/hooks-integration.test.ts +1 -1
  44. package/src/__tests__/hooks-manager.test.ts +1 -1
  45. package/src/__tests__/hooks-runner.test.ts +1 -23
  46. package/src/__tests__/hooks-settings.test.ts +1 -1
  47. package/src/__tests__/hooks-templates.test.ts +1 -1
  48. package/src/__tests__/integration-status.test.ts +0 -1
  49. package/src/__tests__/invite-routes-http.test.ts +0 -3
  50. package/src/__tests__/list-messages-attachments.test.ts +4 -4
  51. package/src/__tests__/llm-usage-store.test.ts +50 -0
  52. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  53. package/src/__tests__/media-generate-image.test.ts +2 -2
  54. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
  55. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  56. package/src/__tests__/memory-regressions.test.ts +27 -27
  57. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  58. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  59. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  60. package/src/__tests__/oauth-cli.test.ts +1 -4
  61. package/src/__tests__/oauth-store.test.ts +1 -3
  62. package/src/__tests__/openai-provider.test.ts +7 -7
  63. package/src/__tests__/platform.test.ts +14 -4
  64. package/src/__tests__/pricing.test.ts +0 -223
  65. package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
  66. package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
  67. package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
  68. package/src/__tests__/provider-registry-ollama.test.ts +3 -3
  69. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  70. package/src/__tests__/registry.test.ts +3 -103
  71. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  72. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
  74. package/src/__tests__/secure-keys.test.ts +241 -229
  75. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  76. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  77. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  78. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  79. package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
  80. package/src/__tests__/session-queue.test.ts +2 -4
  81. package/src/__tests__/session-slash-known.test.ts +0 -1
  82. package/src/__tests__/session-slash-queue.test.ts +0 -1
  83. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  84. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  85. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  87. package/src/__tests__/slack-channel-config.test.ts +1 -7
  88. package/src/__tests__/swarm-recursion.test.ts +0 -1
  89. package/src/__tests__/swarm-session-integration.test.ts +0 -1
  90. package/src/__tests__/swarm-tool.test.ts +0 -1
  91. package/src/__tests__/task-compiler.test.ts +1 -1
  92. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  93. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  94. package/src/__tests__/tool-executor.test.ts +1 -1
  95. package/src/__tests__/trust-store.test.ts +3 -82
  96. package/src/__tests__/twilio-config.test.ts +0 -1
  97. package/src/__tests__/twilio-provider.test.ts +0 -5
  98. package/src/__tests__/twilio-routes.test.ts +0 -1
  99. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  100. package/src/calls/guardian-question-copy.ts +1 -1
  101. package/src/cli/commands/bash.ts +3 -0
  102. package/src/cli/commands/doctor.ts +10 -34
  103. package/src/cli/commands/memory.ts +3 -5
  104. package/src/cli/commands/sessions.ts +1 -1
  105. package/src/cli/commands/usage.ts +359 -0
  106. package/src/cli/http-client.ts +22 -12
  107. package/src/cli/program.ts +2 -0
  108. package/src/cli/reference.ts +1 -0
  109. package/src/cli.ts +251 -181
  110. package/src/config/assistant-feature-flags.ts +0 -7
  111. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  112. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  113. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  114. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  115. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  116. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  117. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  118. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  119. package/src/config/env.ts +13 -0
  120. package/src/config/feature-flag-registry.json +9 -41
  121. package/src/config/schemas/security.ts +1 -2
  122. package/src/config/skills.ts +1 -1
  123. package/src/contacts/contact-store.ts +0 -50
  124. package/src/daemon/approved-devices-store.ts +0 -44
  125. package/src/daemon/classifier.ts +1 -1
  126. package/src/daemon/config-watcher.ts +14 -8
  127. package/src/daemon/handlers/config-model.ts +1 -1
  128. package/src/daemon/handlers/sessions.ts +4 -116
  129. package/src/daemon/handlers/skills.ts +1 -1
  130. package/src/daemon/lifecycle.ts +13 -15
  131. package/src/daemon/providers-setup.ts +1 -1
  132. package/src/daemon/server.ts +20 -3
  133. package/src/daemon/session-slash.ts +2 -2
  134. package/src/daemon/shutdown-handlers.ts +15 -0
  135. package/src/daemon/watch-handler.ts +2 -2
  136. package/src/email/guardrails.ts +1 -1
  137. package/src/email/service.ts +0 -5
  138. package/src/hooks/templates.ts +1 -1
  139. package/src/media/app-icon-generator.ts +2 -2
  140. package/src/media/avatar-router.ts +2 -2
  141. package/src/media/gemini-image-service.ts +5 -5
  142. package/src/memory/admin.ts +2 -2
  143. package/src/memory/app-git-service.ts +0 -7
  144. package/src/memory/conversation-crud.ts +1 -1
  145. package/src/memory/conversation-title-service.ts +2 -2
  146. package/src/memory/embedding-backend.ts +30 -26
  147. package/src/memory/external-conversation-store.ts +0 -30
  148. package/src/memory/guardian-action-store.ts +0 -31
  149. package/src/memory/guardian-approvals.ts +1 -56
  150. package/src/memory/indexer.ts +4 -3
  151. package/src/memory/items-extractor.ts +1 -1
  152. package/src/memory/job-handlers/backfill.ts +5 -2
  153. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  154. package/src/memory/job-handlers/media-processing.ts +2 -2
  155. package/src/memory/job-handlers/summarization.ts +1 -1
  156. package/src/memory/job-utils.ts +1 -2
  157. package/src/memory/jobs-worker.ts +2 -2
  158. package/src/memory/llm-usage-store.ts +57 -11
  159. package/src/memory/media-store.ts +4 -535
  160. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  161. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  162. package/src/memory/published-pages-store.ts +0 -83
  163. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  164. package/src/memory/retriever.ts +1 -1
  165. package/src/memory/schema/calls.ts +0 -67
  166. package/src/memory/search/semantic.ts +1 -8
  167. package/src/memory/shared-app-links-store.ts +0 -15
  168. package/src/messaging/registry.ts +0 -5
  169. package/src/messaging/style-analyzer.ts +1 -1
  170. package/src/notifications/copy-composer.ts +5 -13
  171. package/src/notifications/decision-engine.ts +2 -2
  172. package/src/notifications/deliveries-store.ts +0 -39
  173. package/src/notifications/guardian-question-mode.ts +6 -10
  174. package/src/notifications/preference-extractor.ts +1 -1
  175. package/src/oauth/byo-connection.test.ts +29 -20
  176. package/src/oauth/provider-behaviors.ts +1 -1
  177. package/src/permissions/checker.ts +1 -1
  178. package/src/permissions/shell-identity.ts +0 -5
  179. package/src/permissions/trust-store.ts +0 -37
  180. package/src/prompts/system-prompt.ts +4 -4
  181. package/src/prompts/templates/SOUL.md +1 -1
  182. package/src/providers/managed-proxy/constants.ts +8 -10
  183. package/src/providers/managed-proxy/context.ts +14 -9
  184. package/src/providers/provider-send-message.ts +4 -52
  185. package/src/providers/registry.ts +16 -50
  186. package/src/runtime/actor-token-store.ts +0 -23
  187. package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
  188. package/src/runtime/http-router.ts +5 -1
  189. package/src/runtime/http-server.ts +101 -4
  190. package/src/runtime/invite-instruction-generator.ts +25 -51
  191. package/src/runtime/invite-service.ts +0 -20
  192. package/src/runtime/routes/attachment-routes.ts +1 -1
  193. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  194. package/src/runtime/routes/call-routes.ts +1 -1
  195. package/src/runtime/routes/conversation-routes.ts +32 -11
  196. package/src/runtime/routes/debug-routes.ts +1 -1
  197. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  198. package/src/runtime/routes/documents-routes.ts +3 -3
  199. package/src/runtime/routes/global-search-routes.ts +1 -1
  200. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  201. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  202. package/src/runtime/routes/secret-routes.ts +4 -4
  203. package/src/runtime/routes/session-management-routes.ts +27 -0
  204. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  205. package/src/security/credential-backend.ts +148 -0
  206. package/src/security/oauth2.ts +1 -1
  207. package/src/security/secret-allowlist.ts +1 -1
  208. package/src/security/secure-keys.ts +98 -160
  209. package/src/security/token-manager.ts +0 -7
  210. package/src/sequence/guardrails.ts +0 -4
  211. package/src/sequence/store.ts +1 -20
  212. package/src/sequence/types.ts +1 -36
  213. package/src/signals/bash.ts +33 -0
  214. package/src/signals/cancel.ts +69 -0
  215. package/src/signals/conversation-undo.ts +127 -0
  216. package/src/signals/trust-rule.ts +174 -0
  217. package/src/skills/clawhub.ts +5 -5
  218. package/src/skills/managed-store.ts +4 -4
  219. package/src/subagent/manager.ts +8 -1
  220. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  221. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  222. package/src/tools/claude-code/claude-code.ts +2 -2
  223. package/src/tools/credentials/vault.ts +8 -4
  224. package/src/tools/memory/handlers.test.ts +24 -26
  225. package/src/tools/memory/handlers.ts +1 -13
  226. package/src/tools/registry.ts +5 -100
  227. package/src/tools/terminal/parser.ts +34 -4
  228. package/src/tools/tool-manifest.ts +0 -10
  229. package/src/usage/actors.ts +0 -12
  230. package/src/util/canonicalize-identity.ts +0 -9
  231. package/src/util/errors.ts +0 -3
  232. package/src/util/platform.ts +24 -7
  233. package/src/util/pricing.ts +0 -38
  234. package/src/watcher/constants.ts +0 -7
  235. package/src/watcher/providers/linear.ts +1 -1
  236. package/src/work-items/work-item-store.ts +4 -4
  237. package/src/workspace/commit-message-provider.ts +1 -1
  238. package/src/workspace/git-service.ts +44 -1
  239. package/src/workspace/provider-commit-message-generator.ts +1 -1
  240. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  241. package/src/browser-extension-relay/client.ts +0 -155
  242. package/src/contacts/index.ts +0 -18
  243. package/src/daemon/tls-certs.ts +0 -270
  244. package/src/errors.ts +0 -41
  245. package/src/events/index.ts +0 -18
  246. package/src/followups/index.ts +0 -10
  247. package/src/playbooks/index.ts +0 -10
  248. package/src/runtime/auth/index.ts +0 -44
  249. package/src/tasks/candidate-store.ts +0 -95
  250. package/src/tools/browser/api-map.ts +0 -313
  251. package/src/tools/browser/auto-navigate.ts +0 -469
  252. package/src/tools/browser/headless-browser.ts +0 -590
  253. package/src/tools/browser/recording-store.ts +0 -75
  254. package/src/tools/computer-use/registry.ts +0 -21
  255. package/src/tools/tasks/index.ts +0 -27
@@ -23,49 +23,6 @@ const GENERATION_TIMEOUT_MS = 5_000;
23
23
  /** Maximum allowed length for a generated instruction. */
24
24
  const MAX_INSTRUCTION_LENGTH = 500;
25
25
 
26
- // ---------------------------------------------------------------------------
27
- // Channel display label
28
- // ---------------------------------------------------------------------------
29
-
30
- /** Human-readable label for a channel type. */
31
- export function channelDisplayLabel(type: string): string {
32
- switch (type) {
33
- case "telegram":
34
- return "Telegram";
35
- case "email":
36
- return "Email";
37
- case "slack":
38
- return "Slack";
39
- case "phone":
40
- return "Voice";
41
- default:
42
- return type.charAt(0).toUpperCase() + type.slice(1);
43
- }
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Deterministic fallback
48
- // ---------------------------------------------------------------------------
49
-
50
- /**
51
- * Build a deterministic fallback instruction when the LLM is unavailable.
52
- */
53
- export function buildFallbackInstruction(params: {
54
- contactName?: string;
55
- channelLabel: string;
56
- channelHandle?: string;
57
- shareUrl?: string;
58
- }): string {
59
- const contact = params.contactName || "the contact";
60
- const handle = params.channelHandle
61
- ? ` at ${params.channelHandle}`
62
- : ` on ${params.channelLabel}`;
63
- if (params.shareUrl) {
64
- return `Send ${contact} this link: ${params.shareUrl} — or tell them to message me${handle} with the code below.`;
65
- }
66
- return `Tell ${contact} to message me${handle} with the code below.`;
67
- }
68
-
69
26
  // ---------------------------------------------------------------------------
70
27
  // LLM-powered generation
71
28
  // ---------------------------------------------------------------------------
@@ -88,15 +45,32 @@ export async function generateInviteInstruction(params: {
88
45
  */
89
46
  shareUrl?: string;
90
47
  }): Promise<string> {
91
- const channelLabel = channelDisplayLabel(params.channelType);
92
- const fallback = buildFallbackInstruction({
93
- contactName: params.contactName,
94
- channelLabel,
95
- channelHandle: params.channelHandle,
96
- shareUrl: params.shareUrl,
97
- });
48
+ const channelLabel = (() => {
49
+ switch (params.channelType) {
50
+ case "telegram":
51
+ return "Telegram";
52
+ case "email":
53
+ return "Email";
54
+ case "slack":
55
+ return "Slack";
56
+ case "phone":
57
+ return "Voice";
58
+ default:
59
+ return (
60
+ params.channelType.charAt(0).toUpperCase() +
61
+ params.channelType.slice(1)
62
+ );
63
+ }
64
+ })();
65
+ const contact = params.contactName || "the contact";
66
+ const handle = params.channelHandle
67
+ ? ` at ${params.channelHandle}`
68
+ : ` on ${channelLabel}`;
69
+ const fallback = params.shareUrl
70
+ ? `Send ${contact} this link: ${params.shareUrl} — or tell them to message me${handle} with the code below.`
71
+ : `Tell ${contact} to message me${handle} with the code below.`;
98
72
 
99
- const resolved = resolveConfiguredProvider();
73
+ const resolved = await resolveConfiguredProvider();
100
74
  if (!resolved) {
101
75
  log.debug(
102
76
  "No provider available for invite instruction generation, using fallback",
@@ -34,7 +34,6 @@ import {
34
34
  } from "./channel-invite-transport.js";
35
35
  import { generateInviteInstruction } from "./invite-instruction-generator.js";
36
36
  import {
37
- type InviteRedemptionOutcome,
38
37
  redeemInvite as redeemInviteTyped,
39
38
  redeemVoiceInviteCode as redeemVoiceInviteCodeTyped,
40
39
  type VoiceRedemptionOutcome,
@@ -372,25 +371,6 @@ export function redeemIngressInvite(params: {
372
371
  return { ok: true, data: inviteToResponse(inv) };
373
372
  }
374
373
 
375
- // ---------------------------------------------------------------------------
376
- // Typed invite redemption — preferred entry point for new callers
377
- // ---------------------------------------------------------------------------
378
-
379
- export { type InviteRedemptionOutcome } from "./invite-redemption-service.js";
380
- export { type VoiceRedemptionOutcome } from "./invite-redemption-service.js";
381
-
382
- export function redeemIngressInviteTyped(params: {
383
- rawToken: string;
384
- sourceChannel: string;
385
- externalUserId?: string;
386
- externalChatId?: string;
387
- displayName?: string;
388
- username?: string;
389
- assistantId?: string;
390
- }): InviteRedemptionOutcome {
391
- return redeemInviteTyped(params);
392
- }
393
-
394
374
  export function redeemVoiceInviteCode(params: {
395
375
  assistantId?: string;
396
376
  callerExternalUserId: string;
@@ -101,7 +101,7 @@ export async function handleDeleteAttachment(req: Request): Promise<Response> {
101
101
  return new Response(null, { status: 204 });
102
102
  }
103
103
 
104
- export function handleGetAttachment(attachmentId: string): Response {
104
+ function handleGetAttachment(attachmentId: string): Response {
105
105
  const attachment = attachmentsStore.getAttachmentById(attachmentId);
106
106
  if (!attachment) {
107
107
  return httpError("NOT_FOUND", "Attachment not found", 404);
@@ -34,7 +34,7 @@ function getMemoryKindColor(kind: string): string {
34
34
  }
35
35
  }
36
36
 
37
- export function handleGetBrainGraph(): Response {
37
+ function handleGetBrainGraph(): Response {
38
38
  try {
39
39
  const db = getDb();
40
40
 
@@ -148,7 +148,7 @@ export async function handleStartCall(
148
148
  /**
149
149
  * GET /v1/calls/:callSessionId
150
150
  */
151
- export function handleGetCallStatus(callSessionId: string): Response {
151
+ function handleGetCallStatus(callSessionId: string): Response {
152
152
  const result = getCallStatus(callSessionId);
153
153
 
154
154
  if (!result.ok) {
@@ -352,16 +352,37 @@ export function handleListMessages(
352
352
  });
353
353
  }
354
354
  } else {
355
- const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id);
355
+ const linked =
356
+ attachmentsStore.getAttachmentMetadataForMessage(m.id);
356
357
  if (linked.length > 0) {
357
- msgAttachments = linked.map((a) => ({
358
- id: a.id,
359
- filename: a.originalFilename,
360
- mimeType: a.mimeType,
361
- sizeBytes: a.sizeBytes,
362
- kind: a.kind,
363
- ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
364
- }));
358
+ msgAttachments = linked.map((a) => {
359
+ if (a.mimeType.startsWith("image/")) {
360
+ const full = attachmentsStore.getAttachmentById(a.id);
361
+ return {
362
+ id: a.id,
363
+ filename: a.originalFilename,
364
+ mimeType: a.mimeType,
365
+ sizeBytes: a.sizeBytes,
366
+ kind: a.kind,
367
+ ...(full?.dataBase64
368
+ ? { data: full.dataBase64 }
369
+ : {}),
370
+ ...(a.thumbnailBase64
371
+ ? { thumbnailData: a.thumbnailBase64 }
372
+ : {}),
373
+ };
374
+ }
375
+ return {
376
+ id: a.id,
377
+ filename: a.originalFilename,
378
+ mimeType: a.mimeType,
379
+ sizeBytes: a.sizeBytes,
380
+ kind: a.kind,
381
+ ...(a.thumbnailBase64
382
+ ? { thumbnailData: a.thumbnailBase64 }
383
+ : {}),
384
+ };
385
+ });
365
386
  }
366
387
  }
367
388
  }
@@ -1116,7 +1137,7 @@ export async function handleGetSuggestion(
1116
1137
  }
1117
1138
 
1118
1139
  // Try LLM suggestion using the configured provider
1119
- const provider = getConfiguredProvider();
1140
+ const provider = await getConfiguredProvider();
1120
1141
  if (provider) {
1121
1142
  try {
1122
1143
  // Deduplicate concurrent requests
@@ -1169,7 +1190,7 @@ export async function handleGetSuggestion(
1169
1190
  * Full-text search across all conversation threads (message content + titles).
1170
1191
  * Returns ranked results grouped by conversation, each with matching message excerpts.
1171
1192
  */
1172
- export function handleSearchConversations(url: URL): Response {
1193
+ function handleSearchConversations(url: URL): Response {
1173
1194
  const query = url.searchParams.get("q") ?? "";
1174
1195
  if (!query.trim()) {
1175
1196
  return httpError("BAD_REQUEST", "q query parameter is required", 400);
@@ -35,7 +35,7 @@ function getMemoryItemCount(): number {
35
35
  }
36
36
  }
37
37
 
38
- export function handleDebug(): Response {
38
+ function handleDebug(): Response {
39
39
  const now = Date.now();
40
40
  const uptimeSeconds = Math.floor((now - startedAt) / 1000);
41
41
 
@@ -684,7 +684,7 @@ async function handleDictation(body: DictationBody): Promise<Response> {
684
684
  const transcription = expandSnippets(body.transcription, profile.snippets);
685
685
 
686
686
  try {
687
- const provider = getConfiguredProvider();
687
+ const provider = await getConfiguredProvider();
688
688
  if (!provider) {
689
689
  log.warn(
690
690
  "Dictation: no provider available, using heuristic + raw transcription",
@@ -843,7 +843,7 @@ async function handleCommandMode(
843
843
  const maxTokens = Math.max(1024, computeMaxTokens(inputLength));
844
844
 
845
845
  try {
846
- const provider = getConfiguredProvider();
846
+ const provider = await getConfiguredProvider();
847
847
  if (!provider) {
848
848
  log.warn("Command mode: no provider available, returning selected text");
849
849
  const normalizedText = applyDictionary(
@@ -27,7 +27,7 @@ type DocumentListRow = Omit<DocumentRow, "content">;
27
27
  // Shared business logic (used by both message handlers and HTTP routes)
28
28
  // ---------------------------------------------------------------------------
29
29
 
30
- export function saveDocument(params: {
30
+ function saveDocument(params: {
31
31
  surfaceId: string;
32
32
  conversationId: string;
33
33
  title: string;
@@ -66,7 +66,7 @@ export function saveDocument(params: {
66
66
  }
67
67
  }
68
68
 
69
- export function loadDocument(surfaceId: string):
69
+ function loadDocument(surfaceId: string):
70
70
  | {
71
71
  success: true;
72
72
  surfaceId: string;
@@ -112,7 +112,7 @@ export function loadDocument(surfaceId: string):
112
112
  }
113
113
  }
114
114
 
115
- export function listDocuments(conversationId?: string): Array<{
115
+ function listDocuments(conversationId?: string): Array<{
116
116
  surfaceId: string;
117
117
  conversationId: string;
118
118
  title: string;
@@ -134,7 +134,7 @@ async function searchMemoriesSemantic(
134
134
  existingIds: Set<string>,
135
135
  ): Promise<GlobalSearchMemory[]> {
136
136
  const config = getConfig();
137
- const backendStatus = getMemoryBackendStatus(config);
137
+ const backendStatus = await getMemoryBackendStatus(config);
138
138
  if (!backendStatus.provider) return [];
139
139
 
140
140
  try {
@@ -19,7 +19,6 @@ import { getLogger } from "../../util/logger.js";
19
19
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
20
20
  import { mintCredentialPair } from "../auth/credential-service.js";
21
21
  import { httpError } from "../http-errors.js";
22
- import type { RouteDefinition } from "../http-router.js";
23
22
 
24
23
  /** Bun server shape needed for requestIP -- avoids importing the full Bun type. */
25
24
  type ServerWithRequestIP = {
@@ -154,22 +153,3 @@ export async function handleGuardianBootstrap(
154
153
  return httpError("INTERNAL_ERROR", "Internal server error", 500);
155
154
  }
156
155
  }
157
-
158
- // ---------------------------------------------------------------------------
159
- // Route definitions
160
- // ---------------------------------------------------------------------------
161
-
162
- /**
163
- * Guardian bootstrap is a pre-auth endpoint (handled before JWT auth in
164
- * http-server.ts), so these definitions are exported for completeness but
165
- * are not added to the authenticated route table.
166
- */
167
- export function guardianBootstrapRouteDefinitions(): RouteDefinition[] {
168
- return [
169
- {
170
- endpoint: "guardian/init",
171
- method: "POST",
172
- handler: async ({ req, server }) => handleGuardianBootstrap(req, server),
173
- },
174
- ];
175
- }
@@ -8,7 +8,6 @@
8
8
  import { getLogger } from "../../util/logger.js";
9
9
  import { rotateCredentials } from "../auth/credential-service.js";
10
10
  import { httpError } from "../http-errors.js";
11
- import type { RouteDefinition } from "../http-router.js";
12
11
 
13
12
  const log = getLogger("guardian-refresh");
14
13
 
@@ -78,22 +77,3 @@ export async function handleGuardianRefresh(req: Request): Promise<Response> {
78
77
  return httpError("INTERNAL_ERROR", "Internal server error", 500);
79
78
  }
80
79
  }
81
-
82
- // ---------------------------------------------------------------------------
83
- // Route definitions
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Guardian refresh is a pre-auth endpoint (handled before JWT auth in
88
- * http-server.ts), so these definitions are exported for completeness but
89
- * are not added to the authenticated route table.
90
- */
91
- export function guardianRefreshRouteDefinitions(): RouteDefinition[] {
92
- return [
93
- {
94
- endpoint: "guardian/refresh",
95
- method: "POST",
96
- handler: async ({ req }) => handleGuardianRefresh(req),
97
- },
98
- ];
99
- }
@@ -73,7 +73,7 @@ export async function handleAddSecret(req: Request): Promise<Response> {
73
73
  );
74
74
  }
75
75
  invalidateConfigCache();
76
- initializeProviders(getConfig());
76
+ await initializeProviders(getConfig());
77
77
  log.info({ provider: name }, "API key updated via HTTP");
78
78
  return Response.json({ success: true, type, name }, { status: 201 });
79
79
  }
@@ -101,7 +101,7 @@ export async function handleAddSecret(req: Request): Promise<Response> {
101
101
  }
102
102
  upsertCredentialMetadata(service, field, {});
103
103
  if (isManagedProxyCredential(service, field)) {
104
- initializeProviders(getConfig());
104
+ await initializeProviders(getConfig());
105
105
  }
106
106
  log.info({ service, field }, "Credential added via HTTP");
107
107
  return Response.json({ success: true, type, name }, { status: 201 });
@@ -162,7 +162,7 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
162
162
  );
163
163
  }
164
164
  invalidateConfigCache();
165
- initializeProviders(getConfig());
165
+ await initializeProviders(getConfig());
166
166
  log.info({ provider: name }, "API key deleted via HTTP");
167
167
  return Response.json({ success: true, type, name });
168
168
  }
@@ -196,7 +196,7 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
196
196
  }
197
197
  deleteCredentialMetadata(service, field);
198
198
  if (isManagedProxyCredential(service, field)) {
199
- initializeProviders(getConfig());
199
+ await initializeProviders(getConfig());
200
200
  }
201
201
  log.info({ service, field }, "Credential deleted via HTTP");
202
202
  return Response.json({ success: true, type, name });
@@ -7,8 +7,10 @@
7
7
  * POST /v1/conversations/:id/cancel — cancel generation
8
8
  * POST /v1/conversations/:id/undo — undo last message
9
9
  * POST /v1/conversations/:id/regenerate — regenerate last assistant response
10
+ * POST /v1/sessions/reorder — reorder / pin sessions
10
11
  */
11
12
 
13
+ import { batchSetDisplayOrders } from "../../memory/conversation-crud.js";
12
14
  import { setConversationKeyIfAbsent } from "../../memory/conversation-key-store.js";
13
15
  import { getLogger } from "../../util/logger.js";
14
16
  import { httpError } from "../http-errors.js";
@@ -159,5 +161,30 @@ export function sessionManagementRouteDefinitions(
159
161
  }
160
162
  },
161
163
  },
164
+ {
165
+ endpoint: "sessions/reorder",
166
+ method: "POST",
167
+ policyKey: "sessions/reorder",
168
+ handler: async ({ req }) => {
169
+ const body = (await req.json()) as {
170
+ updates?: Array<{
171
+ sessionId: string;
172
+ displayOrder?: number;
173
+ isPinned?: boolean;
174
+ }>;
175
+ };
176
+ if (!Array.isArray(body.updates)) {
177
+ return httpError("BAD_REQUEST", "Missing updates array", 400);
178
+ }
179
+ batchSetDisplayOrders(
180
+ body.updates.map((u) => ({
181
+ id: u.sessionId,
182
+ displayOrder: u.displayOrder ?? null,
183
+ isPinned: u.isPinned ?? false,
184
+ })),
185
+ );
186
+ return Response.json({ ok: true });
187
+ },
188
+ },
162
189
  ];
163
190
  }
@@ -20,7 +20,7 @@ const log = getLogger("trust-rules-routes");
20
20
  /**
21
21
  * GET /v1/trust-rules/manage — list all trust rules.
22
22
  */
23
- export function handleListTrustRules(): Response {
23
+ function handleListTrustRules(): Response {
24
24
  const rules = getAllRules();
25
25
  return Response.json({ type: "trust_rules_list_response", rules });
26
26
  }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * CredentialBackend interface and adapters — abstracts credential storage
3
+ * behind a unified async API so callers don't need to know which backend
4
+ * (macOS Keychain, encrypted file store, etc.) is in use.
5
+ */
6
+
7
+ import * as encryptedStore from "./encrypted-store.js";
8
+ import type { KeychainBrokerClient } from "./keychain-broker-client.js";
9
+ import { createBrokerClient } from "./keychain-broker-client.js";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Types
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /** Result of a delete operation — distinguishes success, not-found, and error. */
16
+ export type DeleteResult = "deleted" | "not-found" | "error";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Interface
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface CredentialBackend {
23
+ /** Human-readable name for logging (e.g. "keychain", "encrypted-store"). */
24
+ readonly name: string;
25
+
26
+ /** Whether this backend is currently reachable. Sync and cheap. */
27
+ isAvailable(): boolean;
28
+
29
+ /** Retrieve a secret. Returns undefined if not found or on error. */
30
+ get(account: string): Promise<string | undefined>;
31
+
32
+ /** Store a secret. Returns true on success. */
33
+ set(account: string, value: string): Promise<boolean>;
34
+
35
+ /** Delete a secret. */
36
+ delete(account: string): Promise<DeleteResult>;
37
+
38
+ /** List all account names. */
39
+ list(): Promise<string[]>;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // KeychainBackend
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export class KeychainBackend implements CredentialBackend {
47
+ readonly name = "keychain";
48
+
49
+ constructor(private readonly client: KeychainBrokerClient) {}
50
+
51
+ isAvailable(): boolean {
52
+ return this.client.isAvailable();
53
+ }
54
+
55
+ async get(account: string): Promise<string | undefined> {
56
+ try {
57
+ const result = await this.client.get(account);
58
+ if (result == null || !result.found) return undefined;
59
+ return result.value;
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ async set(account: string, value: string): Promise<boolean> {
66
+ try {
67
+ const result = await this.client.set(account, value);
68
+ return result.status === "ok";
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ async delete(account: string): Promise<DeleteResult> {
75
+ try {
76
+ const ok = await this.client.del(account);
77
+ // The keychain broker returns a boolean — it does not distinguish
78
+ // "not found" from a genuine error, so we map false → "error".
79
+ return ok ? "deleted" : "error";
80
+ } catch {
81
+ return "error";
82
+ }
83
+ }
84
+
85
+ async list(): Promise<string[]> {
86
+ try {
87
+ return await this.client.list();
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // EncryptedStoreBackend
96
+ // ---------------------------------------------------------------------------
97
+
98
+ export class EncryptedStoreBackend implements CredentialBackend {
99
+ readonly name = "encrypted-store";
100
+
101
+ isAvailable(): boolean {
102
+ return true;
103
+ }
104
+
105
+ async get(account: string): Promise<string | undefined> {
106
+ try {
107
+ return encryptedStore.getKey(account);
108
+ } catch {
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ async set(account: string, value: string): Promise<boolean> {
114
+ try {
115
+ return encryptedStore.setKey(account, value);
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ async delete(account: string): Promise<DeleteResult> {
122
+ try {
123
+ return encryptedStore.deleteKey(account);
124
+ } catch {
125
+ return "error";
126
+ }
127
+ }
128
+
129
+ async list(): Promise<string[]> {
130
+ try {
131
+ return encryptedStore.listKeys();
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Factory functions
140
+ // ---------------------------------------------------------------------------
141
+
142
+ export function createKeychainBackend(): KeychainBackend {
143
+ return new KeychainBackend(createBrokerClient());
144
+ }
145
+
146
+ export function createEncryptedStoreBackend(): EncryptedStoreBackend {
147
+ return new EncryptedStoreBackend();
148
+ }
@@ -45,7 +45,7 @@ export interface OAuth2Config {
45
45
  * How the client authenticates at the token endpoint when a clientSecret is present.
46
46
  * - `client_secret_post`: Send client_id and client_secret in the POST body (default).
47
47
  * - `client_secret_basic`: Send an HTTP Basic Auth header with base64(client_id:client_secret).
48
- * Defaults to `client_secret_post` for backward compatibility.
48
+ * Defaults to `client_secret_post`.
49
49
  */
50
50
  tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
51
51
  }
@@ -131,7 +131,7 @@ export interface AllowlistValidationError {
131
131
  * Validate all regex patterns in an allowlist config without loading them.
132
132
  * Returns an array of validation errors (empty = all valid).
133
133
  */
134
- export function validateAllowlist(
134
+ function validateAllowlist(
135
135
  config: AllowlistConfig,
136
136
  ): AllowlistValidationError[] {
137
137
  const errors: AllowlistValidationError[] = [];