@vellumai/assistant 0.12.2-staging.5 → 0.12.2-staging.7

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/Dockerfile +7 -7
  2. package/docs/architecture/memory.md +11 -2
  3. package/docs/desktop-browser-cli.md +4 -2
  4. package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
  5. package/node_modules/@vellumai/environments/src/shell.ts +24 -0
  6. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
  7. package/openapi.yaml +157 -55
  8. package/package.json +5 -4
  9. package/scripts/bundled-plugin-packages.ts +154 -0
  10. package/scripts/generate-bundled-plugin-packages.ts +29 -0
  11. package/scripts/postinstall.ts +33 -0
  12. package/scripts/smoke-desktop-browser-cli.ts +1 -0
  13. package/scripts/test.ts +15 -13
  14. package/src/__tests__/agent-loop.test.ts +124 -0
  15. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  16. package/src/__tests__/channel-approval.test.ts +9 -14
  17. package/src/__tests__/conversation-agent-loop.test.ts +25 -0
  18. package/src/__tests__/db-conversation-tool-surface.test.ts +144 -0
  19. package/src/__tests__/managed-store.test.ts +121 -0
  20. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  21. package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
  22. package/src/__tests__/scaffold-managed-skill-tool.test.ts +88 -0
  23. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  24. package/src/__tests__/subagent-tool-gate-mode.test.ts +169 -0
  25. package/src/__tests__/terminal-tools.test.ts +8 -0
  26. package/src/__tests__/unicode.test.ts +36 -0
  27. package/src/agent/loop.ts +19 -0
  28. package/src/api/events/desktop-activity-changed.ts +10 -0
  29. package/src/api/index.ts +6 -0
  30. package/src/approvals/approval-primitive.ts +5 -2
  31. package/src/approvals/scoped-approval-grants.ts +6 -2
  32. package/src/calls/__tests__/voice-control-protocol.test.ts +24 -2
  33. package/src/calls/__tests__/voice-session-bridge.test.ts +22 -2
  34. package/src/calls/voice-control-protocol.ts +13 -4
  35. package/src/calls/voice-session-bridge.ts +27 -6
  36. package/src/cli/commands/__tests__/plugins.test.ts +66 -0
  37. package/src/cli/commands/plugins.ts +50 -18
  38. package/src/cli/lib/__tests__/install-from-github.test.ts +67 -0
  39. package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +169 -0
  40. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +57 -0
  41. package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +14 -0
  42. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +27 -1
  43. package/src/cli/lib/__tests__/plugin-details.test.ts +9 -2
  44. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +20 -0
  45. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +43 -0
  46. package/src/cli/lib/__tests__/search-plugins.test.ts +31 -0
  47. package/src/cli/lib/bundled-plugin-packages.json +4 -0
  48. package/src/cli/lib/bundled-plugin-packages.ts +87 -0
  49. package/src/cli/lib/diff-plugin.ts +1 -1
  50. package/src/cli/lib/inspect-plugin.ts +80 -12
  51. package/src/cli/lib/install-from-github.ts +133 -76
  52. package/src/cli/lib/plugin-catalog-cache.ts +22 -3
  53. package/src/cli/lib/plugin-catalog-local.ts +13 -3
  54. package/src/cli/lib/plugin-catalog-platform.ts +6 -1
  55. package/src/cli/lib/plugin-catalog-resolve.ts +20 -0
  56. package/src/cli/lib/plugin-details.ts +12 -0
  57. package/src/cli/lib/plugin-marketplace.ts +121 -21
  58. package/src/cli/lib/plugin-pin-history.ts +5 -2
  59. package/src/cli/lib/search-plugins.ts +58 -16
  60. package/src/cli/lib/upgrade-plugin.ts +48 -3
  61. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  62. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -6
  63. package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
  64. package/src/daemon/conversation-agent-loop.ts +2 -0
  65. package/src/daemon/conversation-tool-setup.ts +61 -23
  66. package/src/daemon/conversation.ts +17 -0
  67. package/src/daemon/daemon-control.ts +2 -6
  68. package/src/daemon/orphan-reaper.ts +4 -3
  69. package/src/daemon/tool-setup-types.ts +6 -0
  70. package/src/daemon/wake-conversation-ops.ts +38 -15
  71. package/src/desktop/desktop-automation-lease.test.ts +143 -0
  72. package/src/desktop/desktop-automation-lease.ts +39 -3
  73. package/src/live-voice/__tests__/live-voice-vad.test.ts +624 -3
  74. package/src/live-voice/__tests__/session-controls.test.ts +18 -0
  75. package/src/live-voice/live-voice-session.ts +575 -53
  76. package/src/live-voice/session-controls.ts +7 -3
  77. package/src/messaging/provider-message-metadata.ts +3 -3
  78. package/src/monitoring/plugin-auto-update.ts +6 -0
  79. package/src/notifications/__tests__/copy-composer.test.ts +70 -0
  80. package/src/notifications/copy-composer.ts +11 -3
  81. package/src/persistence/conversation-plugin-facade.ts +13 -0
  82. package/src/persistence/conversation-tool-surface.ts +86 -0
  83. package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
  84. package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
  85. package/src/persistence/schema/conversation-tool-surfaces.ts +22 -0
  86. package/src/persistence/schema/index.ts +1 -0
  87. package/src/persistence/steps.ts +2 -0
  88. package/src/plugin-api/conversation-turn.ts +31 -7
  89. package/src/plugin-api/index.ts +9 -1
  90. package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
  91. package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
  92. package/src/plugins/defaults/memory/AGENTS.md +14 -2
  93. package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +320 -0
  94. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
  95. package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
  96. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +46 -0
  97. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  98. package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
  99. package/src/plugins/defaults/memory/buffer-file.ts +354 -0
  100. package/src/plugins/defaults/memory/buffer-format.ts +40 -0
  101. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
  102. package/src/plugins/defaults/memory/context-search/format.ts +2 -1
  103. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
  104. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
  105. package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
  106. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
  107. package/src/plugins/defaults/memory/host-utils.ts +0 -10
  108. package/src/plugins/defaults/memory/injectors.ts +4 -3
  109. package/src/plugins/defaults/memory/memory-retrospective-job.ts +55 -181
  110. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  111. package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
  112. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
  113. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
  114. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
  115. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
  116. package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
  117. package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
  118. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
  119. package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
  120. package/src/plugins/defaults/memory/tools.ts +1 -1
  121. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
  122. package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
  123. package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
  124. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
  125. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
  126. package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
  127. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
  128. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
  129. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +21 -0
  130. package/src/plugins/defaults/memory/v3/card.ts +2 -1
  131. package/src/plugins/defaults/memory/v3/injector.ts +212 -178
  132. package/src/plugins/defaults/memory/v3/orchestrate.ts +86 -22
  133. package/src/plugins/defaults/memory/v3/pool-select.ts +10 -7
  134. package/src/plugins/defaults/memory/v3/sections.ts +2 -1
  135. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -1
  136. package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
  137. package/src/runtime/AGENTS.md +1 -1
  138. package/src/runtime/__tests__/agent-wake.test.ts +86 -1
  139. package/src/runtime/agent-wake.ts +20 -4
  140. package/src/runtime/guardian-action-service.ts +2 -17
  141. package/src/runtime/guardian-reply-router.ts +1 -8
  142. package/src/runtime/routes/__tests__/plugins-routes.test.ts +102 -0
  143. package/src/runtime/routes/channel-route-shared.ts +1 -9
  144. package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
  145. package/src/runtime/routes/desktop-setup-routes.ts +7 -3
  146. package/src/runtime/routes/guardian-approval-interception.ts +24 -0
  147. package/src/runtime/routes/inbound-message-handler.ts +2 -3
  148. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
  149. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
  150. package/src/runtime/routes/plugins-routes.ts +69 -49
  151. package/src/schedule/run-script.ts +2 -2
  152. package/src/skills/managed-store.ts +98 -32
  153. package/src/tools/host-terminal/host-shell.ts +12 -6
  154. package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
  155. package/src/tools/shared/shell-output.test.ts +10 -0
  156. package/src/tools/shared/shell-output.ts +14 -2
  157. package/src/tools/skills/find-similar-skills.test.ts +3 -0
  158. package/src/tools/skills/resolve-execute-invocation.ts +24 -0
  159. package/src/tools/skills/sandbox-runner.ts +13 -2
  160. package/src/tools/skills/scaffold-managed.ts +18 -14
  161. package/src/tools/terminal/__tests__/safe-env.test.ts +29 -0
  162. package/src/tools/terminal/safe-env.ts +30 -1
  163. package/src/tools/terminal/sanitized-bash.ts +15 -2
  164. package/src/tools/terminal/shell-launch.test.ts +162 -0
  165. package/src/tools/terminal/shell.test.ts +29 -0
  166. package/src/tools/terminal/shell.ts +13 -7
  167. package/src/util/host-process.test.ts +17 -1
  168. package/src/util/host-process.ts +24 -0
  169. package/src/util/unicode.ts +29 -0
package/Dockerfile CHANGED
@@ -130,13 +130,13 @@ RUN bun run scripts/smoke-container-workspace-dependencies.ts
130
130
  # The repo-root .dockerignore decides which skills (and files) ship.
131
131
  COPY skills /app/skills
132
132
 
133
- # Materialize the git-ignored offline plugin manifest from its canonical source.
134
- # `assistant/src/cli/lib/plugin-catalog-local.ts` statically imports this JSON
135
- # (loaded even when platform features are enabled), so it must exist in the image
136
- # the build installs with --ignore-scripts, so the postinstall sync that
137
- # regenerates it in dev never runs here. Mirrors how CI materializes the
138
- # feature-flag registry before building.
139
- COPY plugins/marketplace.json /app/assistant/src/cli/lib/bundled-marketplace.json
133
+ # Materialize the git-ignored offline marketplace and reviewed local packages.
134
+ # The build installs with --ignore-scripts, so the postinstall sync that creates
135
+ # these static imports in development never runs here.
136
+ COPY plugins /app/plugins
137
+ COPY meta/sync-bundled-copies.ts /app/meta/
138
+ RUN cd /app && bun run meta/sync-bundled-copies.ts --plugins-only && \
139
+ bun run assistant/scripts/generate-bundled-plugin-packages.ts
140
140
 
141
141
  # Install assistant CLI launcher backed by the bundled assistant package
142
142
  RUN printf '#!/usr/bin/env sh\nexec bun run /app/assistant/src/index.ts "$@"\n' > /usr/local/bin/assistant && \
@@ -87,8 +87,17 @@ graph LR
87
87
  nesting existed still parse, since an unindented body line that is not itself
88
88
  entry-shaped is read as a continuation.
89
89
  - **Consolidation** (`substrate/consolidation-job.ts`) is a background
90
- agent conversation that files buffer entries into concept pages, rewrites
91
- the aggregate views, and trims the buffer. Scheduling
90
+ agent conversation that files buffer entries into concept pages and
91
+ rewrites the aggregate views. The agent never writes the buffer: the job
92
+ snapshots `buffer.md`, hands the run its pass's entries verbatim in the
93
+ prompt, and after the run removes exactly those entries itself through
94
+ `buffer-file.ts` (the module that also owns the append path, so the two
95
+ writers of the buffer share one protocol). Removal happens only when the
96
+ run's persisted messages hold a page-writing tool call with a non-error
97
+ result, the same evidence bar the retrospective's cursor advance uses; a
98
+ run that wrote nothing, failed, or timed out leaves the buffer intact for
99
+ the next pass. Entries deferred past the per-run cap and entries appended
100
+ while the run was in flight are therefore never lost. Scheduling
92
101
  (`maybeEnqueueGraphMaintenanceJobs` in `jobs-worker.ts`):
93
102
  - interval-based (`memory.v2.consolidation_interval_hours`, default 8h),
94
103
  skipped below `MIN_BUFFER_LINES_FOR_CONSOLIDATION` (10) **unless** the
@@ -18,7 +18,7 @@ flowchart LR
18
18
 
19
19
  Chrome and its dock launcher share the managed profile and loopback debug port. For Chrome reopened from the dock, the profile singleton lock identifies a candidate PID, validated against the installed executable, profile and loopback debug arguments. Discovery checks `/proc` socket ownership against that browser PID, refuses redirects and validates the returned browser WebSocket endpoint. The connection stays within the container. No CDP endpoint or token is exposed to the renderer.
20
20
 
21
- The desktop client bypasses personal-browser discovery, extension reconnect waits and backend fallback. It reuses the existing AX snapshot, DOM element resolution, mouse, keyboard, extraction and credential-fill implementations. Operation-scoped clients borrow the lease's connection; disposing one does not release the lease. `detach`, `close`, cancellation, errors and idle expiry release control. Only `tabs close` closes a Chrome tab.
21
+ The desktop client bypasses personal-browser discovery, extension reconnect waits and backend fallback. It reuses the existing AX snapshot, DOM element resolution, mouse, keyboard, extraction and credential-fill implementations. Operation-scoped clients borrow the lease's connection; disposing one does not release the lease. `detach`, `close`, turn completion, cancellation, errors and idle expiry release control. Turn completion clears the activity indicator immediately and queues browser cleanup before the next automation session; it leaves Chrome and the current page open for the user. Only `tabs close` closes a Chrome tab.
22
22
 
23
23
  `assistant browser --virtual-desktop screenshot --output /tmp/desktop-page.jpg` captures a color page image through CDP `Page.captureScreenshot`. It excludes the browser toolbar and desktop dock. `snapshot` returns semantic page structure. Agents should use these interfaces and let the manager start the desktop, including when older memory notes describe manual Xvnc, `xdotool` or scratch XWD conversion scripts.
24
24
 
@@ -26,8 +26,10 @@ Tab IDs are ephemeral numeric aliases for this managed browser's CDP target IDs.
26
26
 
27
27
  CDP mouse events use page viewport CSS coordinates. Before dispatching them, the client animates a purple arrow overlay to the same point. The overlay is excluded from accessibility and hit testing. It appears in the existing desktop stream and is removed on release. It does not move the OS pointer, appear in browser toolbar UI or visualize every programmatic DOM operation. Native dialogs and other applications are outside the browser CLI; users can interact with them directly in the expanded desktop. The picture-in-picture preview is view-only. Direct user interaction does not pause automation.
28
28
 
29
- The client records key and mouse presses before dispatch. On release it opens a fresh bounded cleanup connection, attaches to the same live targets, releases uncertain held input and removes overlays. A failed cleanup preserves state and the automation slot for retry. Dispatched actions are never automatically retried. Closed targets need no input cleanup. Browser-process loss disposes the client, and later requests discover the replacement process.
29
+ The client records key and mouse presses before dispatch. On release it opens a fresh bounded cleanup connection, attaches to the same live targets, releases uncertain held input and removes overlays. A failed cleanup preserves state and the automation slot for retry. Turn-triggered cancellation retries cleanup automatically until it succeeds or ownership changes. Dispatched actions are never automatically retried. Closed targets need no input cleanup. Browser-process loss disposes the client, and later requests discover the replacement process.
30
30
 
31
31
  `--use-active-tab` and personal browser targeting are rejected with `--virtual-desktop`. Download waiting is unsupported. Browser operations are bounded to two minutes and share the desktop lease's action budget and idle expiry.
32
32
 
33
33
  Validation: focused client tests exercise shared snapshot/click behavior, namespace isolation, stale references, target changes, cancellation and uncertain-input cleanup. Lease tests cover browser ownership, cancellation and cleanup independently of native input. The Linux smoke script exercises real Chrome, the CLI and visible pointer feedback.
34
+
35
+ The desktop header icon pulses in the assistant's avatar accent color while a browser automation lease is active, including between browser commands. It shares the progress indicator's accent and neutral fallback. Reduced-motion clients show a solid accent. Setup status exposes the optional `automationActive` field; `desktop_activity_changed` events refresh it on acquisition and cancellation or release. Installation progress retains its `assistant:self:desktop` sync invalidations. The indicator reads status without starting installation, and reconnects refetch the current lease state.
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildShellInvocation,
5
+ buildShellSpawnFlags,
5
6
  pathListDelimiter,
6
7
  prependUniquePathEntries,
7
8
  } from "./shell.js";
@@ -38,6 +39,26 @@ describe("buildShellInvocation", () => {
38
39
  });
39
40
  });
40
41
 
42
+ describe("buildShellSpawnFlags", () => {
43
+ test("creates a POSIX process group and hides Windows consoles", () => {
44
+ expect(buildShellSpawnFlags("linux")).toEqual({
45
+ detached: true,
46
+ windowsHide: true,
47
+ });
48
+ expect(buildShellSpawnFlags("darwin")).toEqual({
49
+ detached: true,
50
+ windowsHide: true,
51
+ });
52
+ });
53
+
54
+ test("does not detach Windows children that use piped stdio", () => {
55
+ expect(buildShellSpawnFlags("win32")).toEqual({
56
+ detached: false,
57
+ windowsHide: true,
58
+ });
59
+ });
60
+ });
61
+
41
62
  describe("path list handling", () => {
42
63
  test("uses the platform delimiter", () => {
43
64
  expect(pathListDelimiter("win32")).toBe(";");
@@ -5,6 +5,11 @@ export interface ShellInvocation {
5
5
  args: string[];
6
6
  }
7
7
 
8
+ export interface ShellSpawnFlags {
9
+ detached: boolean;
10
+ windowsHide: true;
11
+ }
12
+
8
13
  const WINDOWS_UTF8_PREAMBLE =
9
14
  "try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}; " +
10
15
  "$OutputEncoding = [System.Text.Encoding]::UTF8; " +
@@ -43,6 +48,25 @@ export function buildShellInvocation(
43
48
  return { command: "bash", args: ["-c", "--", command] };
44
49
  }
45
50
 
51
+ /**
52
+ * Spawn flags for assistant-owned shell children (sandbox bash, local
53
+ * host_bash fallback, sanitized CLI bash, skill runners, scheduled scripts).
54
+ *
55
+ * POSIX uses a new process group so timeout/abort can SIGKILL the tree via
56
+ * `-pid`. Windows process trees are torn down with `taskkill /T`, which does
57
+ * not need a detached process. Combining `DETACHED_PROCESS`,
58
+ * `CREATE_NO_WINDOW`, and piped stdio on Windows can emit `close` with exit
59
+ * 0 and empty pipes without running the encoded command.
60
+ */
61
+ export function buildShellSpawnFlags(
62
+ hostPlatform: NodeJS.Platform = process.platform,
63
+ ): ShellSpawnFlags {
64
+ return {
65
+ detached: hostPlatform !== "win32",
66
+ windowsHide: true,
67
+ };
68
+ }
69
+
46
70
  export function pathListDelimiter(
47
71
  hostPlatform: NodeJS.Platform = process.platform,
48
72
  ): string {
@@ -83,8 +83,14 @@ export const SourceMetadataSchema = z
83
83
  clientTimezone: z.string().optional(),
84
84
  /** Channel command intent (e.g. Telegram /start). */
85
85
  commandIntent: CommandIntentSchema.optional(),
86
- /** Slack-specific: whether the bot was @-mentioned. */
87
- slackBotMentioned: z.boolean().optional(),
86
+ /**
87
+ * Whether the message addresses the assistant by name, an @-mention on
88
+ * the platforms that have one. Stated by the channel's normalizer only
89
+ * where it proved the answer; absent means "not established". Read to
90
+ * decide whether a reply is expected before any text exists, never to
91
+ * admit or deny: admission is the gate's job, upstream of this.
92
+ */
93
+ botMentioned: z.boolean().optional(),
88
94
  /**
89
95
  * Slack-specific: the `edited.ts` float-string from a `message_changed`
90
96
  * event. Used by the daemon to reject out-of-order edit deliveries whose
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.12.2
6
+ version: 0.12.1
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -5042,7 +5042,7 @@ paths:
5042
5042
  type: string
5043
5043
  required:
5044
5044
  - type
5045
- slackBotMentioned:
5045
+ botMentioned:
5046
5046
  type: boolean
5047
5047
  slackEditedTs:
5048
5048
  type: string
@@ -11941,6 +11941,8 @@ paths:
11941
11941
  - ready
11942
11942
  - failed
11943
11943
  - unsupported
11944
+ automationActive:
11945
+ type: boolean
11944
11946
  stage:
11945
11947
  type: string
11946
11948
  enum:
@@ -11971,6 +11973,8 @@ paths:
11971
11973
  - ready
11972
11974
  - failed
11973
11975
  - unsupported
11976
+ automationActive:
11977
+ type: boolean
11974
11978
  stage:
11975
11979
  type: string
11976
11980
  enum:
@@ -26622,26 +26626,44 @@ paths:
26622
26626
  description: Resolved version (installed copy first, then repo `package.json`).
26623
26627
  source:
26624
26628
  anyOf:
26625
- - type: object
26626
- properties:
26627
- kind:
26628
- type: string
26629
- const: github
26630
- repo:
26631
- type: string
26632
- description: "`owner/repo` of the external plugin repository."
26633
- path:
26634
- description: Directory within the repo, when the plugin is not at the root.
26635
- type: string
26636
- ref:
26637
- type: string
26638
- description: Pinned git ref the plugin is fetched from.
26639
- required:
26640
- - kind
26641
- - repo
26642
- - ref
26643
- additionalProperties: false
26644
- description: "Origin of the match: a whitelisted external plugin repository."
26629
+ - oneOf:
26630
+ - type: object
26631
+ properties:
26632
+ kind:
26633
+ type: string
26634
+ const: github
26635
+ repo:
26636
+ type: string
26637
+ description: "`owner/repo` of the external plugin repository."
26638
+ path:
26639
+ description: Directory within the repo, when the plugin is not at the root.
26640
+ type: string
26641
+ ref:
26642
+ type: string
26643
+ description: Pinned git ref the plugin is fetched from.
26644
+ required:
26645
+ - kind
26646
+ - repo
26647
+ - ref
26648
+ additionalProperties: false
26649
+ - type: object
26650
+ properties:
26651
+ kind:
26652
+ type: string
26653
+ const: local
26654
+ path:
26655
+ type: string
26656
+ description: Package key in the assistant bundle.
26657
+ version:
26658
+ type: string
26659
+ description: Bundled package version.
26660
+ required:
26661
+ - kind
26662
+ - path
26663
+ - version
26664
+ additionalProperties: false
26665
+ description: Reviewed source of the marketplace entry.
26666
+ type: object
26645
26667
  - type: "null"
26646
26668
  description: Pinned origin from the marketplace entry, or null when an installed copy has no catalog entry.
26647
26669
  readme:
@@ -26996,28 +27018,42 @@ paths:
26996
27018
  description: ISO-8601 install timestamp from the sidecar; null when absent.
26997
27019
  source:
26998
27020
  anyOf:
26999
- - type: object
27000
- properties:
27001
- kind:
27002
- type: string
27003
- description: Source kind. Only `github` is written today.
27004
- owner:
27005
- type: string
27006
- repo:
27007
- type: string
27008
- path:
27009
- description: Repo-relative directory holding the plugin root; absent = repo root.
27010
- type: string
27011
- ref:
27012
- type: string
27013
- description: Ref the install resolved through (the pinned commit SHA for marketplace installs).
27014
- required:
27015
- - kind
27016
- - owner
27017
- - repo
27018
- - ref
27019
- additionalProperties: false
27021
+ - oneOf:
27022
+ - type: object
27023
+ properties:
27024
+ kind:
27025
+ type: string
27026
+ const: github
27027
+ owner:
27028
+ type: string
27029
+ repo:
27030
+ type: string
27031
+ path:
27032
+ type: string
27033
+ ref:
27034
+ type: string
27035
+ required:
27036
+ - kind
27037
+ - owner
27038
+ - repo
27039
+ - ref
27040
+ additionalProperties: false
27041
+ - type: object
27042
+ properties:
27043
+ kind:
27044
+ type: string
27045
+ const: local
27046
+ path:
27047
+ type: string
27048
+ version:
27049
+ type: string
27050
+ required:
27051
+ - kind
27052
+ - path
27053
+ - version
27054
+ additionalProperties: false
27020
27055
  description: Source coordinates recorded in the install-time provenance sidecar.
27056
+ type: object
27021
27057
  - type: "null"
27022
27058
  description: Source recorded at install time; null when no sidecar exists.
27023
27059
  localChanges:
@@ -27076,6 +27112,9 @@ paths:
27076
27112
  anyOf:
27077
27113
  - type: object
27078
27114
  properties:
27115
+ kind:
27116
+ type: string
27117
+ const: local
27079
27118
  repo:
27080
27119
  type: string
27081
27120
  description: "`owner/repo` of the external plugin repository."
@@ -27111,6 +27150,9 @@ paths:
27111
27150
  marketplaceRef:
27112
27151
  type: string
27113
27152
  description: Ref of the canonical repo the marketplace manifest was read from.
27153
+ version:
27154
+ description: Bundled package version.
27155
+ type: string
27114
27156
  required:
27115
27157
  - repo
27116
27158
  - path
@@ -27547,7 +27589,7 @@ paths:
27547
27589
  description: Install name. Matches `assistant plugins install <name>`.
27548
27590
  path:
27549
27591
  type: string
27550
- description: "Human-readable origin: a `github:owner/repo@ref` locator for the external plugin."
27592
+ description: Human-readable catalog origin locator.
27551
27593
  description:
27552
27594
  description: Short description, when known (external entries only today).
27553
27595
  type: string
@@ -27561,27 +27603,87 @@ paths:
27561
27603
  - type: string
27562
27604
  - type: "null"
27563
27605
  description: Marketplace category slug (Skills taxonomy); null when the entry declares none.
27564
- source:
27606
+ integration:
27565
27607
  type: object
27566
27608
  properties:
27567
27609
  kind:
27568
27610
  type: string
27569
- const: github
27570
- repo:
27611
+ const: mcp
27612
+ displayName:
27613
+ type: string
27614
+ documentationUrl:
27571
27615
  type: string
27572
- description: "`owner/repo` of the external plugin repository."
27573
- path:
27574
- description: Directory within the repo, when the plugin is not at the root.
27616
+ verifiedAt:
27575
27617
  type: string
27576
- ref:
27618
+ verification:
27619
+ type: string
27620
+ const: documentation-only
27621
+ setup:
27622
+ type: object
27623
+ properties:
27624
+ mode:
27625
+ type: string
27626
+ enum:
27627
+ - oauth
27628
+ - manual
27629
+ instructions:
27630
+ type: string
27631
+ required:
27632
+ - mode
27633
+ - instructions
27634
+ additionalProperties: false
27635
+ logo:
27636
+ type: string
27637
+ oauthProvider:
27577
27638
  type: string
27578
- description: Pinned git ref the plugin is fetched from.
27579
27639
  required:
27580
27640
  - kind
27581
- - repo
27582
- - ref
27641
+ - displayName
27642
+ - documentationUrl
27643
+ - verifiedAt
27644
+ - verification
27645
+ - setup
27646
+ - logo
27583
27647
  additionalProperties: false
27584
- description: "Origin of the match: a whitelisted external plugin repository."
27648
+ source:
27649
+ oneOf:
27650
+ - type: object
27651
+ properties:
27652
+ kind:
27653
+ type: string
27654
+ const: github
27655
+ repo:
27656
+ type: string
27657
+ description: "`owner/repo` of the external plugin repository."
27658
+ path:
27659
+ description: Directory within the repo, when the plugin is not at the root.
27660
+ type: string
27661
+ ref:
27662
+ type: string
27663
+ description: Pinned git ref the plugin is fetched from.
27664
+ required:
27665
+ - kind
27666
+ - repo
27667
+ - ref
27668
+ additionalProperties: false
27669
+ - type: object
27670
+ properties:
27671
+ kind:
27672
+ type: string
27673
+ const: local
27674
+ path:
27675
+ type: string
27676
+ description: Package key in the assistant bundle.
27677
+ version:
27678
+ type: string
27679
+ description: Bundled package version.
27680
+ required:
27681
+ - kind
27682
+ - path
27683
+ - version
27684
+ additionalProperties: false
27685
+ description: Reviewed source of the marketplace entry.
27686
+ type: object
27585
27687
  required:
27586
27688
  - name
27587
27689
  - path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.12.2-staging.5",
3
+ "version": "0.12.2-staging.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -24,16 +24,17 @@
24
24
  "lint:unused": "knip --include files,dependencies,unlisted",
25
25
  "lint:circular": "bun run scripts/check-circular-deps.ts",
26
26
  "lint:unused:production": "knip --production --include exports",
27
- "pretypecheck": "cd .. && bun run meta/sync-bundled-copies.ts",
27
+ "sync:bundled-catalog": "cd .. && bun run meta/sync-bundled-copies.ts && bun run assistant/scripts/generate-bundled-plugin-packages.ts",
28
+ "pretypecheck": "bun run sync:bundled-catalog",
28
29
  "typecheck": "bunx tsc --noEmit",
29
- "pretypecheck:fast": "cd .. && bun run meta/sync-bundled-copies.ts",
30
+ "pretypecheck:fast": "bun run sync:bundled-catalog",
30
31
  "typecheck:fast": "bunx tsgo --noEmit",
31
32
  "test": "bun run scripts/test.ts",
32
33
  "test:coverage": "COVERAGE=true bun run scripts/test.ts",
33
34
  "test:stable": "EXCLUDE_EXPERIMENTAL=true bun run scripts/test.ts",
34
35
  "test:bench": "find src -type f -name '*.benchmark.test.ts' -print0 | xargs -0 -P 1 -I {} bun test {}",
35
36
  "test:filesystem-tools": "bash scripts/test-filesystem-tools.sh",
36
- "postinstall": "cd .. && (git config core.hooksPath || git config core.hooksPath .githooks 2>/dev/null || true) && ([ -f meta/sync-bundled-copies.ts ] && bun run meta/sync-bundled-copies.ts 2>/dev/null || true)"
37
+ "postinstall": "bun run scripts/postinstall.ts"
37
38
  },
38
39
  "dependencies": {
39
40
  "@agentclientprotocol/sdk": "0.25.0",
@@ -0,0 +1,154 @@
1
+ import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
2
+ import { join, relative, resolve, sep } from "node:path";
3
+
4
+ import {
5
+ AGENT_PLUGINS_MCP_SCHEMA_URL,
6
+ SpecMcpDocumentSchema,
7
+ SpecMcpServerSchema,
8
+ } from "../src/mcp/spec-schema.js";
9
+ import {
10
+ readPluginManifest,
11
+ STANDARD_PLUGIN_MANIFEST,
12
+ } from "../src/util/plugin-manifest.js";
13
+
14
+ export interface BundledPackageFile {
15
+ path: string;
16
+ contentBase64: string;
17
+ }
18
+
19
+ export interface BundledPackage {
20
+ version: string;
21
+ files: BundledPackageFile[];
22
+ }
23
+
24
+ /** Validate and embed every local source in the canonical marketplace. */
25
+ export function buildBundledPluginPackages(
26
+ repoRoot: string,
27
+ ): Record<string, BundledPackage> {
28
+ const marketplacePath = join(repoRoot, "plugins", "marketplace.json");
29
+ const manifest = JSON.parse(readFileSync(marketplacePath, "utf8")) as {
30
+ plugins?: unknown;
31
+ };
32
+ if (!Array.isArray(manifest.plugins)) {
33
+ throw new Error("plugins/marketplace.json is missing its plugins array");
34
+ }
35
+
36
+ const pluginsRoot = realpathSync(join(repoRoot, "plugins"));
37
+ const packages: Record<string, BundledPackage> = {};
38
+ for (const rawEntry of manifest.plugins) {
39
+ if (typeof rawEntry !== "object" || rawEntry === null) {
40
+ continue;
41
+ }
42
+ const entry = rawEntry as Record<string, unknown>;
43
+ const rawSource = entry.source;
44
+ if (typeof rawSource !== "object" || rawSource === null) {
45
+ continue;
46
+ }
47
+ const source = rawSource as Record<string, unknown>;
48
+ if (source.source !== "local") {
49
+ continue;
50
+ }
51
+ if (
52
+ typeof entry.name !== "string" ||
53
+ typeof source.path !== "string" ||
54
+ typeof source.version !== "string"
55
+ ) {
56
+ throw new Error(
57
+ "local marketplace entries require name, path, and version",
58
+ );
59
+ }
60
+
61
+ const pathSegments = source.path.split("/");
62
+ if (
63
+ !source.path.startsWith("plugins/") ||
64
+ source.path.includes("\\") ||
65
+ pathSegments.some(
66
+ (segment) => segment === "" || segment === "." || segment === "..",
67
+ )
68
+ ) {
69
+ throw new Error(
70
+ `local marketplace package path is not canonical: ${source.path}`,
71
+ );
72
+ }
73
+ let packageRoot = resolve(repoRoot);
74
+ for (const segment of pathSegments) {
75
+ packageRoot = join(packageRoot, segment);
76
+ if (lstatSync(packageRoot).isSymbolicLink()) {
77
+ throw new Error(
78
+ `local marketplace package path may not contain symlinks: ${source.path}`,
79
+ );
80
+ }
81
+ }
82
+ const packageRealpath = realpathSync(packageRoot);
83
+ const relativeToPlugins = relative(pluginsRoot, packageRealpath);
84
+ if (
85
+ relativeToPlugins === "" ||
86
+ relativeToPlugins === ".." ||
87
+ relativeToPlugins.startsWith(`..${sep}`)
88
+ ) {
89
+ throw new Error(
90
+ `local marketplace package escapes plugins/: ${source.path}`,
91
+ );
92
+ }
93
+
94
+ const pluginManifest = readPluginManifest(packageRealpath);
95
+ if (
96
+ pluginManifest.source !== STANDARD_PLUGIN_MANIFEST ||
97
+ pluginManifest.name !== entry.name ||
98
+ pluginManifest.version !== source.version
99
+ ) {
100
+ throw new Error(
101
+ `local marketplace package ${source.path} must match entry name and version`,
102
+ );
103
+ }
104
+ const rawMcp = JSON.parse(
105
+ readFileSync(join(packageRealpath, "mcp.json"), "utf8"),
106
+ ) as unknown;
107
+ const mcp = SpecMcpDocumentSchema.parse(rawMcp);
108
+ if (mcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA_URL) {
109
+ throw new Error(
110
+ `local marketplace package ${source.path} must use the Agent Plugins MCP schema`,
111
+ );
112
+ }
113
+ if (Object.keys(mcp.mcpServers).length === 0) {
114
+ throw new Error(
115
+ `local marketplace package ${source.path} must declare an MCP server`,
116
+ );
117
+ }
118
+ for (const server of Object.values(mcp.mcpServers)) {
119
+ SpecMcpServerSchema.parse(server);
120
+ }
121
+
122
+ const files: BundledPackageFile[] = [];
123
+ const visit = (directory: string): void => {
124
+ for (const child of readdirSync(directory, { withFileTypes: true })) {
125
+ const childPath = join(directory, child.name);
126
+ if (child.isSymbolicLink() || lstatSync(childPath).isSymbolicLink()) {
127
+ throw new Error(
128
+ `local marketplace packages may not contain symlinks: ${source.path}/${child.name}`,
129
+ );
130
+ }
131
+ if (child.isDirectory()) {
132
+ visit(childPath);
133
+ continue;
134
+ }
135
+ if (!child.isFile()) {
136
+ throw new Error(
137
+ `local marketplace package contains an unsupported entry: ${childPath}`,
138
+ );
139
+ }
140
+ files.push({
141
+ path: relative(packageRealpath, childPath).split(sep).join("/"),
142
+ contentBase64: readFileSync(childPath).toString("base64"),
143
+ });
144
+ }
145
+ };
146
+ visit(packageRealpath);
147
+ files.sort((a, b) => a.path.localeCompare(b.path));
148
+ if (packages[source.path]) {
149
+ throw new Error(`duplicate local marketplace package: ${source.path}`);
150
+ }
151
+ packages[source.path] = { version: source.version, files };
152
+ }
153
+ return packages;
154
+ }