@vellumai/assistant 0.4.37 → 0.4.41

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 (169) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/README.md +13 -13
  3. package/bun.lock +80 -24
  4. package/docs/architecture/integrations.md +126 -128
  5. package/docs/runbook-trusted-contacts.md +1 -1
  6. package/docs/trusted-contact-access.md +12 -12
  7. package/package.json +3 -1
  8. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -14
  9. package/src/__tests__/app-bundler.test.ts +209 -0
  10. package/src/__tests__/app-compiler.test.ts +279 -0
  11. package/src/__tests__/app-executors.test.ts +293 -483
  12. package/src/__tests__/app-migration.test.ts +148 -0
  13. package/src/__tests__/app-routes-csp.test.ts +202 -0
  14. package/src/__tests__/avatar-e2e.test.ts +452 -0
  15. package/src/__tests__/avatar-generator.test.ts +193 -0
  16. package/src/__tests__/avatar-router.test.ts +186 -0
  17. package/src/__tests__/browser-download-timeout.test.ts +28 -0
  18. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +9 -9
  19. package/src/__tests__/call-domain.test.ts +3 -7
  20. package/src/__tests__/credential-security-e2e.test.ts +19 -12
  21. package/src/__tests__/credentials-cli.test.ts +30 -4
  22. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +1 -1
  23. package/src/__tests__/handlers-slack-config.test.ts +0 -72
  24. package/src/__tests__/handlers-telegram-config.test.ts +19 -12
  25. package/src/__tests__/handlers-twitter-config.test.ts +105 -48
  26. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  27. package/src/__tests__/integration-status.test.ts +15 -5
  28. package/src/__tests__/integrations-cli.test.ts +1 -1
  29. package/src/__tests__/invite-redemption-service.test.ts +62 -7
  30. package/src/__tests__/ipc-snapshot.test.ts +0 -8
  31. package/src/__tests__/managed-avatar-client.test.ts +280 -0
  32. package/src/__tests__/mcp-cli.test.ts +3 -3
  33. package/src/__tests__/oauth-cli.test.ts +203 -0
  34. package/src/__tests__/relay-server.test.ts +3 -3
  35. package/src/__tests__/secret-onetime-send.test.ts +19 -12
  36. package/src/__tests__/secure-keys.test.ts +78 -0
  37. package/src/__tests__/session-messaging-secret-redirect.test.ts +3 -0
  38. package/src/__tests__/slack-channel-config.test.ts +23 -16
  39. package/src/__tests__/slack-share-routes.test.ts +263 -0
  40. package/src/__tests__/sms-messaging-provider.test.ts +3 -1
  41. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +7 -7
  42. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  43. package/src/__tests__/trusted-contact-verification.test.ts +10 -10
  44. package/src/__tests__/twilio-config.test.ts +15 -36
  45. package/src/__tests__/twilio-provider.test.ts +4 -0
  46. package/src/__tests__/twitter-auth-handler.test.ts +27 -14
  47. package/src/__tests__/twitter-cli-error-shaping.test.ts +1 -1
  48. package/src/__tests__/twitter-cli-routing.test.ts +38 -53
  49. package/src/__tests__/twitter-oauth-client.test.ts +18 -47
  50. package/src/__tests__/voice-invite-redemption.test.ts +27 -3
  51. package/src/amazon/cart.ts +1 -1
  52. package/src/amazon/client.ts +89 -7
  53. package/src/approvals/guardian-request-resolvers.ts +2 -2
  54. package/src/bundler/app-bundler.ts +77 -32
  55. package/src/bundler/app-compiler.ts +195 -0
  56. package/src/bundler/manifest.ts +1 -1
  57. package/src/bundler/package-resolver.ts +185 -0
  58. package/src/calls/call-domain.ts +4 -14
  59. package/src/calls/relay-server.ts +2 -2
  60. package/src/calls/twilio-config.ts +5 -24
  61. package/src/calls/twilio-rest.ts +19 -5
  62. package/src/cli/amazon.ts +74 -249
  63. package/src/cli/audit.ts +2 -2
  64. package/src/cli/autonomy.ts +9 -9
  65. package/src/cli/channels.ts +5 -5
  66. package/src/cli/completions.ts +27 -27
  67. package/src/cli/config.ts +14 -14
  68. package/src/cli/contacts.ts +27 -27
  69. package/src/cli/credentials.ts +28 -28
  70. package/src/cli/dev.ts +2 -2
  71. package/src/cli/doctor.ts +2 -2
  72. package/src/cli/email.ts +82 -82
  73. package/src/cli/influencer.ts +13 -13
  74. package/src/cli/integrations.ts +19 -144
  75. package/src/cli/keys.ts +10 -10
  76. package/src/cli/map.ts +4 -4
  77. package/src/cli/mcp.ts +17 -17
  78. package/src/cli/memory.ts +18 -18
  79. package/src/cli/notifications.ts +13 -13
  80. package/src/cli/oauth.ts +77 -0
  81. package/src/cli/program.ts +2 -0
  82. package/src/cli/sequence.ts +27 -27
  83. package/src/cli/sessions.ts +12 -12
  84. package/src/cli/trust.ts +8 -8
  85. package/src/cli/twitter.ts +124 -70
  86. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  87. package/src/config/bundled-skills/agentmail/SKILL.md +34 -34
  88. package/src/config/bundled-skills/amazon/SKILL.md +54 -54
  89. package/src/config/bundled-skills/app-builder/SKILL.md +137 -3
  90. package/src/config/bundled-skills/app-builder/tools/app-create.ts +10 -4
  91. package/src/config/bundled-skills/configure-settings/SKILL.md +18 -18
  92. package/src/config/bundled-skills/contacts/SKILL.md +12 -12
  93. package/src/config/bundled-skills/doordash/lib/client.ts +7 -9
  94. package/src/config/bundled-skills/email-setup/SKILL.md +4 -4
  95. package/src/config/bundled-skills/frontend-design/icon.svg +16 -0
  96. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +143 -162
  97. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +4 -4
  98. package/src/config/bundled-skills/influencer/SKILL.md +13 -13
  99. package/src/config/bundled-skills/mcp-setup/SKILL.md +11 -11
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +48 -54
  101. package/src/config/bundled-skills/public-ingress/SKILL.md +6 -6
  102. package/src/config/bundled-skills/slack-app-setup/SKILL.md +1 -1
  103. package/src/config/bundled-skills/sms-setup/SKILL.md +3 -3
  104. package/src/config/bundled-skills/telegram-setup/SKILL.md +2 -2
  105. package/src/config/bundled-skills/twilio-setup/SKILL.md +136 -225
  106. package/src/config/bundled-skills/twitter/SKILL.md +68 -44
  107. package/src/config/bundled-skills/voice-setup/SKILL.md +2 -2
  108. package/src/config/core-schema.ts +26 -0
  109. package/src/config/env.ts +4 -0
  110. package/src/config/feature-flag-registry.json +9 -1
  111. package/src/config/schema.ts +8 -0
  112. package/src/config/system-prompt.ts +6 -3
  113. package/src/config/templates/BOOTSTRAP.md +7 -5
  114. package/src/contacts/contacts-write.ts +5 -1
  115. package/src/daemon/handlers/apps.ts +31 -4
  116. package/src/daemon/handlers/config-ingress.ts +3 -3
  117. package/src/daemon/handlers/config-integrations.ts +120 -49
  118. package/src/daemon/handlers/config-slack-channel.ts +26 -7
  119. package/src/daemon/handlers/config-slack.ts +1 -54
  120. package/src/daemon/handlers/config-telegram.ts +28 -10
  121. package/src/daemon/handlers/config.ts +1 -4
  122. package/src/daemon/handlers/twitter-auth.ts +11 -4
  123. package/src/daemon/ipc-contract/apps.ts +0 -13
  124. package/src/daemon/ipc-contract-inventory.json +0 -2
  125. package/src/daemon/lifecycle.ts +8 -1
  126. package/src/daemon/session-messaging.ts +2 -2
  127. package/src/daemon/tool-side-effects.ts +30 -0
  128. package/src/email/providers/agentmail.ts +1 -1
  129. package/src/email/providers/index.ts +1 -1
  130. package/src/email/service.ts +1 -1
  131. package/src/gallery/default-gallery.ts +538 -0
  132. package/src/gallery/gallery-manifest.ts +5 -1
  133. package/src/influencer/client.ts +8 -6
  134. package/src/mcp/client.ts +1 -1
  135. package/src/media/avatar-router.ts +99 -0
  136. package/src/media/avatar-types.ts +60 -0
  137. package/src/media/managed-avatar-client.ts +189 -0
  138. package/src/memory/app-migration.ts +114 -0
  139. package/src/memory/app-store.ts +11 -0
  140. package/src/memory/qdrant-client.ts +1 -1
  141. package/src/messaging/providers/slack/client.ts +12 -2
  142. package/src/messaging/providers/sms/adapter.ts +6 -10
  143. package/src/migrations/data-layout.ts +8 -1
  144. package/src/oauth/token-persistence.ts +9 -6
  145. package/src/runtime/assistant-scope.ts +5 -0
  146. package/src/runtime/auth/route-policy.ts +4 -0
  147. package/src/runtime/channel-readiness-service.ts +9 -4
  148. package/src/runtime/gateway-internal-client.ts +11 -3
  149. package/src/runtime/http-server.ts +2 -0
  150. package/src/runtime/invite-redemption-service.ts +23 -13
  151. package/src/runtime/middleware/twilio-validation.ts +2 -2
  152. package/src/runtime/routes/app-routes.ts +131 -3
  153. package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -3
  154. package/src/runtime/routes/integration-routes.ts +2 -2
  155. package/src/runtime/routes/slack-share-routes.ts +235 -0
  156. package/src/runtime/routes/twilio-routes.ts +47 -34
  157. package/src/schedule/integration-status.ts +2 -3
  158. package/src/security/token-manager.ts +11 -3
  159. package/src/tools/apps/executors.ts +116 -8
  160. package/src/tools/browser/browser-manager.ts +30 -2
  161. package/src/tools/browser/chrome-cdp.ts +31 -3
  162. package/src/tools/credentials/vault.ts +9 -7
  163. package/src/tools/executor.ts +4 -0
  164. package/src/tools/system/avatar-generator.ts +55 -34
  165. package/src/twitter/client.ts +1 -1
  166. package/src/twitter/oauth-client.ts +31 -43
  167. package/src/twitter/router.ts +25 -23
  168. package/src/util/platform.ts +5 -0
  169. package/src/slack/slack-webhook.ts +0 -66
@@ -1,10 +1,15 @@
1
1
  import * as net from "node:net";
2
2
 
3
- import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
4
3
  import {
5
- deleteSecureKey,
4
+ getNestedValue,
5
+ loadRawConfig,
6
+ saveRawConfig,
7
+ setNestedValue,
8
+ } from "../../config/loader.js";
9
+ import {
10
+ deleteSecureKeyAsync,
6
11
  getSecureKey,
7
- setSecureKey,
12
+ setSecureKeyAsync,
8
13
  } from "../../security/secure-keys.js";
9
14
  import {
10
15
  deleteCredentialMetadata,
@@ -17,11 +22,11 @@ import type {
17
22
  } from "../ipc-protocol.js";
18
23
  import { defineHandlers, type HandlerContext, log } from "./shared.js";
19
24
 
20
- export function handleVercelApiConfig(
25
+ export async function handleVercelApiConfig(
21
26
  msg: VercelApiConfigRequest,
22
27
  socket: net.Socket,
23
28
  ctx: HandlerContext,
24
- ): void {
29
+ ): Promise<void> {
25
30
  try {
26
31
  if (msg.action === "get") {
27
32
  const existing = getSecureKey("credential:vercel:api_token");
@@ -40,7 +45,10 @@ export function handleVercelApiConfig(
40
45
  });
41
46
  return;
42
47
  }
43
- const stored = setSecureKey("credential:vercel:api_token", msg.apiToken);
48
+ const stored = await setSecureKeyAsync(
49
+ "credential:vercel:api_token",
50
+ msg.apiToken,
51
+ );
44
52
  if (!stored) {
45
53
  ctx.send(socket, {
46
54
  type: "vercel_api_config_response",
@@ -59,7 +67,16 @@ export function handleVercelApiConfig(
59
67
  success: true,
60
68
  });
61
69
  } else {
62
- deleteSecureKey("credential:vercel:api_token");
70
+ const r = await deleteSecureKeyAsync("credential:vercel:api_token");
71
+ if (r === "error") {
72
+ ctx.send(socket, {
73
+ type: "vercel_api_config_response",
74
+ hasToken: !!getSecureKey("credential:vercel:api_token"),
75
+ success: false,
76
+ error: "Failed to delete Vercel API token from secure storage",
77
+ });
78
+ return;
79
+ }
63
80
  deleteCredentialMetadata("vercel", "api_token");
64
81
  ctx.send(socket, {
65
82
  type: "vercel_api_config_response",
@@ -87,27 +104,27 @@ function hasTwitterClientId(): boolean {
87
104
  );
88
105
  }
89
106
 
90
- export function handleTwitterIntegrationConfig(
107
+ export async function handleTwitterIntegrationConfig(
91
108
  msg: TwitterIntegrationConfigRequest,
92
109
  socket: net.Socket,
93
110
  ctx: HandlerContext,
94
- ): void {
111
+ ): Promise<void> {
95
112
  try {
96
113
  if (msg.action === "get") {
97
114
  const raw = loadRawConfig();
98
115
  const mode =
99
- (raw.twitterIntegrationMode as "local_byo" | "managed" | undefined) ??
100
- "local_byo";
116
+ (getNestedValue(raw, "twitter.integrationMode") as
117
+ | "local_byo"
118
+ | "managed"
119
+ | undefined) ?? "local_byo";
101
120
  const strategy =
102
- (raw.twitterOperationStrategy as
121
+ (getNestedValue(raw, "twitter.operationStrategy") as
103
122
  | "oauth"
104
123
  | "browser"
105
124
  | "auto"
106
125
  | undefined) ?? "auto";
107
- const strategyConfigured = Object.prototype.hasOwnProperty.call(
108
- raw,
109
- "twitterOperationStrategy",
110
- );
126
+ const strategyConfigured =
127
+ getNestedValue(raw, "twitter.operationStrategy") !== undefined;
111
128
  const localClientConfigured = hasTwitterClientId();
112
129
  const connected = !!getSecureKey(
113
130
  "credential:integration:twitter:access_token",
@@ -127,15 +144,13 @@ export function handleTwitterIntegrationConfig(
127
144
  } else if (msg.action === "get_strategy") {
128
145
  const raw = loadRawConfig();
129
146
  const strategy =
130
- (raw.twitterOperationStrategy as
147
+ (getNestedValue(raw, "twitter.operationStrategy") as
131
148
  | "oauth"
132
149
  | "browser"
133
150
  | "auto"
134
151
  | undefined) ?? "auto";
135
- const strategyConfigured = Object.prototype.hasOwnProperty.call(
136
- raw,
137
- "twitterOperationStrategy",
138
- );
152
+ const strategyConfigured =
153
+ getNestedValue(raw, "twitter.operationStrategy") !== undefined;
139
154
  ctx.send(socket, {
140
155
  type: "twitter_integration_config_response",
141
156
  success: true,
@@ -162,7 +177,7 @@ export function handleTwitterIntegrationConfig(
162
177
  return;
163
178
  }
164
179
  const raw = loadRawConfig();
165
- raw.twitterOperationStrategy = value;
180
+ setNestedValue(raw, "twitter.operationStrategy", value);
166
181
  saveRawConfig(raw);
167
182
  ctx.send(socket, {
168
183
  type: "twitter_integration_config_response",
@@ -177,7 +192,7 @@ export function handleTwitterIntegrationConfig(
177
192
  });
178
193
  } else if (msg.action === "set_mode") {
179
194
  const raw = loadRawConfig();
180
- raw.twitterIntegrationMode = msg.mode ?? "local_byo";
195
+ setNestedValue(raw, "twitter.integrationMode", msg.mode ?? "local_byo");
181
196
  saveRawConfig(raw);
182
197
  ctx.send(socket, {
183
198
  type: "twitter_integration_config_response",
@@ -205,8 +220,8 @@ export function handleTwitterIntegrationConfig(
205
220
  const previousClientId =
206
221
  getSecureKey("credential:integration:twitter:client_id") ??
207
222
  getSecureKey("credential:integration:twitter:oauth_client_id");
208
- // Write canonical key
209
- const storedId = setSecureKey(
223
+ // Write canonical key (async — writes broker + encrypted store)
224
+ const storedId = await setSecureKeyAsync(
210
225
  "credential:integration:twitter:client_id",
211
226
  msg.clientId,
212
227
  );
@@ -222,30 +237,34 @@ export function handleTwitterIntegrationConfig(
222
237
  return;
223
238
  }
224
239
  // Also write legacy key for backward compatibility
225
- setSecureKey(
240
+ await setSecureKeyAsync(
226
241
  "credential:integration:twitter:oauth_client_id",
227
242
  msg.clientId,
228
243
  );
229
244
  if (msg.clientSecret) {
230
245
  // Write canonical key
231
- const storedSecret = setSecureKey(
246
+ const storedSecret = await setSecureKeyAsync(
232
247
  "credential:integration:twitter:client_secret",
233
248
  msg.clientSecret,
234
249
  );
235
250
  if (!storedSecret) {
236
251
  // Roll back the client ID to its previous value to avoid inconsistent OAuth state
237
252
  if (previousClientId) {
238
- setSecureKey(
253
+ await setSecureKeyAsync(
239
254
  "credential:integration:twitter:client_id",
240
255
  previousClientId,
241
256
  );
242
- setSecureKey(
257
+ await setSecureKeyAsync(
243
258
  "credential:integration:twitter:oauth_client_id",
244
259
  previousClientId,
245
260
  );
246
261
  } else {
247
- deleteSecureKey("credential:integration:twitter:client_id");
248
- deleteSecureKey("credential:integration:twitter:oauth_client_id");
262
+ await deleteSecureKeyAsync(
263
+ "credential:integration:twitter:client_id",
264
+ );
265
+ await deleteSecureKeyAsync(
266
+ "credential:integration:twitter:oauth_client_id",
267
+ );
249
268
  }
250
269
  ctx.send(socket, {
251
270
  type: "twitter_integration_config_response",
@@ -258,14 +277,18 @@ export function handleTwitterIntegrationConfig(
258
277
  return;
259
278
  }
260
279
  // Also write legacy key for backward compatibility
261
- setSecureKey(
280
+ await setSecureKeyAsync(
262
281
  "credential:integration:twitter:oauth_client_secret",
263
282
  msg.clientSecret,
264
283
  );
265
284
  } else {
266
285
  // Clear any stale secret when updating client without a secret (e.g. switching to PKCE)
267
- deleteSecureKey("credential:integration:twitter:client_secret");
268
- deleteSecureKey("credential:integration:twitter:oauth_client_secret");
286
+ await deleteSecureKeyAsync(
287
+ "credential:integration:twitter:client_secret",
288
+ );
289
+ await deleteSecureKeyAsync(
290
+ "credential:integration:twitter:oauth_client_secret",
291
+ );
269
292
  }
270
293
  ctx.send(socket, {
271
294
  type: "twitter_integration_config_response",
@@ -278,35 +301,83 @@ export function handleTwitterIntegrationConfig(
278
301
  });
279
302
  } else if (msg.action === "clear_local_client") {
280
303
  // If connected, disconnect first
304
+ const deleteResults: Array<"deleted" | "not-found" | "error"> = [];
281
305
  if (getSecureKey("credential:integration:twitter:access_token")) {
282
- deleteSecureKey("credential:integration:twitter:access_token");
283
- deleteSecureKey("credential:integration:twitter:refresh_token");
284
- deleteCredentialMetadata("integration:twitter", "access_token");
306
+ deleteResults.push(
307
+ await deleteSecureKeyAsync(
308
+ "credential:integration:twitter:access_token",
309
+ ),
310
+ );
311
+ deleteResults.push(
312
+ await deleteSecureKeyAsync(
313
+ "credential:integration:twitter:refresh_token",
314
+ ),
315
+ );
285
316
  }
286
317
  // Remove both canonical and legacy client credential keys
287
- deleteSecureKey("credential:integration:twitter:client_id");
288
- deleteSecureKey("credential:integration:twitter:client_secret");
289
- deleteSecureKey("credential:integration:twitter:oauth_client_id");
290
- deleteSecureKey("credential:integration:twitter:oauth_client_secret");
318
+ deleteResults.push(
319
+ await deleteSecureKeyAsync("credential:integration:twitter:client_id"),
320
+ );
321
+ deleteResults.push(
322
+ await deleteSecureKeyAsync(
323
+ "credential:integration:twitter:client_secret",
324
+ ),
325
+ );
326
+ deleteResults.push(
327
+ await deleteSecureKeyAsync(
328
+ "credential:integration:twitter:oauth_client_id",
329
+ ),
330
+ );
331
+ deleteResults.push(
332
+ await deleteSecureKeyAsync(
333
+ "credential:integration:twitter:oauth_client_secret",
334
+ ),
335
+ );
336
+ const hasDeleteError = deleteResults.some((r) => r === "error");
337
+ if (!hasDeleteError) {
338
+ deleteCredentialMetadata("integration:twitter", "access_token");
339
+ }
291
340
  ctx.send(socket, {
292
341
  type: "twitter_integration_config_response",
293
- success: true,
342
+ success: !hasDeleteError,
294
343
  managedAvailable: false,
295
- localClientConfigured: false,
296
- connected: false,
344
+ localClientConfigured: hasDeleteError ? hasTwitterClientId() : false,
345
+ connected: hasDeleteError
346
+ ? !!getSecureKey("credential:integration:twitter:access_token")
347
+ : false,
348
+ ...(hasDeleteError
349
+ ? {
350
+ error:
351
+ "Failed to delete some Twitter credentials from secure storage",
352
+ }
353
+ : {}),
297
354
  });
298
355
  } else if (msg.action === "disconnect") {
299
- deleteSecureKey("credential:integration:twitter:access_token");
300
- deleteSecureKey("credential:integration:twitter:refresh_token");
301
- deleteCredentialMetadata("integration:twitter", "access_token");
356
+ const dr1 = await deleteSecureKeyAsync(
357
+ "credential:integration:twitter:access_token",
358
+ );
359
+ const dr2 = await deleteSecureKeyAsync(
360
+ "credential:integration:twitter:refresh_token",
361
+ );
302
362
  // Client credentials (client_id, oauth_client_id, etc.) are intentionally
303
363
  // preserved so the user can re-connect without reconfiguring.
364
+ const disconnectFailed = dr1 === "error" || dr2 === "error";
365
+ if (!disconnectFailed) {
366
+ deleteCredentialMetadata("integration:twitter", "access_token");
367
+ }
304
368
  ctx.send(socket, {
305
369
  type: "twitter_integration_config_response",
306
- success: true,
370
+ success: !disconnectFailed,
307
371
  managedAvailable: false,
308
372
  localClientConfigured: hasTwitterClientId(),
309
- connected: false,
373
+ connected: disconnectFailed
374
+ ? !!getSecureKey("credential:integration:twitter:access_token")
375
+ : false,
376
+ ...(disconnectFailed
377
+ ? {
378
+ error: "Failed to delete Twitter tokens from secure storage",
379
+ }
380
+ : {}),
310
381
  });
311
382
  } else {
312
383
  ctx.send(socket, {
@@ -1,7 +1,7 @@
1
1
  import {
2
- deleteSecureKey,
2
+ deleteSecureKeyAsync,
3
3
  getSecureKey,
4
- setSecureKey,
4
+ setSecureKeyAsync,
5
5
  } from "../../security/secure-keys.js";
6
6
  import {
7
7
  deleteCredentialMetadata,
@@ -122,7 +122,10 @@ export async function setSlackChannelConfig(
122
122
  };
123
123
  }
124
124
 
125
- const stored = setSecureKey("credential:slack_channel:bot_token", botToken);
125
+ const stored = await setSecureKeyAsync(
126
+ "credential:slack_channel:bot_token",
127
+ botToken,
128
+ );
126
129
  if (!stored) {
127
130
  const storedBotToken = !!getSecureKey(
128
131
  "credential:slack_channel:bot_token",
@@ -166,7 +169,10 @@ export async function setSlackChannelConfig(
166
169
  };
167
170
  }
168
171
 
169
- const stored = setSecureKey("credential:slack_channel:app_token", appToken);
172
+ const stored = await setSecureKeyAsync(
173
+ "credential:slack_channel:app_token",
174
+ appToken,
175
+ );
170
176
  if (!stored) {
171
177
  const storedBotToken = !!getSecureKey(
172
178
  "credential:slack_channel:bot_token",
@@ -207,10 +213,23 @@ export async function setSlackChannelConfig(
207
213
  };
208
214
  }
209
215
 
210
- export function clearSlackChannelConfig(): SlackChannelConfigResult {
211
- deleteSecureKey("credential:slack_channel:bot_token");
216
+ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResult> {
217
+ const r1 = await deleteSecureKeyAsync("credential:slack_channel:bot_token");
218
+ const r2 = await deleteSecureKeyAsync("credential:slack_channel:app_token");
219
+
220
+ if (r1 === "error" || r2 === "error") {
221
+ const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
222
+ const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
223
+ return {
224
+ success: false,
225
+ hasBotToken,
226
+ hasAppToken,
227
+ connected: hasBotToken && hasAppToken,
228
+ error: "Failed to delete Slack channel credentials from secure storage",
229
+ };
230
+ }
231
+
212
232
  deleteCredentialMetadata("slack_channel", "bot_token");
213
- deleteSecureKey("credential:slack_channel:app_token");
214
233
  deleteCredentialMetadata("slack_channel", "app_token");
215
234
 
216
235
  return {
@@ -1,61 +1,9 @@
1
1
  import * as net from "node:net";
2
2
 
3
3
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
4
- import { getApp } from "../../memory/app-store.js";
5
- import { postToSlackWebhook } from "../../slack/slack-webhook.js";
6
- import type {
7
- ShareToSlackRequest,
8
- SlackWebhookConfigRequest,
9
- } from "../ipc-protocol.js";
4
+ import type { SlackWebhookConfigRequest } from "../ipc-protocol.js";
10
5
  import { defineHandlers, type HandlerContext, log } from "./shared.js";
11
6
 
12
- export async function handleShareToSlack(
13
- msg: ShareToSlackRequest,
14
- socket: net.Socket,
15
- ctx: HandlerContext,
16
- ): Promise<void> {
17
- try {
18
- const config = loadRawConfig();
19
- const webhookUrl = config.slackWebhookUrl as string | undefined;
20
- if (!webhookUrl) {
21
- ctx.send(socket, {
22
- type: "share_to_slack_response",
23
- success: false,
24
- error:
25
- "No Slack webhook URL configured. Provide one here in the chat, or set it from the Settings page.",
26
- });
27
- return;
28
- }
29
-
30
- const app = getApp(msg.appId);
31
- if (!app) {
32
- ctx.send(socket, {
33
- type: "share_to_slack_response",
34
- success: false,
35
- error: `App not found: ${msg.appId}`,
36
- });
37
- return;
38
- }
39
-
40
- await postToSlackWebhook(
41
- webhookUrl,
42
- app.name,
43
- app.description ?? "",
44
- "\u{1F4F1}",
45
- );
46
-
47
- ctx.send(socket, { type: "share_to_slack_response", success: true });
48
- } catch (err) {
49
- const message = err instanceof Error ? err.message : String(err);
50
- log.error({ err, appId: msg.appId }, "Failed to share app to Slack");
51
- ctx.send(socket, {
52
- type: "share_to_slack_response",
53
- success: false,
54
- error: message,
55
- });
56
- }
57
- }
58
-
59
7
  export function handleSlackWebhookConfig(
60
8
  msg: SlackWebhookConfigRequest,
61
9
  socket: net.Socket,
@@ -89,6 +37,5 @@ export function handleSlackWebhookConfig(
89
37
  }
90
38
 
91
39
  export const slackHandlers = defineHandlers({
92
- share_to_slack: handleShareToSlack,
93
40
  slack_webhook_config: handleSlackWebhookConfig,
94
41
  });
@@ -6,9 +6,9 @@ import {
6
6
  shouldUsePlatformCallbacks,
7
7
  } from "../../inbound/platform-callback-registration.js";
8
8
  import {
9
- deleteSecureKey,
9
+ deleteSecureKeyAsync,
10
10
  getSecureKey,
11
- setSecureKey,
11
+ setSecureKeyAsync,
12
12
  } from "../../security/secure-keys.js";
13
13
  import {
14
14
  deleteCredentialMetadata,
@@ -130,8 +130,11 @@ export async function setTelegramConfig(
130
130
  };
131
131
  }
132
132
 
133
- // Store bot token securely
134
- const stored = setSecureKey("credential:telegram:bot_token", resolvedToken);
133
+ // Store bot token securely (async — writes broker + encrypted store)
134
+ const stored = await setSecureKeyAsync(
135
+ "credential:telegram:bot_token",
136
+ resolvedToken,
137
+ );
135
138
  if (!stored) {
136
139
  return {
137
140
  success: false,
@@ -152,7 +155,7 @@ export async function setTelegramConfig(
152
155
  if (!hasWebhookSecret) {
153
156
  const { randomUUID } = await import("node:crypto");
154
157
  const webhookSecret = randomUUID();
155
- const secretStored = setSecureKey(
158
+ const secretStored = await setSecureKeyAsync(
156
159
  "credential:telegram:webhook_secret",
157
160
  webhookSecret,
158
161
  );
@@ -164,7 +167,7 @@ export async function setTelegramConfig(
164
167
  // When the token came from secure storage it was already valid
165
168
  // configuration; deleting it would destroy working state.
166
169
  if (isNewToken) {
167
- deleteSecureKey("credential:telegram:bot_token");
170
+ await deleteSecureKeyAsync("credential:telegram:bot_token");
168
171
  deleteCredentialMetadata("telegram", "bot_token");
169
172
  }
170
173
  return {
@@ -226,10 +229,8 @@ export async function clearTelegramConfig(): Promise<TelegramConfigResult> {
226
229
  }
227
230
  }
228
231
 
229
- deleteSecureKey("credential:telegram:bot_token");
230
- deleteCredentialMetadata("telegram", "bot_token");
231
- deleteSecureKey("credential:telegram:webhook_secret");
232
- deleteCredentialMetadata("telegram", "webhook_secret");
232
+ const r1 = await deleteSecureKeyAsync("credential:telegram:bot_token");
233
+ const r2 = await deleteSecureKeyAsync("credential:telegram:webhook_secret");
233
234
 
234
235
  // Trigger reconcile to deregister webhook
235
236
  const effectiveUrl = getIngressPublicBaseUrl();
@@ -237,6 +238,23 @@ export async function clearTelegramConfig(): Promise<TelegramConfigResult> {
237
238
  triggerGatewayReconcile(effectiveUrl);
238
239
  }
239
240
 
241
+ if (r1 === "error" || r2 === "error") {
242
+ const hasBotToken = !!getSecureKey("credential:telegram:bot_token");
243
+ const hasWebhookSecret = !!getSecureKey(
244
+ "credential:telegram:webhook_secret",
245
+ );
246
+ return {
247
+ success: false,
248
+ hasBotToken,
249
+ connected: hasBotToken && hasWebhookSecret,
250
+ hasWebhookSecret,
251
+ error: "Failed to delete Telegram credentials from secure storage",
252
+ };
253
+ }
254
+
255
+ deleteCredentialMetadata("telegram", "bot_token");
256
+ deleteCredentialMetadata("telegram", "webhook_secret");
257
+
240
258
  return {
241
259
  success: true,
242
260
  hasBotToken: false,
@@ -40,10 +40,7 @@ export {
40
40
  handleSchedulesList,
41
41
  handleScheduleToggle,
42
42
  } from "./config-scheduling.js";
43
- export {
44
- handleShareToSlack,
45
- handleSlackWebhookConfig,
46
- } from "./config-slack.js";
43
+ export { handleSlackWebhookConfig } from "./config-slack.js";
47
44
  export {
48
45
  handleTelegramConfig,
49
46
  summarizeTelegramError,
@@ -1,6 +1,10 @@
1
1
  import * as net from "node:net";
2
2
 
3
- import { loadConfig, loadRawConfig } from "../../config/loader.js";
3
+ import {
4
+ getNestedValue,
5
+ loadConfig,
6
+ loadRawConfig,
7
+ } from "../../config/loader.js";
4
8
  import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
5
9
  import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
6
10
  import { getSecureKey } from "../../security/secure-keys.js";
@@ -51,7 +55,8 @@ export async function handleTwitterAuthStart(
51
55
  try {
52
56
  const raw = loadRawConfig();
53
57
  const mode =
54
- (raw.twitterIntegrationMode as string | undefined) ?? "local_byo";
58
+ (getNestedValue(raw, "twitter.integrationMode") as string | undefined) ??
59
+ "local_byo";
55
60
  if (mode !== "local_byo") {
56
61
  ctx.send(socket, {
57
62
  type: "twitter_auth_result",
@@ -186,8 +191,10 @@ export function handleTwitterAuthStatus(
186
191
  );
187
192
  const raw = loadRawConfig();
188
193
  const mode =
189
- (raw.twitterIntegrationMode as "local_byo" | "managed" | undefined) ??
190
- "local_byo";
194
+ (getNestedValue(raw, "twitter.integrationMode") as
195
+ | "local_byo"
196
+ | "managed"
197
+ | undefined) ?? "local_byo";
191
198
  const meta = getCredentialMetadata("integration:twitter", "access_token");
192
199
 
193
200
  ctx.send(socket, {
@@ -127,11 +127,6 @@ export interface ShareAppCloudRequest {
127
127
  appId: string;
128
128
  }
129
129
 
130
- export interface ShareToSlackRequest {
131
- type: "share_to_slack";
132
- appId: string;
133
- }
134
-
135
130
  export interface PublishPageRequest {
136
131
  type: "publish_page";
137
132
  html: string;
@@ -341,12 +336,6 @@ export interface AppRestoreResponse {
341
336
  error?: string;
342
337
  }
343
338
 
344
- export interface ShareToSlackResponse {
345
- type: "share_to_slack_response";
346
- success: boolean;
347
- error?: string;
348
- }
349
-
350
339
  export interface PublishPageResponse {
351
340
  type: "publish_page_response";
352
341
  success: boolean;
@@ -389,7 +378,6 @@ export type _AppsClientMessages =
389
378
  | AppFileAtVersionRequest
390
379
  | AppRestoreRequest
391
380
  | ShareAppCloudRequest
392
- | ShareToSlackRequest
393
381
  | AppUpdatePreviewRequest
394
382
  | AppPreviewRequest
395
383
  | PublishPageRequest
@@ -414,7 +402,6 @@ export type _AppsServerMessages =
414
402
  | AppDiffResponse
415
403
  | AppFileAtVersionResponse
416
404
  | AppRestoreResponse
417
- | ShareToSlackResponse
418
405
  | AppUpdatePreviewResponse
419
406
  | AppPreviewResponse
420
407
  | PublishPageResponse
@@ -134,7 +134,6 @@
134
134
  "session_switch",
135
135
  "sessions_clear",
136
136
  "share_app_cloud",
137
- "share_to_slack",
138
137
  "shared_app_delete",
139
138
  "shared_apps_list",
140
139
  "sign_bundle_payload_response",
@@ -291,7 +290,6 @@
291
290
  "session_title_updated",
292
291
  "sessions_clear_response",
293
292
  "share_app_cloud_response",
294
- "share_to_slack_response",
295
293
  "shared_app_delete_response",
296
294
  "shared_apps_list_response",
297
295
  "sign_bundle_payload",
@@ -10,6 +10,7 @@ import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
10
10
  import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
11
11
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
12
12
  import {
13
+ getQdrantHttpPortEnv,
13
14
  getQdrantUrlEnv,
14
15
  getRuntimeHttpHost,
15
16
  getRuntimeHttpPort,
@@ -250,7 +251,13 @@ export async function runDaemon(): Promise<void> {
250
251
  log.info("Daemon startup: DaemonServer started");
251
252
 
252
253
  // Initialize Qdrant vector store — non-fatal so the daemon stays up without it
253
- const qdrantUrl = getQdrantUrlEnv() || config.memory.qdrant.url;
254
+ // Prefer QDRANT_HTTP_PORT (locally-spawned Qdrant on a specific port) over
255
+ // QDRANT_URL (external Qdrant instance) so the CLI can set the port without
256
+ // triggering QdrantManager's external mode which skips local process spawn.
257
+ const qdrantHttpPort = getQdrantHttpPortEnv();
258
+ const qdrantUrl = qdrantHttpPort
259
+ ? `http://127.0.0.1:${qdrantHttpPort}`
260
+ : getQdrantUrlEnv() || config.memory.qdrant.url;
254
261
  log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
255
262
  const qdrantManager = new QdrantManager({ url: qdrantUrl });
256
263
  try {
@@ -403,7 +403,7 @@ export function redirectToSecurePrompt(
403
403
  .then(async (result): Promise<void> => {
404
404
  if (!result.value) return;
405
405
 
406
- const { setSecureKey } = await import("../security/secure-keys.js");
406
+ const { setSecureKeyAsync } = await import("../security/secure-keys.js");
407
407
  const { upsertCredentialMetadata } =
408
408
  await import("../tools/credentials/metadata-store.js");
409
409
 
@@ -435,7 +435,7 @@ export function redirectToSecurePrompt(
435
435
  );
436
436
  } else {
437
437
  const key = `credential:${target.service}:${target.field}`;
438
- const stored = setSecureKey(key, result.value);
438
+ const stored = await setSecureKeyAsync(key, result.value);
439
439
  if (stored) {
440
440
  try {
441
441
  upsertCredentialMetadata(target.service, target.field, {});