@vellumai/assistant 0.11.10 → 0.11.11-staging.1

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 (199) hide show
  1. package/AGENTS.md +1 -1
  2. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  3. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  6. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  12. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  13. package/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  14. package/openapi.yaml +152 -55
  15. package/package.json +1 -1
  16. package/src/__tests__/anthropic-provider.test.ts +60 -0
  17. package/src/__tests__/config-get-vision-flag.test.ts +36 -0
  18. package/src/__tests__/conversation-error.test.ts +38 -0
  19. package/src/__tests__/copy-composer-tc-templates.test.ts +2 -2
  20. package/src/__tests__/guardian-action-service-scope.test.ts +53 -0
  21. package/src/__tests__/headless-browser-mode.test.ts +63 -13
  22. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  23. package/src/__tests__/heartbeat-service.test.ts +118 -27
  24. package/src/__tests__/host-app-control-proxy.test.ts +2 -2
  25. package/src/__tests__/host-app-control-routes.test.ts +41 -0
  26. package/src/__tests__/host-bash-routes.test.ts +30 -1
  27. package/src/__tests__/host-browser-routes.test.ts +34 -0
  28. package/src/__tests__/host-cu-routes-targeted.test.ts +29 -0
  29. package/src/__tests__/host-file-routes-targeted.test.ts +29 -0
  30. package/src/__tests__/host-proxy-base.test.ts +1 -0
  31. package/src/__tests__/host-transfer-routes-targeted.test.ts +86 -0
  32. package/src/__tests__/llm-schema.test.ts +32 -0
  33. package/src/__tests__/mcp-config-secret-boundary.test.ts +2 -0
  34. package/src/__tests__/memory-config-file-parity.test.ts +18 -0
  35. package/src/__tests__/memory-retrieval-hook.test.ts +15 -0
  36. package/src/__tests__/messaging-send-tool.test.ts +159 -13
  37. package/src/__tests__/messaging-shared.test.ts +19 -0
  38. package/src/__tests__/notification-events-source-context-probe.test.ts +211 -0
  39. package/src/__tests__/openai-responses-prompt-cache.test.ts +40 -6
  40. package/src/__tests__/outlook-messaging-provider.test.ts +9 -0
  41. package/src/__tests__/provider-catalog-visibility.test.ts +2 -2
  42. package/src/__tests__/schedule-routes.test.ts +30 -1
  43. package/src/__tests__/scheduler-result-notification.test.ts +209 -0
  44. package/src/__tests__/skills-install-extract.test.ts +26 -0
  45. package/src/__tests__/skills-install-staging.test.ts +11 -2
  46. package/src/api/constants/personality-sliders.test.ts +21 -0
  47. package/src/api/constants/personality-sliders.ts +64 -0
  48. package/src/api/index.ts +8 -0
  49. package/src/archive/ustar-size.test.ts +32 -0
  50. package/src/archive/ustar-size.ts +23 -0
  51. package/src/background-wake/next-wake.test.ts +13 -0
  52. package/src/background-wake/next-wake.ts +4 -2
  53. package/src/calls/voice-session-bridge.ts +26 -0
  54. package/src/cli/__tests__/catalog-search-help.test.ts +1 -0
  55. package/src/cli/commands/__tests__/plugins-search.test.ts +81 -0
  56. package/src/cli/commands/config.help.ts +3 -1
  57. package/src/cli/commands/memory/worker.ts +1 -1
  58. package/src/cli/commands/platform/__tests__/credits-format.test.ts +94 -5
  59. package/src/cli/commands/platform/__tests__/credits.test.ts +10 -0
  60. package/src/cli/commands/platform/credits-format.ts +57 -0
  61. package/src/cli/commands/platform/index.help.ts +19 -3
  62. package/src/cli/commands/plugins.help.ts +4 -1
  63. package/src/cli/commands/plugins.ts +2 -1
  64. package/src/cli/lib/bundled-marketplace.json +1 -1
  65. package/src/cli/lib/install-from-platform.ts +8 -10
  66. package/src/config/bundled-skills/messaging/SKILL.md +2 -0
  67. package/src/config/bundled-skills/messaging/TOOLS.json +4 -4
  68. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +85 -1
  69. package/src/config/bundled-skills/messaging/tools/shared.ts +6 -0
  70. package/src/config/bundled-skills/schedule/SKILL.md +9 -1
  71. package/src/config/bundled-skills/screen-annotation/SKILL.md +87 -20
  72. package/src/config/bundled-skills/screen-annotation/TOOLS.json +21 -5
  73. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +67 -0
  74. package/src/config/feature-flag-registry.json +17 -1
  75. package/src/config/input-modalities.test.ts +71 -0
  76. package/src/config/input-modalities.ts +95 -0
  77. package/src/config/schemas/heartbeat.ts +3 -3
  78. package/src/config/schemas/llm.ts +13 -3
  79. package/src/config/schemas/memory-storage.test.ts +39 -0
  80. package/src/config/schemas/memory-storage.ts +21 -0
  81. package/src/context/token-estimator.ts +2 -2
  82. package/src/daemon/__tests__/embedding-reconcile.test.ts +77 -0
  83. package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +3 -0
  84. package/src/daemon/conversation-error.ts +20 -7
  85. package/src/daemon/embedding-reconcile.ts +64 -1
  86. package/src/daemon/handlers/config-embeddings.ts +31 -0
  87. package/src/daemon/host-app-control-proxy.ts +2 -0
  88. package/src/daemon/host-bash-proxy.ts +1 -0
  89. package/src/daemon/host-cu-proxy.ts +6 -6
  90. package/src/daemon/host-file-proxy.ts +6 -6
  91. package/src/daemon/host-proxy-base.ts +10 -9
  92. package/src/daemon/host-transfer-proxy.ts +21 -24
  93. package/src/daemon/mcp-reload-service.ts +6 -0
  94. package/src/daemon/providers-setup.ts +5 -26
  95. package/src/heartbeat/heartbeat-service.ts +72 -13
  96. package/src/live-voice/__tests__/live-voice-photo.test.ts +72 -0
  97. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +81 -0
  98. package/src/live-voice/live-voice-photo.ts +68 -4
  99. package/src/live-voice/live-voice-session.ts +36 -2
  100. package/src/live-voice/protocol.ts +98 -1
  101. package/src/mcp/__tests__/reload-signal-emission.test.ts +54 -0
  102. package/src/mcp/__tests__/reload-signal.test.ts +57 -0
  103. package/src/mcp/__tests__/startup.test.ts +150 -0
  104. package/src/mcp/reload-signal.ts +50 -0
  105. package/src/mcp/startup.ts +101 -0
  106. package/src/messaging/providers/outlook/adapter.ts +2 -13
  107. package/src/messaging/providers/outlook/client.ts +13 -1
  108. package/src/messaging/providers/outlook/types.ts +3 -0
  109. package/src/notifications/__tests__/decision-engine.test.ts +79 -6
  110. package/src/notifications/__tests__/home-feed-side-effect.test.ts +27 -0
  111. package/src/notifications/__tests__/schedule-result-producer.test.ts +397 -0
  112. package/src/notifications/copy-composer.ts +33 -11
  113. package/src/notifications/decision-engine.ts +48 -11
  114. package/src/notifications/events-store.ts +68 -2
  115. package/src/notifications/home-feed-side-effect.ts +15 -3
  116. package/src/notifications/schedule-result-producer.ts +275 -0
  117. package/src/notifications/signal.ts +5 -0
  118. package/src/oauth/seed-providers.ts +3 -2
  119. package/src/persistence/conversation-crud.ts +38 -0
  120. package/src/persistence/embeddings/embedding-backend.test.ts +162 -0
  121. package/src/persistence/embeddings/embedding-backend.ts +148 -11
  122. package/src/persistence/embeddings/embedding-openai.test.ts +113 -0
  123. package/src/persistence/embeddings/embedding-openai.ts +55 -4
  124. package/src/persistence/embeddings/embedding-types.test.ts +24 -0
  125. package/src/persistence/embeddings/embedding-types.ts +25 -1
  126. package/src/persistence/job-utils.ts +6 -2
  127. package/src/plugin-api/index.ts +2 -1
  128. package/src/plugin-api/vision-support.test.ts +81 -0
  129. package/src/plugin-api/vision-support.ts +57 -10
  130. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +8 -0
  131. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +1 -0
  132. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +11 -3
  133. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +20 -0
  134. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +3 -1
  135. package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +1 -0
  136. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +1 -0
  137. package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +82 -0
  138. package/src/plugins/defaults/memory/v3/section-dense-store.ts +9 -27
  139. package/src/providers/__tests__/provider-secret-catalog.test.ts +4 -0
  140. package/src/providers/__tests__/xml-tool-call-salvage.test.ts +172 -0
  141. package/src/providers/anthropic/client.ts +9 -4
  142. package/src/providers/inline-audio-support.test.ts +34 -0
  143. package/src/providers/inline-audio-support.ts +27 -0
  144. package/src/providers/model-catalog.ts +22 -2
  145. package/src/providers/openai/__tests__/api-error-normalization.test.ts +50 -0
  146. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +51 -0
  147. package/src/providers/openai/__tests__/chat-completions-provider-xml-salvage.test.ts +291 -0
  148. package/src/providers/openai/api-error-normalization.ts +18 -3
  149. package/src/providers/openai/chat-completions-provider.ts +113 -13
  150. package/src/providers/openai/responses-provider.ts +10 -8
  151. package/src/providers/provider-secret-catalog.ts +11 -1
  152. package/src/providers/vellum/client.ts +14 -1
  153. package/src/providers/vellum/personality-directions.test.ts +163 -0
  154. package/src/providers/vellum/personality-directions.ts +120 -0
  155. package/src/providers/xml-tool-call-salvage.ts +212 -0
  156. package/src/runtime/__tests__/background-job-runner.test.ts +42 -4
  157. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -6
  158. package/src/runtime/auth/__tests__/route-policy.test.ts +65 -0
  159. package/src/runtime/auth/same-actor.ts +29 -5
  160. package/src/runtime/background-job-runner.ts +38 -29
  161. package/src/runtime/guardian-action-service.ts +20 -1
  162. package/src/runtime/migrations/vbundle-validator.ts +3 -3
  163. package/src/runtime/pending-interactions.ts +4 -5
  164. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +24 -0
  165. package/src/runtime/routes/__tests__/platform-credits-routes.test.ts +99 -5
  166. package/src/runtime/routes/conversation-query-routes.ts +53 -11
  167. package/src/runtime/routes/document-comments-routes.ts +8 -2
  168. package/src/runtime/routes/guardian-action-routes.ts +7 -0
  169. package/src/runtime/routes/heartbeat-routes.ts +41 -53
  170. package/src/runtime/routes/host-app-control-routes.ts +10 -36
  171. package/src/runtime/routes/host-bash-routes.ts +11 -45
  172. package/src/runtime/routes/host-browser-routes.ts +24 -0
  173. package/src/runtime/routes/host-cu-routes.ts +11 -45
  174. package/src/runtime/routes/host-file-routes.ts +11 -44
  175. package/src/runtime/routes/host-proxy-result-binding.ts +57 -0
  176. package/src/runtime/routes/host-transfer-routes.ts +33 -25
  177. package/src/runtime/routes/index.ts +0 -2
  178. package/src/runtime/routes/integrations/a2a.ts +12 -3
  179. package/src/runtime/routes/integrations/vercel.ts +13 -3
  180. package/src/runtime/routes/platform-routes.ts +121 -24
  181. package/src/runtime/routes/schedule-routes.ts +15 -1
  182. package/src/schedule/__tests__/schedule-timezone.test.ts +21 -3
  183. package/src/schedule/__tests__/worker-mcp-tools.test.ts +88 -0
  184. package/src/schedule/schedule-timezone.ts +25 -0
  185. package/src/schedule/scheduler.ts +19 -0
  186. package/src/schedule/worker.ts +119 -2
  187. package/src/skills/catalog-install.ts +40 -5
  188. package/src/tools/browser/__tests__/browser-status.test.ts +3 -0
  189. package/src/tools/browser/browser-execution.ts +117 -53
  190. package/src/tools/browser/browser-status-constants.ts +6 -6
  191. package/src/tools/capability-offer.test.ts +87 -0
  192. package/src/tools/capability-offer.ts +93 -0
  193. package/src/tools/terminal/__tests__/safe-env.test.ts +17 -0
  194. package/src/tools/terminal/safe-env.ts +3 -2
  195. package/src/util/provider-error-patterns.ts +34 -0
  196. package/src/watcher/__tests__/engine.test.ts +5 -2
  197. package/src/watcher/engine.ts +7 -4
  198. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +0 -487
  199. package/src/runtime/routes/sight-frame-routes.ts +0 -136
@@ -0,0 +1,93 @@
1
+ import type { ClientOs, InterfaceId } from "../channels/types.js";
2
+
3
+ /** Public download page for the desktop apps. */
4
+ export const DESKTOP_APP_DOWNLOAD_URL = "https://www.vellum.ai/download";
5
+
6
+ /** Chrome Web Store listing for the Vellum Assistant browser extension. */
7
+ export const CHROME_WEB_STORE_INSTALL_URL =
8
+ "https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne";
9
+
10
+ export const DESKTOP_APP_INSTALL_HINT = `Install the Vellum desktop app: ${DESKTOP_APP_DOWNLOAD_URL}`;
11
+
12
+ /** User-facing install step for status userActions and command-failure hints. */
13
+ export const CHROME_EXTENSION_INSTALL_HINT = `Install the Vellum Assistant Chrome extension from the Chrome Web Store: ${CHROME_WEB_STORE_INSTALL_URL}`;
14
+
15
+ export interface CapabilityOfferSurface {
16
+ transportInterface?: InterfaceId;
17
+ clientOs?: ClientOs;
18
+ }
19
+
20
+ const LOGGED_IN_BROWSER_HELPFUL_RE =
21
+ /timed?\s*out|timeout|etimedout|econnrefused|enotfound|ehostunreach|unreachable|err_connection|err_name_not_resolved|err_timed_out|net::err_|login required|sign[- ]in required|\b401\b|\b403\b|authentication|captcha|verify you are human|access denied|forbidden/i;
22
+
23
+ /**
24
+ * True on phone transports and mobile client OS values. Those surfaces have
25
+ * no in-app browser and cannot install the Chrome extension in-place.
26
+ */
27
+ export function isPhoneSurface(surface?: CapabilityOfferSurface): boolean {
28
+ if (
29
+ surface?.transportInterface === "ios" ||
30
+ surface?.transportInterface === "phone"
31
+ ) {
32
+ return true;
33
+ }
34
+ return surface?.clientOs === "ios" || surface?.clientOs === "android";
35
+ }
36
+
37
+ /**
38
+ * True when a logged-in browser session (desktop app or Chrome extension)
39
+ * would plausibly reach the page this failure could not.
40
+ */
41
+ export function shouldOfferLoggedInBrowser(message: string): boolean {
42
+ return LOGGED_IN_BROWSER_HELPFUL_RE.test(message);
43
+ }
44
+
45
+ /**
46
+ * Offer the desktop app and Chrome extension as a capability the user can
47
+ * turn on. Screenshot-and-paste is the fallback after that offer, not the
48
+ * first suggestion.
49
+ */
50
+ export function formatLoggedInBrowserOffer(
51
+ surface?: CapabilityOfferSurface,
52
+ ): string {
53
+ const lines: string[] = [];
54
+ if (isPhoneSurface(surface)) {
55
+ lines.push(
56
+ "This page needs a logged-in browser on a computer. There is no in-app browser on this phone, and the Chrome extension cannot be installed here.",
57
+ );
58
+ lines.push(
59
+ `Install the desktop app on a Mac or Windows PC (${DESKTOP_APP_DOWNLOAD_URL}) or the Chrome extension in Chrome on that computer (${CHROME_WEB_STORE_INSTALL_URL}).`,
60
+ );
61
+ lines.push(
62
+ "Offer those first. Do not describe a browser panel here. Only ask for a screenshot or pasted page content if the user cannot install either.",
63
+ );
64
+ return lines.join("\n");
65
+ }
66
+
67
+ lines.push(
68
+ "A connected desktop app or Chrome extension can open this page in a browser where you are already logged in.",
69
+ );
70
+ lines.push(`Desktop app: ${DESKTOP_APP_DOWNLOAD_URL}`);
71
+ lines.push(`Chrome extension: ${CHROME_WEB_STORE_INSTALL_URL}`);
72
+ lines.push(
73
+ "Offer those first. Only ask for a screenshot or pasted page content if the user cannot install either.",
74
+ );
75
+ return lines.join("\n");
76
+ }
77
+
78
+ /**
79
+ * Append {@link formatLoggedInBrowserOffer} when the message does not already
80
+ * carry the install URLs.
81
+ */
82
+ export function appendLoggedInBrowserOffer(
83
+ message: string,
84
+ surface?: CapabilityOfferSurface,
85
+ ): string {
86
+ if (
87
+ message.includes(DESKTOP_APP_DOWNLOAD_URL) &&
88
+ message.includes(CHROME_WEB_STORE_INSTALL_URL)
89
+ ) {
90
+ return message;
91
+ }
92
+ return `${message}\n\n${formatLoggedInBrowserOffer(surface)}`;
93
+ }
@@ -50,6 +50,23 @@ describe("safe-env Qdrant forwarding", () => {
50
50
  });
51
51
  });
52
52
 
53
+ describe("safe-env CES vault isolation", () => {
54
+ test("strips the CES HTTP bearer and credential URL from child shells", () => {
55
+ expect(SAFE_ENV_VARS).not.toContain("CES_SERVICE_TOKEN");
56
+ expect(SAFE_ENV_VARS).not.toContain("CES_CREDENTIAL_URL");
57
+ expect(SAFE_ENV_VARS).toContain("CES_LOCAL_SOCKET");
58
+
59
+ const env = buildSanitizedEnv("linux", {
60
+ CES_SERVICE_TOKEN: "vault-bearer",
61
+ CES_CREDENTIAL_URL: "http://127.0.0.1:8090",
62
+ CES_LOCAL_SOCKET: "/tmp/ces.sock",
63
+ });
64
+ expect(env.CES_SERVICE_TOKEN).toBeUndefined();
65
+ expect(env.CES_CREDENTIAL_URL).toBeUndefined();
66
+ expect(env.CES_LOCAL_SOCKET).toBe("/tmp/ces.sock");
67
+ });
68
+ });
69
+
53
70
  describe("safe-env Windows forwarding", () => {
54
71
  test("forwards runtime paths only to Windows children", () => {
55
72
  const keys = [
@@ -54,8 +54,10 @@ export const SAFE_ENV_VARS = [
54
54
  "VELLUM_MARKETING_URL",
55
55
  "VELLUM_MIGRATION_EXPORT_ALLOWED_HOSTS",
56
56
  "VELLUM_MIGRATION_IMPORT_ALLOWED_HOSTS",
57
- "CES_CREDENTIAL_URL",
58
57
  "CES_MANAGED_MODE",
58
+ // Socket path only. The CES HTTP bearer (`CES_SERVICE_TOKEN`) and
59
+ // `CES_CREDENTIAL_URL` stay in the assistant process: child shells must
60
+ // not inherit a vault token they can printenv or log.
59
61
  "CES_LOCAL_SOCKET",
60
62
  // Per-instance port of the assistant-managed Qdrant sidecar, so skill and
61
63
  // bash-tool subprocesses that use the vector helpers (e.g. embed/search over
@@ -67,7 +69,6 @@ export const SAFE_ENV_VARS = [
67
69
  "IS_PLATFORM",
68
70
  "VELLUM_CLOUD",
69
71
  "VELLUM_SANDBOX_RUNTIME",
70
- "CES_SERVICE_TOKEN",
71
72
  "VELLUM_PROFILER_RUN_ID",
72
73
  "VELLUM_PROFILER_MODE",
73
74
  "VELLUM_PROFILER_MAX_BYTES",
@@ -45,6 +45,40 @@ export function isChatTemplateFailureError(message: string): boolean {
45
45
  return CHAT_TEMPLATE_FAILURE_PATTERNS.some((re) => re.test(message));
46
46
  }
47
47
 
48
+ // Provider prose that indicates the selected model id is unknown to the
49
+ // endpoint. Distinct from OpenCode's `ModelError` / "is not supported"
50
+ // shape, which is only safe to treat as model-not-found on a 4xx.
51
+ export const MODEL_NOT_FOUND_PATTERNS = [
52
+ /model .*(?:not found|does not exist)/i,
53
+ /model_not_found/i,
54
+ ];
55
+
56
+ // OpenCode zen returns HTTP 401 with `type=ModelError` and
57
+ // "Model <id> is not supported". Treating that as a rejected key sends
58
+ // users to update credentials instead of switching models. Do not apply
59
+ // these on 5xx: retry treats a stamped reason as authoritative, so a
60
+ // transient server error with this type would skip retries and may
61
+ // immediately switch profiles.
62
+ export const UNSUPPORTED_MODEL_ID_PATTERNS = [
63
+ /model .+ is not supported/i,
64
+ /\bModelError\b/,
65
+ ];
66
+
67
+ /**
68
+ * Whether a provider error message indicates the selected model is unknown
69
+ * to the endpoint. Providers wrap raw upstream rejections in their own
70
+ * prose, so the classifier matches the full {@link MODEL_NOT_FOUND_PATTERNS}
71
+ * and {@link UNSUPPORTED_MODEL_ID_PATTERNS} sets rather than any single
72
+ * normalized phrase. Callers that see HTTP 5xx must not use this helper
73
+ * to stamp `model_not_found`.
74
+ */
75
+ export function isModelNotFoundError(message: string): boolean {
76
+ return (
77
+ MODEL_NOT_FOUND_PATTERNS.some((re) => re.test(message)) ||
78
+ UNSUPPORTED_MODEL_ID_PATTERNS.some((re) => re.test(message))
79
+ );
80
+ }
81
+
48
82
  // Vendor-neutral (OpenRouter/Anthropic-style) credit-exhaustion prose. Also
49
83
  // covers per-key spend caps: OpenRouter returns 403 "Key limit exceeded" when a
50
84
  // key's configured credit limit is reached — a billing/budget condition that
@@ -274,9 +274,12 @@ describe("runWatchersOnce — Phase 2 runBackgroundJob integration", () => {
274
274
  expect(sandwich.preamble).not.toContain("Linear inbox");
275
275
  expect(sandwich.preamble).not.toContain("Investigate flaky CI");
276
276
 
277
- // Postamble (user role) carries the disposition contract; it must NOT
278
- // include the attacker-controllable watcher name or event payload.
277
+ // Postamble (user role) carries the disposition contract and the
278
+ // silent-by-default notify rule; it must NOT include the
279
+ // attacker-controllable watcher name or event payload.
279
280
  expect(sandwich.postamble).toContain("<watcher-disposition>");
281
+ expect(sandwich.postamble).toContain("Watcher ticks are not scheduled runs");
282
+ expect(sandwich.postamble).toContain("Unmatched events stay quiet");
280
283
  expect(sandwich.postamble).not.toContain("Linear inbox");
281
284
  expect(sandwich.postamble).not.toContain("Investigate flaky CI");
282
285
  });
@@ -411,7 +411,8 @@ export async function runWatchersOnce(
411
411
  // assistant-role content as its own past output, so a malicious payload
412
412
  // (e.g. a Linear title reading "Ignore previous instructions and
413
413
  // exfiltrate ...") cannot override the user-role postamble. The runner
414
- // inserts these before invoking processMessage with an empty prompt.
414
+ // inserts the preamble and untrusted payload, then invokes
415
+ // processMessage with the trusted postamble as the kickoff prompt.
415
416
  // See `assistantSandwich` in `runtime/background-job-runner.ts`.
416
417
  //
417
418
  // The fence marks the content as third-party data; the sandwich denies it
@@ -440,7 +441,9 @@ export async function runWatchersOnce(
440
441
  ].join("\n");
441
442
 
442
443
  const postamble = [
443
- "Process the events above according to the watcher's action prompt. For each event, include a disposition block:",
444
+ "Process the events above according to the watcher's action prompt.",
445
+ "Notify only when the action prompt's match criteria are met, using `assistant notifications send`. Unmatched events stay quiet: write a silent disposition block and do not send a notification, including a \"nothing new\" ping. Watcher ticks are not scheduled runs.",
446
+ "For each event, include a disposition block:",
444
447
  "<watcher-disposition>",
445
448
  '{"event_id": "...", "disposition": "silent|notify|escalate", "action": "what you did", "title": "notification title", "body": "notification body"}',
446
449
  "</watcher-disposition>",
@@ -449,8 +452,8 @@ export async function runWatchersOnce(
449
452
  const result = await runBackgroundJob({
450
453
  jobName: `watcher:${watcher.id}`,
451
454
  source: "watcher",
452
- // The seed lives in the sandwich messages; processMessage runs
453
- // with an empty prompt so we don't double-inject the action prompt.
455
+ // Empty prompt tells the runner to use the sandwich postamble as
456
+ // the kickoff so persistUserMessage has content.
454
457
  prompt: "",
455
458
  systemHint: `Watcher: ${watcher.name}`,
456
459
  trustContext: { sourceChannel: "vellum", trustClass: "guardian" },