@vellumai/assistant 0.12.0-staging.1 → 0.12.0

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 (92) hide show
  1. package/Dockerfile +5 -0
  2. package/docs/architecture/turn-actor.md +9 -0
  3. package/knip.json +1 -0
  4. package/node_modules/@vellumai/app-icons/package.json +18 -0
  5. package/node_modules/@vellumai/app-icons/src/index.test.ts +85 -0
  6. package/node_modules/@vellumai/app-icons/src/index.ts +387 -0
  7. package/node_modules/@vellumai/app-icons/tsconfig.json +20 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +31 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +28 -3
  10. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +31 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +28 -3
  12. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +15 -2
  15. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +31 -0
  16. package/node_modules/@vellumai/service-contracts/src/channels.ts +28 -3
  17. package/openapi.yaml +628 -0
  18. package/package.json +3 -1
  19. package/scripts/smoke-container-workspace-dependencies.ts +19 -0
  20. package/src/__tests__/app-builder-icon-names.test.ts +29 -0
  21. package/src/__tests__/assistant-attachment-directive.test.ts +4 -0
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  24. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  25. package/src/__tests__/conversation-attachments.test.ts +142 -0
  26. package/src/__tests__/conversation-delete-activation-progress.test.ts +145 -0
  27. package/src/__tests__/conversation-event-sink.test.ts +50 -0
  28. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -2
  29. package/src/__tests__/conversation-queue.test.ts +297 -0
  30. package/src/__tests__/credential-routes.test.ts +185 -6
  31. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  32. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  33. package/src/__tests__/messaging-send-tool.test.ts +42 -0
  34. package/src/__tests__/oauth-commands-routes.test.ts +47 -0
  35. package/src/__tests__/subagent-manager-notify.test.ts +25 -4
  36. package/src/activation/progress-store.test.ts +1564 -0
  37. package/src/activation/progress-store.ts +1296 -0
  38. package/src/activation/turn-hooks.test.ts +246 -0
  39. package/src/activation/turn-hooks.ts +126 -0
  40. package/src/api/events/subagent-status-changed.ts +7 -5
  41. package/src/api/responses/activation.ts +136 -0
  42. package/src/apps/app-store.ts +8 -0
  43. package/src/cli/__tests__/catalog-search-help.test.ts +7 -5
  44. package/src/cli/commands/__tests__/cli-test-harness.ts +12 -3
  45. package/src/cli/commands/channels/__tests__/channels.test.ts +2 -0
  46. package/src/cli/commands/channels/__tests__/request.test.ts +191 -0
  47. package/src/cli/commands/channels/index.help.ts +70 -18
  48. package/src/cli/commands/channels/index.ts +17 -6
  49. package/src/cli/commands/channels/request.ts +66 -0
  50. package/src/cli/commands/oauth/request.test.ts +2 -0
  51. package/src/cli/commands/oauth/request.ts +227 -186
  52. package/src/config/bundled-skills/app-builder/SKILL.md +3 -1
  53. package/src/config/bundled-skills/app-builder/TOOLS.json +2 -2
  54. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +8 -4
  55. package/src/config/feature-flag-registry.json +35 -5
  56. package/src/daemon/assistant-attachments.ts +11 -0
  57. package/src/daemon/conversation-agent-loop-handlers.ts +5 -0
  58. package/src/daemon/conversation-agent-loop.ts +71 -3
  59. package/src/daemon/conversation-attachments.ts +42 -1
  60. package/src/daemon/conversation-event-sink.ts +25 -0
  61. package/src/daemon/conversation-process.ts +69 -21
  62. package/src/daemon/conversation-store.ts +4 -3
  63. package/src/daemon/conversation-surfaces.ts +19 -4
  64. package/src/daemon/message-types/sync.ts +2 -0
  65. package/src/ipc/assistant-server.ts +2 -0
  66. package/src/ipc/routes/__tests__/activation-sync-ipc-routes.test.ts +51 -0
  67. package/src/ipc/routes/activation-sync-ipc-routes.ts +42 -0
  68. package/src/notifications/AGENTS.md +1 -1
  69. package/src/notifications/__tests__/proactive-home-thread.test.ts +111 -0
  70. package/src/notifications/conversation-pairing.ts +47 -5
  71. package/src/notifications/delivered-post-record.ts +3 -0
  72. package/src/persistence/__tests__/slack-thread-root-evidence.test.ts +102 -0
  73. package/src/persistence/conversation-crud.ts +39 -0
  74. package/src/persistence/delivery-crud.ts +13 -0
  75. package/src/plugins/AGENTS.md +1 -0
  76. package/src/runtime/auth/__tests__/route-policy.test.ts +37 -0
  77. package/src/runtime/routes/__tests__/user-routes-notices.test.ts +248 -0
  78. package/src/runtime/routes/activation-routes.test.ts +415 -0
  79. package/src/runtime/routes/activation-routes.ts +172 -0
  80. package/src/runtime/routes/credential-routes.ts +46 -3
  81. package/src/runtime/routes/index.ts +2 -0
  82. package/src/runtime/routes/oauth-commands-routes.ts +21 -8
  83. package/src/runtime/routes/platform-managed-credentials.ts +48 -0
  84. package/src/runtime/routes/secret-routes.ts +3 -12
  85. package/src/runtime/routes/user-route-resolution.ts +21 -0
  86. package/src/runtime/routes/user-routes.ts +112 -9
  87. package/src/runtime/sync/activation-sidecar-publish.test.ts +78 -0
  88. package/src/runtime/sync/documents-sidecar-publish.test.ts +3 -0
  89. package/src/runtime/sync/resource-sync-events.ts +23 -0
  90. package/src/runtime/sync/worker-daemon-notify.test.ts +36 -0
  91. package/src/runtime/sync/worker-daemon-notify.ts +39 -1
  92. package/src/tools/apps/executors.ts +8 -8
@@ -93,9 +93,34 @@ export const CHANNEL_BOT_PROVIDER = {
93
93
  * connected" question, for any provider key.
94
94
  */
95
95
  export function isChannelBotProvider(providerKey: string): boolean {
96
- return (Object.values(CHANNEL_BOT_PROVIDER) as readonly string[]).includes(
97
- providerKey,
98
- );
96
+ return channelForBotProvider(providerKey) !== undefined;
97
+ }
98
+
99
+ /**
100
+ * The provider key holding a channel's bot credential, or `undefined` when
101
+ * the channel has none, or is not a channel at all. Walks the map's own
102
+ * entries, so an inherited object property name is never a channel.
103
+ */
104
+ export function botProviderForChannel(channelId: string): string | undefined {
105
+ return Object.entries(CHANNEL_BOT_PROVIDER).find(
106
+ ([id]) => id === channelId,
107
+ )?.[1];
108
+ }
109
+
110
+ /**
111
+ * The channel whose bot credential a provider key holds, or `undefined` when
112
+ * the key is not a bot's. The inverse of `botProviderForChannel`, for a
113
+ * surface that has only the provider key in hand and needs to speak in the
114
+ * channel's vocabulary.
115
+ */
116
+ export function channelForBotProvider(
117
+ providerKey: string,
118
+ ): keyof typeof CHANNEL_BOT_PROVIDER | undefined {
119
+ return (
120
+ Object.entries(CHANNEL_BOT_PROVIDER) as Array<
121
+ [keyof typeof CHANNEL_BOT_PROVIDER, string]
122
+ >
123
+ ).find(([, key]) => key === providerKey)?.[0];
99
124
  }
100
125
 
101
126
  /**
@@ -3,6 +3,7 @@ import { describe, expect, test } from "bun:test";
3
3
  import {
4
4
  ACCESS_DENIED_NOT_APPROVED_REPLY,
5
5
  PLUGIN_ADMISSION_DENIED_NOTICE_PATH,
6
+ PLUGIN_NOTICES_ROUTE_PREFIX,
6
7
  PluginAdmissionDeniedNoticeSchema,
7
8
  } from "../plugin-admission-denied-contract.js";
8
9
 
@@ -19,6 +20,15 @@ describe("plugin admission-denied contract", () => {
19
20
  );
20
21
  });
21
22
 
23
+ test("the notice lives under the gateway-only notices prefix", () => {
24
+ // The runtime reserves this prefix for the gateway's service principal,
25
+ // so a notice posted anywhere else would be reachable by actor clients.
26
+ expect(PLUGIN_NOTICES_ROUTE_PREFIX).toBe("notices");
27
+ expect(PLUGIN_ADMISSION_DENIED_NOTICE_PATH).toStartWith(
28
+ `${PLUGIN_NOTICES_ROUTE_PREFIX}/`,
29
+ );
30
+ });
31
+
22
32
  test("round-trips a floor-deny notice", () => {
23
33
  const notice = {
24
34
  reason: "admission_floor" as const,
@@ -104,6 +104,7 @@ export type {
104
104
  export {
105
105
  ACCESS_DENIED_NOT_APPROVED_REPLY,
106
106
  PLUGIN_ADMISSION_DENIED_NOTICE_PATH,
107
+ PLUGIN_NOTICES_ROUTE_PREFIX,
107
108
  PluginAdmissionDeniedNoticeSchema,
108
109
  } from "./plugin-admission-denied-contract.js";
109
110
 
@@ -22,10 +22,23 @@ export const ACCESS_DENIED_NOT_APPROVED_REPLY =
22
22
  "Sorry, you haven't been approved to message this assistant.";
23
23
 
24
24
  /**
25
- * Plugin route the gateway posts the notice to, under
25
+ * Prefix, under `/x/plugins/<plugin>/`, of the routes reserved for notices
26
+ * the gateway posts to a plugin.
27
+ *
28
+ * The runtime serves every path under it to the gateway's service principal
29
+ * only (`assistant/src/runtime/routes/user-routes.ts`). A notice is the
30
+ * gateway's own verified decision, and a plugin acts on it without running a
31
+ * turn, so an authenticated client must not be able to post one: a forged
32
+ * notice would have the plugin spend its vendor credentials on a recipient
33
+ * and a message the client chose.
34
+ */
35
+ export const PLUGIN_NOTICES_ROUTE_PREFIX = "notices";
36
+
37
+ /**
38
+ * Plugin route the gateway posts the admission-denied notice to, under
26
39
  * `/v1/x/plugins/<plugin>/`. Not a public ingress path.
27
40
  */
28
- export const PLUGIN_ADMISSION_DENIED_NOTICE_PATH = "notices/admission-denied";
41
+ export const PLUGIN_ADMISSION_DENIED_NOTICE_PATH = `${PLUGIN_NOTICES_ROUTE_PREFIX}/admission-denied`;
29
42
 
30
43
  export const PluginAdmissionDeniedNoticeSchema = z.object({
31
44
  reason: z.literal("admission_floor"),
@@ -1,8 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
+ botProviderForChannel,
4
5
  CHANNEL_BOT_PROVIDER,
5
6
  CHANNEL_IDS,
7
+ channelForBotProvider,
6
8
  isChannelBotProvider,
7
9
  isChannelUserIntegration,
8
10
  isChannelId,
@@ -41,6 +43,35 @@ describe("isChannelId", () => {
41
43
  });
42
44
  });
43
45
 
46
+ describe("botProviderForChannel and channelForBotProvider", () => {
47
+ test("are inverses over every entry of the map", () => {
48
+ for (const [channelId, botProviderKey] of Object.entries(
49
+ CHANNEL_BOT_PROVIDER,
50
+ )) {
51
+ expect(botProviderForChannel(channelId)).toBe(botProviderKey);
52
+ expect(channelForBotProvider(botProviderKey)).toBe(
53
+ channelId as keyof typeof CHANNEL_BOT_PROVIDER,
54
+ );
55
+ }
56
+ });
57
+
58
+ test("answer nothing for a channel without a bot, a person's grant, or an unknown key", () => {
59
+ expect(botProviderForChannel("phone")).toBeUndefined();
60
+ expect(botProviderForChannel("vellum")).toBeUndefined();
61
+ expect(botProviderForChannel("not-a-channel")).toBeUndefined();
62
+ // `slack` is the person's integration standing beside the bot's key.
63
+ expect(channelForBotProvider("slack")).toBeUndefined();
64
+ expect(channelForBotProvider("google")).toBeUndefined();
65
+ });
66
+
67
+ test("never resolve an inherited object property as a channel or a key", () => {
68
+ for (const name of ["constructor", "toString", "__proto__"]) {
69
+ expect(botProviderForChannel(name)).toBeUndefined();
70
+ expect(channelForBotProvider(name)).toBeUndefined();
71
+ }
72
+ });
73
+ });
74
+
44
75
  describe("isChannelUserIntegration", () => {
45
76
  test("names the grant standing beside a bot of the same brand", () => {
46
77
  expect(isChannelUserIntegration("slack")).toBe(true);
@@ -93,9 +93,34 @@ export const CHANNEL_BOT_PROVIDER = {
93
93
  * connected" question, for any provider key.
94
94
  */
95
95
  export function isChannelBotProvider(providerKey: string): boolean {
96
- return (Object.values(CHANNEL_BOT_PROVIDER) as readonly string[]).includes(
97
- providerKey,
98
- );
96
+ return channelForBotProvider(providerKey) !== undefined;
97
+ }
98
+
99
+ /**
100
+ * The provider key holding a channel's bot credential, or `undefined` when
101
+ * the channel has none, or is not a channel at all. Walks the map's own
102
+ * entries, so an inherited object property name is never a channel.
103
+ */
104
+ export function botProviderForChannel(channelId: string): string | undefined {
105
+ return Object.entries(CHANNEL_BOT_PROVIDER).find(
106
+ ([id]) => id === channelId,
107
+ )?.[1];
108
+ }
109
+
110
+ /**
111
+ * The channel whose bot credential a provider key holds, or `undefined` when
112
+ * the key is not a bot's. The inverse of `botProviderForChannel`, for a
113
+ * surface that has only the provider key in hand and needs to speak in the
114
+ * channel's vocabulary.
115
+ */
116
+ export function channelForBotProvider(
117
+ providerKey: string,
118
+ ): keyof typeof CHANNEL_BOT_PROVIDER | undefined {
119
+ return (
120
+ Object.entries(CHANNEL_BOT_PROVIDER) as Array<
121
+ [keyof typeof CHANNEL_BOT_PROVIDER, string]
122
+ >
123
+ ).find(([, key]) => key === providerKey)?.[0];
99
124
  }
100
125
 
101
126
  /**