@vellumai/assistant 0.4.48 → 0.4.49

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 (252) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/runbook-trusted-contacts.md +3 -8
  6. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  7. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +0 -1
  10. package/src/__tests__/anthropic-provider.test.ts +156 -0
  11. package/src/__tests__/approval-cascade.test.ts +810 -0
  12. package/src/__tests__/approval-primitive.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +2 -0
  14. package/src/__tests__/assistant-attachments.test.ts +12 -34
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  17. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  18. package/src/__tests__/channel-guardian.test.ts +0 -2
  19. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  20. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  21. package/src/__tests__/checker.test.ts +9 -29
  22. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  23. package/src/__tests__/computer-use-tools.test.ts +2 -19
  24. package/src/__tests__/config-watcher.test.ts +0 -1
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  26. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  27. package/src/__tests__/context-token-estimator.test.ts +196 -13
  28. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  31. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  32. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  33. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  34. package/src/__tests__/credential-vault-unit.test.ts +280 -49
  35. package/src/__tests__/credential-vault.test.ts +138 -16
  36. package/src/__tests__/credentials-cli.test.ts +71 -0
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  38. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  39. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  40. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  41. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  42. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  43. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  44. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  45. package/src/__tests__/heartbeat-service.test.ts +0 -1
  46. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  47. package/src/__tests__/host-shell-tool.test.ts +27 -15
  48. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  49. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  50. package/src/__tests__/integration-status.test.ts +32 -51
  51. package/src/__tests__/intent-routing.test.ts +0 -1
  52. package/src/__tests__/invite-routes-http.test.ts +10 -9
  53. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  54. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  55. package/src/__tests__/oauth-cli.test.ts +373 -14
  56. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  57. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  58. package/src/__tests__/oauth-store.test.ts +756 -0
  59. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  60. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  61. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  62. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  63. package/src/__tests__/recording-handler.test.ts +3 -4
  64. package/src/__tests__/registry.test.ts +2 -2
  65. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  66. package/src/__tests__/schedule-store.test.ts +0 -1
  67. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  68. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  69. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  70. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  71. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  72. package/src/__tests__/sequence-store.test.ts +0 -1
  73. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  74. package/src/__tests__/skill-include-graph.test.ts +66 -0
  75. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  76. package/src/__tests__/skill-load-tool.test.ts +149 -1
  77. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  78. package/src/__tests__/skills-uninstall.test.ts +1 -1
  79. package/src/__tests__/skills.test.ts +3 -3
  80. package/src/__tests__/slack-channel-config.test.ts +67 -3
  81. package/src/__tests__/slack-share-routes.test.ts +17 -19
  82. package/src/__tests__/system-prompt.test.ts +0 -1
  83. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  84. package/src/__tests__/terminal-tools.test.ts +4 -3
  85. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  86. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  87. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  88. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  90. package/src/__tests__/tool-executor.test.ts +0 -1
  91. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  92. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  93. package/src/__tests__/trust-store.test.ts +1 -22
  94. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  95. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  96. package/src/__tests__/twilio-routes.test.ts +0 -16
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  99. package/src/agent/ax-tree-compaction.test.ts +235 -0
  100. package/src/agent/loop.ts +76 -130
  101. package/src/calls/call-domain.ts +1 -6
  102. package/src/calls/relay-server.ts +9 -13
  103. package/src/calls/twilio-config.ts +2 -7
  104. package/src/calls/twilio-routes.ts +1 -2
  105. package/src/calls/voice-ingress-preflight.ts +1 -1
  106. package/src/cli/commands/browser-relay.ts +18 -12
  107. package/src/cli/commands/completions.ts +0 -3
  108. package/src/cli/commands/credentials.ts +101 -15
  109. package/src/cli/commands/oauth/apps.ts +255 -0
  110. package/src/cli/commands/oauth/connections.ts +299 -0
  111. package/src/cli/commands/oauth/index.ts +52 -0
  112. package/src/cli/commands/oauth/providers.ts +242 -0
  113. package/src/cli/commands/skills.ts +4 -338
  114. package/src/cli/program.ts +1 -5
  115. package/src/cli/reference.ts +1 -3
  116. package/src/config/assistant-feature-flags.ts +0 -3
  117. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  118. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  119. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  120. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  121. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  122. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  123. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  124. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  125. package/src/config/env-registry.ts +14 -83
  126. package/src/config/env.ts +11 -50
  127. package/src/config/feature-flag-registry.json +16 -16
  128. package/src/config/loader.ts +0 -6
  129. package/src/config/schema.ts +3 -1
  130. package/src/config/skills.ts +21 -2
  131. package/src/context/image-dimensions.ts +229 -0
  132. package/src/context/token-estimator.ts +75 -12
  133. package/src/context/window-manager.ts +49 -10
  134. package/src/daemon/assistant-attachments.ts +1 -13
  135. package/src/daemon/handlers/config-ingress.ts +8 -33
  136. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  137. package/src/daemon/handlers/config-telegram.ts +32 -16
  138. package/src/daemon/handlers/sessions.ts +10 -24
  139. package/src/daemon/handlers/shared.ts +0 -130
  140. package/src/daemon/host-cu-proxy.ts +401 -0
  141. package/src/daemon/lifecycle.ts +36 -68
  142. package/src/daemon/message-protocol.ts +3 -0
  143. package/src/daemon/message-types/computer-use.ts +2 -119
  144. package/src/daemon/message-types/host-cu.ts +19 -0
  145. package/src/daemon/message-types/messages.ts +3 -0
  146. package/src/daemon/server.ts +14 -21
  147. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  148. package/src/daemon/session-attachments.ts +1 -2
  149. package/src/daemon/session-slash.ts +1 -1
  150. package/src/daemon/session-surfaces.ts +40 -28
  151. package/src/daemon/session-tool-setup.ts +2 -9
  152. package/src/daemon/session.ts +138 -15
  153. package/src/daemon/tool-side-effects.ts +2 -8
  154. package/src/daemon/watch-handler.ts +2 -2
  155. package/src/events/tool-metrics-listener.ts +2 -2
  156. package/src/hooks/manager.ts +1 -4
  157. package/src/inbound/public-ingress-urls.ts +7 -7
  158. package/src/logfire.ts +16 -5
  159. package/src/memory/conversation-key-store.ts +21 -0
  160. package/src/memory/db-init.ts +4 -0
  161. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  162. package/src/memory/migrations/index.ts +1 -0
  163. package/src/memory/schema/index.ts +1 -0
  164. package/src/memory/schema/oauth.ts +65 -0
  165. package/src/messaging/provider.ts +4 -4
  166. package/src/messaging/providers/gmail/client.ts +82 -2
  167. package/src/messaging/providers/gmail/people-client.ts +10 -10
  168. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  169. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  170. package/src/messaging/registry.ts +2 -32
  171. package/src/notifications/copy-composer.ts +0 -5
  172. package/src/notifications/signal.ts +4 -5
  173. package/src/oauth/byo-connection.test.ts +126 -25
  174. package/src/oauth/byo-connection.ts +22 -6
  175. package/src/oauth/connect-orchestrator.ts +113 -57
  176. package/src/oauth/connect-types.ts +17 -23
  177. package/src/oauth/connection-resolver.ts +35 -11
  178. package/src/oauth/connection.ts +1 -1
  179. package/src/oauth/manual-token-connection.ts +104 -0
  180. package/src/oauth/oauth-store.ts +496 -0
  181. package/src/oauth/platform-connection.test.ts +29 -0
  182. package/src/oauth/platform-connection.ts +6 -5
  183. package/src/oauth/provider-behaviors.ts +124 -0
  184. package/src/oauth/scope-policy.ts +9 -2
  185. package/src/oauth/seed-providers.ts +161 -0
  186. package/src/oauth/token-persistence.ts +74 -78
  187. package/src/permissions/checker.ts +3 -3
  188. package/src/permissions/defaults.ts +0 -1
  189. package/src/permissions/prompter.ts +10 -1
  190. package/src/permissions/trust-store.ts +13 -0
  191. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  192. package/src/prompts/system-prompt.ts +28 -40
  193. package/src/providers/anthropic/client.ts +133 -24
  194. package/src/providers/retry.ts +1 -27
  195. package/src/runtime/auth/route-policy.ts +0 -3
  196. package/src/runtime/channel-reply-delivery.ts +0 -40
  197. package/src/runtime/gateway-client.ts +0 -7
  198. package/src/runtime/http-server.ts +8 -6
  199. package/src/runtime/http-types.ts +2 -2
  200. package/src/runtime/middleware/twilio-validation.ts +1 -11
  201. package/src/runtime/pending-interactions.ts +14 -12
  202. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  203. package/src/runtime/routes/conversation-routes.ts +73 -19
  204. package/src/runtime/routes/events-routes.ts +21 -11
  205. package/src/runtime/routes/host-cu-routes.ts +97 -0
  206. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  207. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  208. package/src/runtime/routes/log-export-routes.ts +126 -8
  209. package/src/runtime/routes/settings-routes.ts +55 -48
  210. package/src/runtime/routes/surface-action-routes.ts +1 -1
  211. package/src/runtime/routes/watch-routes.ts +128 -0
  212. package/src/schedule/integration-status.ts +10 -9
  213. package/src/security/credential-key.ts +0 -156
  214. package/src/security/keychain-broker-client.ts +5 -6
  215. package/src/security/oauth2.ts +1 -1
  216. package/src/security/token-manager.ts +119 -46
  217. package/src/skills/catalog-install.ts +358 -0
  218. package/src/skills/include-graph.ts +32 -0
  219. package/src/telegram/bot-username.ts +2 -3
  220. package/src/tools/browser/network-recorder.ts +1 -1
  221. package/src/tools/browser/network-recording-types.ts +1 -1
  222. package/src/tools/computer-use/definitions.ts +46 -11
  223. package/src/tools/computer-use/registry.ts +4 -5
  224. package/src/tools/credentials/broker.ts +1 -2
  225. package/src/tools/credentials/metadata-store.ts +17 -121
  226. package/src/tools/credentials/vault.ts +94 -167
  227. package/src/tools/registry.ts +2 -7
  228. package/src/tools/skills/load.ts +62 -3
  229. package/src/tools/watch/watch-state.ts +0 -12
  230. package/src/util/logger.ts +7 -41
  231. package/src/util/platform.ts +9 -28
  232. package/src/watcher/providers/google-calendar.ts +2 -1
  233. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  234. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  235. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  236. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  237. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  238. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  239. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  240. package/src/cli/commands/dev.ts +0 -129
  241. package/src/cli/commands/map.ts +0 -391
  242. package/src/cli/commands/oauth.ts +0 -77
  243. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  244. package/src/daemon/computer-use-session.ts +0 -1026
  245. package/src/daemon/ride-shotgun-handler.ts +0 -569
  246. package/src/oauth/provider-base-urls.ts +0 -21
  247. package/src/oauth/provider-profiles.ts +0 -192
  248. package/src/prompts/computer-use-prompt.ts +0 -98
  249. package/src/runtime/routes/computer-use-routes.ts +0 -641
  250. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  251. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  252. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -63,6 +63,20 @@ function isToolUseBlock(block: unknown): block is Anthropic.ToolUseBlockParam {
63
63
  );
64
64
  }
65
65
 
66
+ /** Type-guard for server_tool_use blocks (e.g. native web search). */
67
+ function isServerToolUseBlock(block: unknown): block is {
68
+ type: "server_tool_use";
69
+ id: string;
70
+ name: string;
71
+ input: unknown;
72
+ } {
73
+ return (
74
+ typeof block === "object" &&
75
+ block != null &&
76
+ (block as { type: string }).type === "server_tool_use"
77
+ );
78
+ }
79
+
66
80
  /** Type-guard for tool_result blocks in Anthropic-formatted content. */
67
81
  function isToolResultBlock(
68
82
  block: unknown,
@@ -74,6 +88,19 @@ function isToolResultBlock(
74
88
  );
75
89
  }
76
90
 
91
+ /** Type-guard for web_search_tool_result blocks. */
92
+ function isWebSearchToolResultBlock(block: unknown): block is {
93
+ type: "web_search_tool_result";
94
+ tool_use_id: string;
95
+ content: unknown;
96
+ } {
97
+ return (
98
+ typeof block === "object" &&
99
+ block != null &&
100
+ (block as { type: string }).type === "web_search_tool_result"
101
+ );
102
+ }
103
+
77
104
  /**
78
105
  * Build a short diagnostic summary of a message array for error logging.
79
106
  * Shows role + block types (with tool_use/tool_result IDs) for each message.
@@ -84,8 +111,12 @@ function summarizeMessages(messages: Anthropic.MessageParam[]): string[] {
84
111
  const blockDescs = content.map((b) => {
85
112
  const bt = (b as { type: string }).type;
86
113
  if (bt === "tool_use") return `tool_use(${(b as { id: string }).id})`;
114
+ if (bt === "server_tool_use")
115
+ return `server_tool_use(${(b as { id: string }).id})`;
87
116
  if (bt === "tool_result")
88
117
  return `tool_result(${(b as { tool_use_id: string }).tool_use_id})`;
118
+ if (bt === "web_search_tool_result")
119
+ return `web_search_tool_result(${(b as { tool_use_id: string }).tool_use_id})`;
89
120
  return bt;
90
121
  });
91
122
  return `[${idx}] ${m.role}: ${blockDescs.join(", ") || "(empty)"}`;
@@ -103,29 +134,70 @@ function buildSyntheticToolResult(
103
134
  };
104
135
  }
105
136
 
106
- function getOrderedToolUseIds(
107
- content: Anthropic.ContentBlockParam[],
108
- ): string[] {
137
+ function buildSyntheticWebSearchToolResult(
138
+ toolUseId: string,
139
+ ): Anthropic.ContentBlockParam {
140
+ return {
141
+ type: "web_search_tool_result",
142
+ tool_use_id: toolUseId,
143
+ content: {
144
+ type: "web_search_tool_result_error",
145
+ error_code: "unavailable",
146
+ },
147
+ } as unknown as Anthropic.ContentBlockParam;
148
+ }
149
+
150
+ /** Build the appropriate synthetic result block based on whether the ID is for a server tool or regular tool. */
151
+ function buildSyntheticResult(
152
+ toolUseId: string,
153
+ serverToolIds: ReadonlySet<string>,
154
+ ): Anthropic.ContentBlockParam {
155
+ if (serverToolIds.has(toolUseId)) {
156
+ return buildSyntheticWebSearchToolResult(toolUseId);
157
+ }
158
+ return buildSyntheticToolResult(toolUseId);
159
+ }
160
+
161
+ function getOrderedToolUseIds(content: Anthropic.ContentBlockParam[]): {
162
+ ids: string[];
163
+ serverToolIds: Set<string>;
164
+ } {
109
165
  const ids: string[] = [];
110
166
  const seen = new Set<string>();
167
+ const serverToolIds = new Set<string>();
111
168
  for (const block of content) {
112
- if (!isToolUseBlock(block)) continue;
113
- if (seen.has(block.id)) continue;
114
- seen.add(block.id);
115
- ids.push(block.id);
169
+ if (isToolUseBlock(block)) {
170
+ if (!seen.has(block.id)) {
171
+ seen.add(block.id);
172
+ ids.push(block.id);
173
+ }
174
+ } else if (isServerToolUseBlock(block)) {
175
+ if (!seen.has(block.id)) {
176
+ seen.add(block.id);
177
+ ids.push(block.id);
178
+ serverToolIds.add(block.id);
179
+ }
180
+ }
116
181
  }
117
- return ids;
182
+ return { ids, serverToolIds };
118
183
  }
119
184
 
120
185
  function hasOrderedToolResultPrefix(
121
186
  content: Anthropic.ContentBlockParam[],
122
187
  orderedToolUseIds: string[],
188
+ serverToolIds: ReadonlySet<string>,
123
189
  ): boolean {
124
190
  if (content.length < orderedToolUseIds.length) return false;
125
191
  for (let idx = 0; idx < orderedToolUseIds.length; idx++) {
126
192
  const block = content[idx];
127
- if (!isToolResultBlock(block)) return false;
128
- if (block.tool_use_id !== orderedToolUseIds[idx]) return false;
193
+ const expectedId = orderedToolUseIds[idx];
194
+ if (serverToolIds.has(expectedId)) {
195
+ if (!isWebSearchToolResultBlock(block)) return false;
196
+ if (block.tool_use_id !== expectedId) return false;
197
+ } else {
198
+ if (!isToolResultBlock(block)) return false;
199
+ if (block.tool_use_id !== expectedId) return false;
200
+ }
129
201
  }
130
202
  return true;
131
203
  }
@@ -134,14 +206,15 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
134
206
  pairedContent: Anthropic.ContentBlockParam[];
135
207
  carryoverContent: Anthropic.ContentBlockParam[];
136
208
  toolUseIds: string[];
209
+ serverToolIds: Set<string>;
137
210
  } {
138
211
  const leading: Anthropic.ContentBlockParam[] = [];
139
- const toolUseBlocks: Anthropic.ToolUseBlockParam[] = [];
212
+ const toolUseBlocks: Anthropic.ContentBlockParam[] = [];
140
213
  const carryover: Anthropic.ContentBlockParam[] = [];
141
214
  let seenToolUse = false;
142
215
 
143
216
  for (const block of content) {
144
- if (isToolUseBlock(block)) {
217
+ if (isToolUseBlock(block) || isServerToolUseBlock(block)) {
145
218
  seenToolUse = true;
146
219
  toolUseBlocks.push(block);
147
220
  continue;
@@ -158,6 +231,7 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
158
231
  pairedContent: content,
159
232
  carryoverContent: [],
160
233
  toolUseIds: [],
234
+ serverToolIds: new Set(),
161
235
  };
162
236
  }
163
237
 
@@ -165,16 +239,19 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
165
239
  ...leading,
166
240
  ...toolUseBlocks,
167
241
  ];
242
+ const { ids, serverToolIds } = getOrderedToolUseIds(pairedContent);
168
243
  return {
169
244
  pairedContent,
170
245
  carryoverContent: carryover,
171
- toolUseIds: getOrderedToolUseIds(pairedContent),
246
+ toolUseIds: ids,
247
+ serverToolIds,
172
248
  };
173
249
  }
174
250
 
175
251
  function normalizeFollowingUserContent(
176
252
  nextContent: Anthropic.ContentBlockParam[],
177
253
  orderedToolUseIds: string[],
254
+ serverToolIds: ReadonlySet<string>,
178
255
  ): {
179
256
  toolResultPrefix: Anthropic.ContentBlockParam[];
180
257
  remainingContent: Anthropic.ContentBlockParam[];
@@ -182,24 +259,37 @@ function normalizeFollowingUserContent(
182
259
  hadOrderedPrefix: boolean;
183
260
  } {
184
261
  const pendingIds = new Set(orderedToolUseIds);
185
- const matchedById = new Map<string, Anthropic.ToolResultBlockParam>();
262
+ const matchedById = new Map<string, Anthropic.ContentBlockParam>();
186
263
  const remaining: Anthropic.ContentBlockParam[] = [];
187
264
 
188
265
  for (const block of nextContent) {
189
266
  if (
190
267
  isToolResultBlock(block) &&
191
268
  pendingIds.has(block.tool_use_id) &&
192
- !matchedById.has(block.tool_use_id)
269
+ !matchedById.has(block.tool_use_id) &&
270
+ !serverToolIds.has(block.tool_use_id)
193
271
  ) {
194
272
  matchedById.set(block.tool_use_id, block);
195
273
  continue;
196
274
  }
275
+ if (
276
+ isWebSearchToolResultBlock(block) &&
277
+ pendingIds.has(block.tool_use_id) &&
278
+ !matchedById.has(block.tool_use_id) &&
279
+ serverToolIds.has(block.tool_use_id)
280
+ ) {
281
+ matchedById.set(
282
+ block.tool_use_id,
283
+ block as unknown as Anthropic.ContentBlockParam,
284
+ );
285
+ continue;
286
+ }
197
287
  remaining.push(block);
198
288
  }
199
289
 
200
290
  const missingIds = orderedToolUseIds.filter((id) => !matchedById.has(id));
201
291
  const orderedResults = orderedToolUseIds.map(
202
- (id) => matchedById.get(id) ?? buildSyntheticToolResult(id),
292
+ (id) => matchedById.get(id) ?? buildSyntheticResult(id, serverToolIds),
203
293
  );
204
294
 
205
295
  return {
@@ -209,6 +299,7 @@ function normalizeFollowingUserContent(
209
299
  hadOrderedPrefix: hasOrderedToolResultPrefix(
210
300
  nextContent,
211
301
  orderedToolUseIds,
302
+ serverToolIds,
212
303
  ),
213
304
  };
214
305
  }
@@ -237,7 +328,7 @@ function ensureToolPairing(
237
328
  }
238
329
 
239
330
  const content = Array.isArray(msg.content) ? msg.content : [];
240
- const { pairedContent, carryoverContent, toolUseIds } =
331
+ const { pairedContent, carryoverContent, toolUseIds, serverToolIds } =
241
332
  splitAssistantForToolPairing(content);
242
333
 
243
334
  if (toolUseIds.length === 0) {
@@ -246,7 +337,7 @@ function ensureToolPairing(
246
337
  continue;
247
338
  }
248
339
 
249
- // Assistant message — push the paired portion (pre-tool text + tool_use blocks)
340
+ // Assistant message — push the paired portion (pre-tool text + tool_use/server_tool_use blocks)
250
341
  result.push({
251
342
  role: "assistant" as const,
252
343
  content: pairedContent,
@@ -267,7 +358,11 @@ function ensureToolPairing(
267
358
  const next = messages[i + 1];
268
359
  if (next && next.role === "user") {
269
360
  const nextContent = Array.isArray(next.content) ? next.content : [];
270
- const normalized = normalizeFollowingUserContent(nextContent, toolUseIds);
361
+ const normalized = normalizeFollowingUserContent(
362
+ nextContent,
363
+ toolUseIds,
364
+ serverToolIds,
365
+ );
271
366
  if (normalized.missingIds.length > 0) {
272
367
  log.warn(
273
368
  {
@@ -332,7 +427,9 @@ function ensureToolPairing(
332
427
  );
333
428
  result.push({
334
429
  role: "user" as const,
335
- content: toolUseIds.map((id) => buildSyntheticToolResult(id)),
430
+ content: toolUseIds.map((id) =>
431
+ buildSyntheticResult(id, serverToolIds),
432
+ ),
336
433
  });
337
434
 
338
435
  // If the assistant contained collapsed post-tool text, preserve it as a
@@ -353,17 +450,29 @@ function ensureToolPairing(
353
450
  const m = result[j];
354
451
  if (m.role !== "assistant") continue;
355
452
  const c = Array.isArray(m.content) ? m.content : [];
356
- const ids = getOrderedToolUseIds(c);
357
- if (ids.length === 0) continue;
453
+ const { ids: validationIds, serverToolIds: validationServerToolIds } =
454
+ getOrderedToolUseIds(c);
455
+ if (validationIds.length === 0) continue;
358
456
 
359
457
  const nxt = result[j + 1];
360
458
  const nxtContent =
361
459
  nxt && nxt.role === "user" && Array.isArray(nxt.content)
362
460
  ? nxt.content
363
461
  : [];
364
- if (!hasOrderedToolResultPrefix(nxtContent, ids)) {
365
- const unmatchedIds = ids.filter((id, idx) => {
462
+ if (
463
+ !hasOrderedToolResultPrefix(
464
+ nxtContent,
465
+ validationIds,
466
+ validationServerToolIds,
467
+ )
468
+ ) {
469
+ const unmatchedIds = validationIds.filter((id, idx) => {
366
470
  const block = nxtContent[idx];
471
+ if (validationServerToolIds.has(id)) {
472
+ return !(
473
+ isWebSearchToolResultBlock(block) && block.tool_use_id === id
474
+ );
475
+ }
367
476
  return !(isToolResultBlock(block) && block.tool_use_id === id);
368
477
  });
369
478
  log.error(
@@ -1,5 +1,5 @@
1
1
  import { ProviderError } from "../util/errors.js";
2
- import { getLogger, isDebug } from "../util/logger.js";
2
+ import { getLogger } from "../util/logger.js";
3
3
  import {
4
4
  computeRetryDelay,
5
5
  DEFAULT_BASE_DELAY_MS,
@@ -96,43 +96,17 @@ export class RetryProvider implements Provider {
96
96
  options?: SendMessageOptions,
97
97
  ): Promise<ProviderResponse> {
98
98
  let lastError: unknown;
99
- const debug = isDebug();
100
-
101
- if (debug) {
102
- log.debug(
103
- {
104
- provider: this.name,
105
- messageCount: messages.length,
106
- toolCount: tools?.length ?? 0,
107
- },
108
- "Provider sendMessage start",
109
- );
110
- }
111
99
 
112
100
  const normalizedOptions = normalizeSendMessageOptions(this.name, options);
113
101
 
114
102
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
115
103
  try {
116
- const start = Date.now();
117
104
  const result = await this.inner.sendMessage(
118
105
  messages,
119
106
  tools,
120
107
  systemPrompt,
121
108
  normalizedOptions,
122
109
  );
123
- if (debug) {
124
- log.debug(
125
- {
126
- provider: this.name,
127
- durationMs: Date.now() - start,
128
- attempt: attempt + 1,
129
- model: result.model,
130
- inputTokens: result.usage.inputTokens,
131
- outputTokens: result.usage.outputTokens,
132
- },
133
- "Provider sendMessage success",
134
- );
135
- }
136
110
  return result;
137
111
  } catch (error) {
138
112
  lastError = error;
@@ -358,9 +358,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
358
358
  { endpoint: "computer-use/sessions/abort", scopes: ["chat.write"] },
359
359
  { endpoint: "computer-use/observations", scopes: ["chat.write"] },
360
360
  { endpoint: "computer-use/tasks", scopes: ["chat.write"] },
361
- { endpoint: "computer-use/ride-shotgun/start", scopes: ["chat.write"] },
362
- { endpoint: "computer-use/ride-shotgun/stop", scopes: ["chat.write"] },
363
- { endpoint: "computer-use/ride-shotgun/status", scopes: ["chat.write"] },
364
361
  { endpoint: "computer-use/watch", scopes: ["chat.write"] },
365
362
 
366
363
  // Recordings
@@ -208,43 +208,3 @@ export async function deliverReplyViaCallback(
208
208
  break;
209
209
  }
210
210
  }
211
-
212
- /**
213
- * Deliver only the attachments from the last assistant message, skipping text.
214
- * Used when streaming already delivered the text content and only file
215
- * attachments remain to be sent via the normal delivery path.
216
- */
217
- export async function deliverAttachmentsOnly(
218
- conversationId: string,
219
- externalChatId: string,
220
- callbackUrl: string,
221
- bearerToken?: string,
222
- assistantId?: string,
223
- ): Promise<void> {
224
- const msgs = getMessages(conversationId);
225
- for (let i = msgs.length - 1; i >= 0; i--) {
226
- if (msgs[i].role !== "assistant") continue;
227
-
228
- const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
229
- if (linked.length === 0) return;
230
-
231
- const replyAttachments: RuntimeAttachmentMetadata[] = linked.map((a) => ({
232
- id: a.id,
233
- filename: a.originalFilename,
234
- mimeType: a.mimeType,
235
- sizeBytes: a.sizeBytes,
236
- kind: a.kind,
237
- }));
238
-
239
- await deliverChannelReply(
240
- callbackUrl,
241
- {
242
- chatId: externalChatId,
243
- attachments: replyAttachments,
244
- assistantId,
245
- },
246
- bearerToken,
247
- );
248
- break;
249
- }
250
- }
@@ -31,8 +31,6 @@ export interface ChannelReplyPayload {
31
31
  ephemeral?: boolean;
32
32
  /** Slack user ID — required when `ephemeral` is true. */
33
33
  user?: string;
34
- /** Telegram message_id for editing an existing message instead of sending a new one. */
35
- messageId?: number;
36
34
  /** When provided, instructs the delivery endpoint to update an existing message instead of posting a new one. */
37
35
  messageTs?: string;
38
36
  /** When true, auto-generate Block Kit blocks from text via textToBlocks(). */
@@ -45,8 +43,6 @@ export interface ChannelDeliveryResult {
45
43
  ok: boolean;
46
44
  /** The message timestamp returned by the delivery endpoint (e.g. Slack message ts). */
47
45
  ts?: string;
48
- /** The Telegram message_id returned when a new message was sent. */
49
- messageId?: number;
50
46
  }
51
47
 
52
48
  interface ManagedOutboundCallbackContext {
@@ -100,9 +96,6 @@ export async function deliverChannelReply(
100
96
  if (typeof responseBody.ts === "string") {
101
97
  result.ts = responseBody.ts;
102
98
  }
103
- if (typeof responseBody.messageId === "number") {
104
- result.messageId = responseBody.messageId;
105
- }
106
99
  } catch {
107
100
  // Response may not be JSON for non-Slack channels; that's fine.
108
101
  }
@@ -110,7 +110,6 @@ import {
110
110
  stopGuardianExpirySweep,
111
111
  } from "./routes/channel-routes.js";
112
112
  import { channelVerificationRouteDefinitions } from "./routes/channel-verification-routes.js";
113
- import { computerUseRouteDefinitions } from "./routes/computer-use-routes.js";
114
113
  import {
115
114
  contactCatchAllRouteDefinitions,
116
115
  contactRouteDefinitions,
@@ -126,6 +125,7 @@ import { guardianActionRouteDefinitions } from "./routes/guardian-action-routes.
126
125
  import { handleGuardianBootstrap } from "./routes/guardian-bootstrap-routes.js";
127
126
  import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
128
127
  import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
128
+ import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
129
129
  import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
130
130
  import { handleHealth } from "./routes/identity-routes.js";
131
131
  import { identityRouteDefinitions } from "./routes/identity-routes.js";
@@ -155,6 +155,7 @@ import { surfaceActionRouteDefinitions } from "./routes/surface-action-routes.js
155
155
  import { surfaceContentRouteDefinitions } from "./routes/surface-content-routes.js";
156
156
  import { trustRulesRouteDefinitions } from "./routes/trust-rules-routes.js";
157
157
  import { usageRouteDefinitions } from "./routes/usage-routes.js";
158
+ import { watchRouteDefinitions } from "./routes/watch-routes.js";
158
159
  import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
159
160
  import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
160
161
 
@@ -216,7 +217,7 @@ export class RuntimeHttpServer {
216
217
  private getSkillContext?: RuntimeHttpServerOptions["getSkillContext"];
217
218
  private sessionManagementDeps?: RuntimeHttpServerOptions["sessionManagementDeps"];
218
219
  private getModelSetContext?: RuntimeHttpServerOptions["getModelSetContext"];
219
- private getComputerUseDeps?: RuntimeHttpServerOptions["getComputerUseDeps"];
220
+ private getWatchDeps?: RuntimeHttpServerOptions["getWatchDeps"];
220
221
  private getRecordingDeps?: RuntimeHttpServerOptions["getRecordingDeps"];
221
222
  private router: HttpRouter;
222
223
 
@@ -237,7 +238,7 @@ export class RuntimeHttpServer {
237
238
  this.getSkillContext = options.getSkillContext;
238
239
  this.sessionManagementDeps = options.sessionManagementDeps;
239
240
  this.getModelSetContext = options.getModelSetContext;
240
- this.getComputerUseDeps = options.getComputerUseDeps;
241
+ this.getWatchDeps = options.getWatchDeps;
241
242
  this.getRecordingDeps = options.getRecordingDeps;
242
243
  this.router = new HttpRouter(this.buildRouteTable());
243
244
  }
@@ -946,6 +947,7 @@ export class RuntimeHttpServer {
946
947
  ...globalSearchRouteDefinitions(),
947
948
  ...approvalRouteDefinitions(),
948
949
  ...hostBashRouteDefinitions(),
950
+ ...hostCuRouteDefinitions(),
949
951
  ...hostFileRouteDefinitions(),
950
952
  ...(this.getSkillContext
951
953
  ? skillRouteDefinitions({
@@ -973,9 +975,9 @@ export class RuntimeHttpServer {
973
975
  ...channelReadinessRouteDefinitions(),
974
976
  ...attachmentRouteDefinitions(),
975
977
 
976
- ...(this.getComputerUseDeps
977
- ? computerUseRouteDefinitions({
978
- getComputerUseDeps: this.getComputerUseDeps,
978
+ ...(this.getWatchDeps
979
+ ? watchRouteDefinitions({
980
+ getWatchDeps: this.getWatchDeps,
979
981
  })
980
982
  : []),
981
983
  ...(this.getRecordingDeps
@@ -219,8 +219,8 @@ export interface RuntimeHttpServerOptions {
219
219
  sessionManagementDeps?: SessionManagementDeps;
220
220
  /** Lazy factory for model config set context (session eviction, config reload suppression). */
221
221
  getModelSetContext?: () => import("../daemon/handlers/config-model.js").ModelSetContext;
222
- /** Provider for computer-use session dependencies (CU routes). */
223
- getComputerUseDeps?: () => import("./routes/computer-use-routes.js").ComputerUseDeps;
222
+ /** Provider for watch observation dependencies (watch routes). */
223
+ getWatchDeps?: () => import("./routes/watch-routes.js").WatchDeps;
224
224
  /** Provider for recording dependencies (recording routes). */
225
225
  getRecordingDeps?: () => import("./routes/recording-routes.js").RecordingDeps;
226
226
  }
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { TwilioConversationRelayProvider } from "../../calls/twilio-provider.js";
6
- import { isTwilioWebhookValidationDisabled } from "../../config/env.js";
7
6
  import { loadConfig } from "../../config/loader.js";
8
7
  import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
9
8
  import { getLogger } from "../../util/logger.js";
@@ -51,22 +50,13 @@ export const GATEWAY_ONLY_BLOCKED_SUBPATHS = new Set([
51
50
  * Returns a 403 Response if signature validation fails.
52
51
  *
53
52
  * Fail-closed: if the auth token is not configured, the request is rejected
54
- * with 403 rather than silently skipping validation. An explicit local-dev
55
- * bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
53
+ * with 403 rather than silently skipping validation.
56
54
  */
57
55
  export async function validateTwilioWebhook(
58
56
  req: Request,
59
57
  ): Promise<{ body: string } | Response> {
60
58
  const rawBody = await req.text();
61
59
 
62
- // Allow explicit local-dev bypass -- must be exactly "true"
63
- if (isTwilioWebhookValidationDisabled()) {
64
- log.warn(
65
- "Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED",
66
- );
67
- return { body: rawBody };
68
- }
69
-
70
60
  const authToken = TwilioConversationRelayProvider.getAuthToken();
71
61
 
72
62
  if (!authToken) {
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * In-memory tracker that maps requestId to session info for pending
3
- * confirmation, secret, host_bash, and host_file interactions.
3
+ * confirmation, secret, host_bash, host_file, and host_cu interactions.
4
4
  *
5
5
  * When the agent loop emits a confirmation_request, secret_request,
6
- * host_bash_request, or host_file_request, the onEvent callback registers
7
- * the interaction here. Standalone HTTP endpoints (/v1/confirm, /v1/secret,
8
- * /v1/trust-rules, /v1/host-bash-result, /v1/host-file-result) look up
9
- * the session from this tracker to resolve the interaction.
6
+ * host_bash_request, host_file_request, or host_cu_request, the onEvent
7
+ * callback registers the interaction here. Standalone HTTP endpoints
8
+ * (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
9
+ * /v1/host-file-result, /v1/host-cu-result) look up the session from
10
+ * this tracker to resolve the interaction.
10
11
  */
11
12
 
12
13
  import type { Session } from "../daemon/session.js";
@@ -29,7 +30,7 @@ export interface ConfirmationDetails {
29
30
  export interface PendingInteraction {
30
31
  session: Session;
31
32
  conversationId: string;
32
- kind: "confirmation" | "secret" | "host_bash" | "host_file";
33
+ kind: "confirmation" | "secret" | "host_bash" | "host_file" | "host_cu";
33
34
  confirmationDetails?: ConfirmationDetails;
34
35
  }
35
36
 
@@ -82,19 +83,20 @@ export function getByConversation(
82
83
  * Remove pending confirmation and secret interactions for a given session.
83
84
  * Used when auto-denying all pending interactions (e.g. new user message).
84
85
  *
85
- * host_bash and host_file interactions are intentionally skipped — they
86
- * represent in-flight tool executions proxied to the client, not
86
+ * host_bash, host_file, and host_cu interactions are intentionally skipped
87
+ * — they represent in-flight tool executions proxied to the client, not
87
88
  * confirmations to auto-deny. Removing them would orphan the request: the
88
- * client would POST to /v1/host-bash-result or /v1/host-file-result after
89
- * completing the operation, get a 404, and the proxy timer would fire with
90
- * a spurious timeout error.
89
+ * client would POST to /v1/host-bash-result, /v1/host-file-result, or
90
+ * /v1/host-cu-result after completing the operation, get a 404, and the
91
+ * proxy timer would fire with a spurious timeout error.
91
92
  */
92
93
  export function removeBySession(session: Session): void {
93
94
  for (const [requestId, interaction] of pending) {
94
95
  if (
95
96
  interaction.session === session &&
96
97
  interaction.kind !== "host_bash" &&
97
- interaction.kind !== "host_file"
98
+ interaction.kind !== "host_file" &&
99
+ interaction.kind !== "host_cu"
98
100
  ) {
99
101
  pending.delete(requestId);
100
102
  }
@@ -5,7 +5,6 @@
5
5
  import * as deliveryStatus from "../../memory/delivery-status.js";
6
6
  import { httpError } from "../http-errors.js";
7
7
  export {
8
- deliverAttachmentsOnly,
9
8
  type DeliverReplyOptions,
10
9
  deliverReplyViaCallback,
11
10
  } from "../channel-reply-delivery.js";