@vellumai/assistant 0.4.43 → 0.4.44

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 (88) hide show
  1. package/ARCHITECTURE.md +13 -14
  2. package/README.md +11 -12
  3. package/docs/architecture/integrations.md +75 -93
  4. package/package.json +1 -1
  5. package/src/__tests__/approval-routes-http.test.ts +0 -2
  6. package/src/__tests__/bundled-asset.test.ts +1 -1
  7. package/src/__tests__/checker.test.ts +31 -28
  8. package/src/__tests__/conversation-routes-guardian-reply.test.ts +6 -6
  9. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  10. package/src/__tests__/error-handler-friendly-messages.test.ts +46 -0
  11. package/src/__tests__/managed-twitter-guardrails.test.ts +5 -1
  12. package/src/__tests__/onboarding-template-contract.test.ts +0 -10
  13. package/src/__tests__/provider-fail-open-selection.test.ts +12 -2
  14. package/src/__tests__/send-endpoint-busy.test.ts +0 -3
  15. package/src/__tests__/session-confirmation-signals.test.ts +7 -45
  16. package/src/__tests__/starter-task-flow.test.ts +9 -19
  17. package/src/__tests__/system-prompt.test.ts +3 -4
  18. package/src/__tests__/trust-store.test.ts +4 -4
  19. package/src/__tests__/twitter-platform-proxy-client.test.ts +43 -18
  20. package/src/cli/commands/amazon/index.ts +4 -39
  21. package/src/cli/commands/amazon/session.ts +18 -26
  22. package/src/cli/commands/twitter/__tests__/cli-read-routing.test.ts +58 -196
  23. package/src/cli/commands/twitter/__tests__/cli-routing.test.ts +26 -186
  24. package/src/cli/commands/twitter/__tests__/oauth-client.test.ts +1 -47
  25. package/src/cli/commands/twitter/index.ts +95 -835
  26. package/src/cli/commands/twitter/oauth-client.ts +1 -35
  27. package/src/cli/commands/twitter/router.ts +70 -115
  28. package/src/cli/commands/twitter/types.ts +30 -0
  29. package/src/cli/reference.ts +2 -2
  30. package/src/config/bundled-skills/amazon/SKILL.md +0 -1
  31. package/src/config/bundled-skills/app-builder/SKILL.md +0 -6
  32. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -4
  33. package/src/config/bundled-skills/doordash/SKILL.md +0 -1
  34. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +1 -82
  35. package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -28
  36. package/src/config/bundled-skills/doordash/lib/session.ts +21 -17
  37. package/src/config/bundled-skills/twitter/SKILL.md +53 -166
  38. package/src/config/feature-flag-registry.json +8 -0
  39. package/src/daemon/handlers/session-history.ts +41 -9
  40. package/src/daemon/lifecycle.ts +4 -17
  41. package/src/daemon/message-types/apps.ts +0 -25
  42. package/src/daemon/message-types/integrations.ts +1 -7
  43. package/src/daemon/message-types/sessions.ts +6 -1
  44. package/src/daemon/message-types/surfaces.ts +2 -0
  45. package/src/daemon/ride-shotgun-handler.ts +33 -1
  46. package/src/daemon/seed-files.ts +3 -27
  47. package/src/daemon/server.ts +2 -18
  48. package/src/daemon/session-agent-loop-handlers.ts +24 -2
  49. package/src/daemon/session-runtime-assembly.ts +0 -7
  50. package/src/daemon/session-surfaces.ts +185 -33
  51. package/src/daemon/session.ts +2 -28
  52. package/src/memory/app-store.ts +0 -18
  53. package/src/memory/schema/infrastructure.ts +0 -8
  54. package/src/permissions/defaults.ts +3 -3
  55. package/src/prompts/system-prompt.ts +4 -5
  56. package/src/prompts/templates/BOOTSTRAP.md +0 -3
  57. package/src/providers/registry.ts +2 -4
  58. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  59. package/src/runtime/auth/__tests__/scopes.test.ts +2 -1
  60. package/src/runtime/auth/route-policy.ts +0 -4
  61. package/src/runtime/auth/scopes.ts +1 -0
  62. package/src/runtime/auth/token-service.ts +1 -1
  63. package/src/runtime/http-types.ts +10 -0
  64. package/src/runtime/middleware/error-handler.ts +14 -1
  65. package/src/runtime/routes/app-management-routes.ts +61 -64
  66. package/src/runtime/routes/brain-graph/brain-graph.html +1845 -0
  67. package/src/runtime/routes/brain-graph-routes.ts +4 -42
  68. package/src/runtime/routes/conversation-routes.ts +9 -6
  69. package/src/runtime/routes/diagnostics-routes.ts +91 -14
  70. package/src/runtime/routes/settings-routes.ts +3 -93
  71. package/src/tools/AGENTS.md +38 -0
  72. package/src/tools/apps/executors.ts +0 -6
  73. package/src/tools/document/editor-template.ts +10 -8
  74. package/src/twitter/platform-proxy-client.ts +6 -3
  75. package/src/util/errors.ts +12 -0
  76. package/src/__tests__/home-base-bootstrap.test.ts +0 -84
  77. package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -79
  78. package/src/cli/commands/twitter/__tests__/cli-error-shaping.test.ts +0 -265
  79. package/src/cli/commands/twitter/client.ts +0 -989
  80. package/src/cli/commands/twitter/session.ts +0 -121
  81. package/src/home-base/app-link-store.ts +0 -78
  82. package/src/home-base/bootstrap.ts +0 -74
  83. package/src/home-base/prebuilt/brain-graph.html +0 -1483
  84. package/src/home-base/prebuilt/index.html +0 -702
  85. package/src/home-base/prebuilt/seed-metadata.json +0 -21
  86. package/src/home-base/prebuilt/seed.ts +0 -122
  87. package/src/home-base/prebuilt-home-base-updater.ts +0 -36
  88. package/src/util/cookie-session.ts +0 -98
@@ -8,19 +8,12 @@ let mockOauthPostResult: {
8
8
  url?: string;
9
9
  } | null = null;
10
10
  let mockOauthPostError: Error | null = null;
11
- let mockBrowserPostResult: {
12
- tweetId: string;
13
- text: string;
14
- url: string;
15
- } | null = null;
16
- let mockBrowserPostError: Error | null = null;
17
11
  let mockManagedPostResult: { data: unknown; status: number } | null = null;
18
12
  let mockManagedPostError: Error | null = null;
19
13
 
20
14
  // Mock the OAuth client
21
15
  mock.module("../oauth-client.js", () => ({
22
16
  oauthIsAvailable: (token?: string) => token != null && token.length > 0,
23
- oauthSupportsOperation: (op: string) => op === "post" || op === "reply",
24
17
  oauthPostTweet: async (
25
18
  _text: string,
26
19
  _opts: { inReplyToTweetId?: string; oauthToken: string },
@@ -29,16 +22,6 @@ mock.module("../oauth-client.js", () => ({
29
22
  if (mockOauthPostResult) return mockOauthPostResult;
30
23
  throw new Error("OAuth mock not configured");
31
24
  },
32
- UnsupportedOAuthOperationError: class UnsupportedOAuthOperationError extends Error {
33
- public readonly suggestFallback = true;
34
- public readonly fallbackPath = "browser" as const;
35
- public readonly operation: string;
36
- constructor(operation: string) {
37
- super(`The "${operation}" operation is not available via the OAuth API.`);
38
- this.name = "UnsupportedOAuthOperationError";
39
- this.operation = operation;
40
- }
41
- },
42
25
  }));
43
26
 
44
27
  // Mock TwitterProxyError for managed path tests
@@ -70,24 +53,6 @@ mock.module("../../../../twitter/platform-proxy-client.js", () => ({
70
53
  TwitterProxyError: MockTwitterProxyError,
71
54
  }));
72
55
 
73
- // Create a SessionExpiredError class that matches the real one
74
- class MockSessionExpiredError extends Error {
75
- constructor(reason: string) {
76
- super(reason);
77
- this.name = "SessionExpiredError";
78
- }
79
- }
80
-
81
- // Mock the browser client
82
- mock.module("../client.js", () => ({
83
- postTweet: async (_text: string, _opts?: { inReplyToTweetId?: string }) => {
84
- if (mockBrowserPostError) throw mockBrowserPostError;
85
- if (mockBrowserPostResult) return mockBrowserPostResult;
86
- throw new Error("Browser mock not configured");
87
- },
88
- SessionExpiredError: MockSessionExpiredError,
89
- }));
90
-
91
56
  // Mock the logger to silence output
92
57
  mock.module("../../../../util/logger.js", () => ({
93
58
  getLogger: () => ({
@@ -111,114 +76,25 @@ import { routedPostTweet } from "../router.js";
111
76
  beforeEach(() => {
112
77
  mockOauthPostResult = null;
113
78
  mockOauthPostError = null;
114
- mockBrowserPostResult = null;
115
- mockBrowserPostError = null;
116
79
  mockManagedPostResult = null;
117
80
  mockManagedPostError = null;
118
81
  });
119
82
 
120
- describe("Twitter strategy router", () => {
121
- describe("auto strategy", () => {
122
- test("uses OAuth when available and supported", async () => {
123
- mockOauthPostResult = {
124
- tweetId: "111",
125
- text: "hello",
126
- url: "https://x.com/u/status/111",
127
- };
128
-
129
- const { result, pathUsed } = await routedPostTweet("hello", {
130
- strategy: "auto",
131
- oauthToken: "test-token",
132
- });
133
-
134
- expect(pathUsed).toBe("oauth");
135
- expect(result.tweetId).toBe("111");
136
- expect(result.text).toBe("hello");
137
- expect(result.url).toBe("https://x.com/u/status/111");
138
- });
139
-
140
- test("falls back to browser when OAuth is unavailable", async () => {
141
- mockBrowserPostResult = {
142
- tweetId: "222",
143
- text: "hello",
144
- url: "https://x.com/u/status/222",
145
- };
146
-
147
- const { result, pathUsed } = await routedPostTweet("hello", {
148
- strategy: "auto",
149
- });
150
-
151
- expect(pathUsed).toBe("browser");
152
- expect(result.tweetId).toBe("222");
153
- });
154
-
155
- test("falls back to browser when OAuth fails", async () => {
156
- mockOauthPostError = new Error("OAuth token expired");
157
- mockBrowserPostResult = {
158
- tweetId: "333",
159
- text: "hello",
160
- url: "https://x.com/u/status/333",
161
- };
162
-
163
- const { result, pathUsed } = await routedPostTweet("hello", {
164
- strategy: "auto",
165
- oauthToken: "test-token",
166
- });
167
-
168
- expect(pathUsed).toBe("browser");
169
- expect(result.tweetId).toBe("333");
170
- });
171
-
172
- test("constructs URL from tweetId when OAuth result has no url", async () => {
173
- mockOauthPostResult = { tweetId: "444", text: "no url" };
174
-
175
- const { result, pathUsed } = await routedPostTweet("no url", {
176
- strategy: "auto",
177
- oauthToken: "test-token",
178
- });
179
-
180
- expect(pathUsed).toBe("oauth");
181
- expect(result.url).toBe("https://x.com/i/status/444");
182
- });
183
-
184
- test("throws combined error when both OAuth and browser fail with SessionExpiredError", async () => {
185
- mockOauthPostError = new Error("OAuth failed");
186
- mockBrowserPostError = new MockSessionExpiredError(
187
- "Browser session expired",
188
- );
189
-
190
- try {
191
- await routedPostTweet("will fail", {
192
- strategy: "auto",
193
- oauthToken: "test-token",
194
- });
195
- expect(true).toBe(false); // should not reach
196
- } catch (err) {
197
- const e = err as Error & { pathUsed: string; oauthError?: string };
198
- expect(e).toBeInstanceOf(MockSessionExpiredError);
199
- expect(e.message).toBe("Browser session expired");
200
- expect(e.pathUsed).toBe("auto");
201
- expect(e.oauthError).toBe("OAuth failed");
202
- }
203
- });
204
- });
205
-
206
- describe("explicit oauth strategy", () => {
83
+ describe("Twitter mode router", () => {
84
+ describe("explicit oauth mode", () => {
207
85
  test("fails with helpful error when OAuth is not configured", async () => {
208
86
  try {
209
- await routedPostTweet("hello", { strategy: "oauth" });
87
+ await routedPostTweet("hello", { mode: "oauth" });
210
88
  expect(true).toBe(false); // should not reach
211
89
  } catch (err) {
212
90
  const e = err as Error & {
213
91
  pathUsed: string;
214
- suggestAlternative: string;
215
92
  };
216
93
  expect(e.message).toContain("OAuth is not configured");
217
94
  expect(e.message).toContain(
218
- "assistant config set twitter.operationStrategy browser",
95
+ "Connect your X developer credentials to set up OAuth",
219
96
  );
220
97
  expect(e.pathUsed).toBe("oauth");
221
- expect(e.suggestAlternative).toBe("browser");
222
98
  }
223
99
  });
224
100
 
@@ -226,63 +102,28 @@ describe("Twitter strategy router", () => {
226
102
  mockOauthPostResult = { tweetId: "555", text: "oauth post" };
227
103
 
228
104
  const { result, pathUsed } = await routedPostTweet("oauth post", {
229
- strategy: "oauth",
105
+ mode: "oauth",
230
106
  oauthToken: "test-token",
231
107
  });
232
108
 
233
109
  expect(pathUsed).toBe("oauth");
234
110
  expect(result.tweetId).toBe("555");
235
111
  });
236
- });
237
112
 
238
- describe("explicit browser strategy", () => {
239
- test("uses browser directly, ignoring OAuth availability", async () => {
240
- mockBrowserPostResult = {
241
- tweetId: "666",
242
- text: "browser post",
243
- url: "https://x.com/u/status/666",
244
- };
113
+ test("constructs URL from tweetId when OAuth result has no url", async () => {
114
+ mockOauthPostResult = { tweetId: "444", text: "no url" };
245
115
 
246
- const { result, pathUsed } = await routedPostTweet("browser post", {
247
- strategy: "browser",
248
- oauthToken: "test-token", // available but should be ignored
116
+ const { result, pathUsed } = await routedPostTweet("no url", {
117
+ mode: "oauth",
118
+ oauthToken: "test-token",
249
119
  });
250
120
 
251
- expect(pathUsed).toBe("browser");
252
- expect(result.tweetId).toBe("666");
253
- });
254
-
255
- test("preserves SessionExpiredError type with router metadata", async () => {
256
- mockBrowserPostError = new MockSessionExpiredError("Session expired");
257
-
258
- try {
259
- await routedPostTweet("will fail", { strategy: "browser" });
260
- expect(true).toBe(false); // should not reach
261
- } catch (err) {
262
- const e = err as Error & {
263
- pathUsed: string;
264
- suggestAlternative: string;
265
- };
266
- expect(e).toBeInstanceOf(MockSessionExpiredError);
267
- expect(e.message).toBe("Session expired");
268
- expect(e.pathUsed).toBe("browser");
269
- expect(e.suggestAlternative).toBe("oauth");
270
- }
271
- });
272
-
273
- test("re-throws non-session errors without wrapping", async () => {
274
- mockBrowserPostError = new Error("Network failure");
275
-
276
- try {
277
- await routedPostTweet("will fail", { strategy: "browser" });
278
- expect(true).toBe(false); // should not reach
279
- } catch (err) {
280
- expect((err as Error).message).toBe("Network failure");
281
- }
121
+ expect(pathUsed).toBe("oauth");
122
+ expect(result.url).toBe("https://x.com/i/status/444");
282
123
  });
283
124
  });
284
125
 
285
- describe("managed strategy", () => {
126
+ describe("managed mode", () => {
286
127
  test("routes post through platform proxy", async () => {
287
128
  mockManagedPostResult = {
288
129
  data: { data: { id: "managed-1" } },
@@ -290,7 +131,7 @@ describe("Twitter strategy router", () => {
290
131
  };
291
132
 
292
133
  const { result, pathUsed } = await routedPostTweet("managed post", {
293
- strategy: "managed",
134
+ mode: "managed",
294
135
  });
295
136
 
296
137
  expect(pathUsed).toBe("managed");
@@ -305,7 +146,7 @@ describe("Twitter strategy router", () => {
305
146
  };
306
147
 
307
148
  const { result, pathUsed } = await routedPostTweet("reply text", {
308
- strategy: "managed",
149
+ mode: "managed",
309
150
  inReplyToTweetId: "original-tweet-123",
310
151
  });
311
152
 
@@ -322,7 +163,7 @@ describe("Twitter strategy router", () => {
322
163
  );
323
164
 
324
165
  try {
325
- await routedPostTweet("will fail", { strategy: "managed" });
166
+ await routedPostTweet("will fail", { mode: "managed" });
326
167
  expect(true).toBe(false); // should not reach
327
168
  } catch (err) {
328
169
  const e = err as Error & {
@@ -348,7 +189,7 @@ describe("Twitter strategy router", () => {
348
189
  );
349
190
 
350
191
  try {
351
- await routedPostTweet("will fail", { strategy: "managed" });
192
+ await routedPostTweet("will fail", { mode: "managed" });
352
193
  expect(true).toBe(false);
353
194
  } catch (err) {
354
195
  const e = err as Error & {
@@ -366,7 +207,7 @@ describe("Twitter strategy router", () => {
366
207
  mockManagedPostError = new Error("Network failure");
367
208
 
368
209
  try {
369
- await routedPostTweet("will fail", { strategy: "managed" });
210
+ await routedPostTweet("will fail", { mode: "managed" });
370
211
  expect(true).toBe(false);
371
212
  } catch (err) {
372
213
  expect((err as Error).message).toBe("Network failure");
@@ -376,7 +217,7 @@ describe("Twitter strategy router", () => {
376
217
  });
377
218
 
378
219
  describe("reply routing", () => {
379
- test("auto strategy routes reply through OAuth when available", async () => {
220
+ test("oauth mode routes reply through OAuth when available", async () => {
380
221
  mockOauthPostResult = {
381
222
  tweetId: "777",
382
223
  text: "reply text",
@@ -385,7 +226,7 @@ describe("Twitter strategy router", () => {
385
226
 
386
227
  const { result, pathUsed } = await routedPostTweet("reply text", {
387
228
  inReplyToTweetId: "100",
388
- strategy: "auto",
229
+ mode: "oauth",
389
230
  oauthToken: "test-token",
390
231
  });
391
232
 
@@ -393,19 +234,18 @@ describe("Twitter strategy router", () => {
393
234
  expect(result.tweetId).toBe("777");
394
235
  });
395
236
 
396
- test("browser strategy routes reply through browser", async () => {
397
- mockBrowserPostResult = {
398
- tweetId: "888",
399
- text: "reply text",
400
- url: "https://x.com/u/status/888",
237
+ test("managed mode routes reply through platform proxy", async () => {
238
+ mockManagedPostResult = {
239
+ data: { data: { id: "888" } },
240
+ status: 200,
401
241
  };
402
242
 
403
243
  const { result, pathUsed } = await routedPostTweet("reply text", {
404
244
  inReplyToTweetId: "200",
405
- strategy: "browser",
245
+ mode: "managed",
406
246
  });
407
247
 
408
- expect(pathUsed).toBe("browser");
248
+ expect(pathUsed).toBe("managed");
409
249
  expect(result.tweetId).toBe("888");
410
250
  });
411
251
  });
@@ -19,12 +19,7 @@ mock.module("../../../../util/logger.js", () => ({
19
19
  }),
20
20
  }));
21
21
 
22
- import {
23
- oauthIsAvailable,
24
- oauthPostTweet,
25
- oauthSupportsOperation,
26
- UnsupportedOAuthOperationError,
27
- } from "../oauth-client.js";
22
+ import { oauthIsAvailable, oauthPostTweet } from "../oauth-client.js";
28
23
 
29
24
  // --- Global fetch mock ---
30
25
 
@@ -153,45 +148,4 @@ describe("Twitter OAuth client", () => {
153
148
  expect(oauthIsAvailable("")).toBe(false);
154
149
  });
155
150
  });
156
-
157
- describe("oauthSupportsOperation", () => {
158
- test("returns true for post", () => {
159
- expect(oauthSupportsOperation("post")).toBe(true);
160
- });
161
-
162
- test("returns true for reply", () => {
163
- expect(oauthSupportsOperation("reply")).toBe(true);
164
- });
165
-
166
- test("returns false for unsupported operations", () => {
167
- const unsupported = [
168
- "timeline",
169
- "search",
170
- "bookmarks",
171
- "home",
172
- "notifications",
173
- "likes",
174
- "followers",
175
- "following",
176
- "media",
177
- "tweet",
178
- ];
179
- for (const op of unsupported) {
180
- expect(oauthSupportsOperation(op)).toBe(false);
181
- }
182
- });
183
- });
184
-
185
- describe("UnsupportedOAuthOperationError", () => {
186
- test("has correct properties", () => {
187
- const err = new UnsupportedOAuthOperationError("search");
188
- expect(err.name).toBe("UnsupportedOAuthOperationError");
189
- expect(err.operation).toBe("search");
190
- expect(err.suggestFallback).toBe(true);
191
- expect(err.fallbackPath).toBe("browser");
192
- expect(err.message).toContain("search");
193
- expect(err.message).toContain("not available via the OAuth API");
194
- expect(err).toBeInstanceOf(Error);
195
- });
196
- });
197
151
  });