@vellumai/assistant 0.4.46 → 0.4.48

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 (194) hide show
  1. package/ARCHITECTURE.md +5 -5
  2. package/docs/architecture/security.md +5 -5
  3. package/package.json +1 -1
  4. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  5. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  6. package/src/__tests__/channel-readiness-routes.test.ts +20 -19
  7. package/src/__tests__/cli.test.ts +23 -0
  8. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  9. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  10. package/src/__tests__/credential-broker.test.ts +2 -1
  11. package/src/__tests__/credential-metadata-store.test.ts +240 -18
  12. package/src/__tests__/credential-resolve.test.ts +5 -4
  13. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  14. package/src/__tests__/credential-security-invariants.test.ts +104 -6
  15. package/src/__tests__/credential-vault-unit.test.ts +22 -20
  16. package/src/__tests__/credential-vault.test.ts +284 -12
  17. package/src/__tests__/credentials-cli.test.ts +11 -6
  18. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  19. package/src/__tests__/gemini-image-service.test.ts +75 -45
  20. package/src/__tests__/gemini-provider.test.ts +9 -6
  21. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  22. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  23. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  24. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  25. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  26. package/src/__tests__/integration-status.test.ts +53 -21
  27. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  28. package/src/__tests__/media-generate-image.test.ts +63 -2
  29. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  30. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  31. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  32. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  33. package/src/__tests__/schema-transforms.test.ts +226 -0
  34. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  35. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  36. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  37. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  38. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/skills.test.ts +0 -9
  41. package/src/__tests__/slack-channel-config.test.ts +9 -8
  42. package/src/__tests__/slack-share-routes.test.ts +11 -6
  43. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  44. package/src/__tests__/twilio-config.test.ts +2 -1
  45. package/src/__tests__/twilio-provider.test.ts +4 -2
  46. package/src/__tests__/twilio-routes.test.ts +5 -4
  47. package/src/calls/call-domain.ts +7 -4
  48. package/src/calls/twilio-config.ts +2 -1
  49. package/src/calls/twilio-provider.ts +2 -1
  50. package/src/calls/twilio-rest.ts +2 -1
  51. package/src/cli/commands/browser-relay.ts +40 -15
  52. package/src/cli/commands/credentials.ts +9 -8
  53. package/src/cli/commands/oauth.ts +1 -1
  54. package/src/cli.ts +3 -2
  55. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  56. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  57. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  58. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  59. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  60. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  61. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  62. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  63. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  64. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  65. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  66. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  67. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  68. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  69. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  70. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  71. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  72. package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
  73. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  74. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  75. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  76. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  77. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  78. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  79. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  80. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  81. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  82. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  83. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  84. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  85. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  86. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  87. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  88. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  89. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  90. package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
  91. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  92. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  93. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  94. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  95. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  96. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  97. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  98. package/src/config/loader.ts +6 -0
  99. package/src/daemon/computer-use-session.ts +7 -1
  100. package/src/daemon/guardian-action-generators.ts +4 -5
  101. package/src/daemon/handlers/config-slack-channel.ts +37 -20
  102. package/src/daemon/handlers/config-telegram.ts +33 -20
  103. package/src/daemon/lifecycle.ts +9 -1
  104. package/src/daemon/message-types/integrations.ts +1 -0
  105. package/src/daemon/ride-shotgun-handler.ts +3 -1
  106. package/src/daemon/session-messaging.ts +3 -1
  107. package/src/daemon/session-tool-setup.ts +18 -2
  108. package/src/daemon/session.ts +1 -1
  109. package/src/email/providers/index.ts +2 -1
  110. package/src/instrument.ts +15 -1
  111. package/src/media/app-icon-generator.ts +30 -4
  112. package/src/media/avatar-router.ts +26 -3
  113. package/src/media/gemini-image-service.ts +28 -2
  114. package/src/memory/guardian-action-store.ts +1 -1
  115. package/src/memory/schema/guardian.ts +1 -1
  116. package/src/messaging/provider.ts +16 -10
  117. package/src/messaging/providers/gmail/adapter.ts +40 -23
  118. package/src/messaging/providers/gmail/client.ts +203 -122
  119. package/src/messaging/providers/gmail/people-client.ts +26 -18
  120. package/src/messaging/providers/slack/adapter.ts +29 -19
  121. package/src/messaging/providers/slack/client.ts +265 -78
  122. package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
  123. package/src/messaging/providers/whatsapp/adapter.ts +6 -3
  124. package/src/messaging/registry.ts +2 -1
  125. package/src/oauth/byo-connection.test.ts +436 -0
  126. package/src/oauth/byo-connection.ts +112 -0
  127. package/src/oauth/connect-orchestrator.ts +27 -0
  128. package/src/oauth/connection-resolver.ts +34 -0
  129. package/src/oauth/connection.ts +38 -0
  130. package/src/oauth/platform-connection.test.ts +163 -0
  131. package/src/oauth/platform-connection.ts +110 -0
  132. package/src/oauth/provider-base-urls.ts +21 -0
  133. package/src/oauth/provider-profiles.ts +1 -1
  134. package/src/oauth/token-persistence.ts +20 -20
  135. package/src/permissions/checker.ts +5 -1
  136. package/src/prompts/system-prompt.ts +49 -12
  137. package/src/providers/gemini/client.ts +15 -6
  138. package/src/providers/managed-proxy/constants.ts +2 -2
  139. package/src/providers/managed-proxy/context.ts +5 -1
  140. package/src/providers/ratelimit.ts +17 -0
  141. package/src/providers/registry.ts +2 -2
  142. package/src/runtime/AGENTS.md +17 -0
  143. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  144. package/src/runtime/channel-readiness-service.ts +168 -195
  145. package/src/runtime/channel-readiness-types.ts +4 -0
  146. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  147. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  148. package/src/runtime/guardian-action-message-composer.ts +3 -23
  149. package/src/runtime/http-server.ts +9 -4
  150. package/src/runtime/http-types.ts +0 -1
  151. package/src/runtime/middleware/rate-limiter.ts +74 -20
  152. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  153. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  154. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  155. package/src/runtime/routes/integrations/slack/share.ts +3 -2
  156. package/src/runtime/routes/integrations/twilio.ts +6 -5
  157. package/src/runtime/routes/secret-routes.ts +3 -2
  158. package/src/runtime/routes/settings-routes.ts +75 -17
  159. package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
  160. package/src/runtime/telegram-streaming-delivery.ts +11 -1
  161. package/src/schedule/integration-status.ts +5 -4
  162. package/src/security/credential-key.ts +170 -0
  163. package/src/security/token-manager.ts +36 -7
  164. package/src/tools/apps/definitions.ts +0 -5
  165. package/src/tools/assets/materialize.ts +0 -5
  166. package/src/tools/assets/search.ts +0 -5
  167. package/src/tools/browser/headless-browser.ts +1 -67
  168. package/src/tools/claude-code/claude-code.ts +0 -5
  169. package/src/tools/computer-use/request-computer-control.ts +0 -5
  170. package/src/tools/credentials/broker.ts +6 -4
  171. package/src/tools/credentials/metadata-store.ts +72 -20
  172. package/src/tools/credentials/resolve.ts +2 -1
  173. package/src/tools/credentials/vault.ts +77 -16
  174. package/src/tools/filesystem/edit.ts +1 -6
  175. package/src/tools/filesystem/read.ts +0 -5
  176. package/src/tools/filesystem/write.ts +1 -6
  177. package/src/tools/host-filesystem/edit.ts +1 -6
  178. package/src/tools/host-filesystem/read.ts +1 -6
  179. package/src/tools/host-filesystem/write.ts +1 -6
  180. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  181. package/src/tools/memory/definitions.ts +0 -5
  182. package/src/tools/network/web-fetch.ts +0 -5
  183. package/src/tools/network/web-search.ts +0 -5
  184. package/src/tools/schema-transforms.ts +99 -0
  185. package/src/tools/skills/load.ts +0 -5
  186. package/src/tools/swarm/delegate.ts +0 -5
  187. package/src/tools/system/avatar-generator.ts +0 -5
  188. package/src/tools/ui-surface/definitions.ts +0 -15
  189. package/src/tools/watch/screen-watch.ts +0 -5
  190. package/src/version.ts +10 -0
  191. package/src/watcher/providers/github.ts +51 -52
  192. package/src/watcher/providers/gmail.ts +88 -80
  193. package/src/watcher/providers/google-calendar.ts +93 -86
  194. package/src/watcher/providers/linear.ts +87 -93
@@ -16,8 +16,8 @@ You are a Google Calendar assistant with full access to the user's calendar. Use
16
16
  Before using any Calendar tool, verify that Google Calendar is connected by attempting a lightweight call (e.g., `calendar_list_events` with a narrow date range). If the call fails with a token/authorization error:
17
17
 
18
18
  1. **Do NOT call `credential_store oauth2_connect` yourself.** You do not have valid OAuth client credentials, and fabricating a client_id will cause a "401: invalid_client" error from Google.
19
- 2. Instead, load the **google-oauth-setup** skill, which walks the user through creating real credentials in Google Cloud Console:
20
- - Call `skill_load` with `skill: "google-oauth-setup"` to load the dependency skill.
19
+ 2. Instead, load the **google-oauth-applescript** skill, which walks the user through creating real credentials in Google Cloud Console:
20
+ - Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
21
21
  3. Tell the user: _"Google Calendar isn't connected yet. I've loaded a setup guide that will walk you through connecting your Google account — it only takes a couple of minutes."_
22
22
 
23
23
  ## Capabilities
@@ -1,3 +1,5 @@
1
+ import type { OAuthConnection } from "../../../oauth/connection.js";
2
+ import { GOOGLE_CALENDAR_BASE_URL } from "../../../oauth/provider-base-urls.js";
1
3
  import type {
2
4
  CalendarEvent,
3
5
  CalendarEventsListResponse,
@@ -6,8 +8,6 @@ import type {
6
8
  FreeBusyResponse,
7
9
  } from "./types.js";
8
10
 
9
- const CALENDAR_API_BASE = "https://www.googleapis.com/calendar/v3";
10
-
11
11
  export class CalendarApiError extends Error {
12
12
  constructor(
13
13
  public readonly status: number,
@@ -20,39 +20,80 @@ export class CalendarApiError extends Error {
20
20
  }
21
21
 
22
22
  async function request<T>(
23
- token: string,
23
+ connection: OAuthConnection,
24
24
  path: string,
25
25
  options?: RequestInit,
26
26
  ): Promise<T> {
27
- const url = `${CALENDAR_API_BASE}${path}`;
28
- const resp = await fetch(url, {
29
- ...options,
27
+ const method = (options?.method ?? "GET").toUpperCase();
28
+
29
+ // Extract non-auth headers
30
+ let extraHeaders: Record<string, string> | undefined;
31
+ if (options?.headers) {
32
+ const raw = options.headers;
33
+ const result: Record<string, string> = {};
34
+ if (raw instanceof Headers) {
35
+ raw.forEach((v, k) => {
36
+ if (k.toLowerCase() !== "authorization") result[k] = v;
37
+ });
38
+ } else if (Array.isArray(raw)) {
39
+ for (const [k, v] of raw) {
40
+ if (k.toLowerCase() !== "authorization") result[k] = v;
41
+ }
42
+ } else {
43
+ for (const [k, v] of Object.entries(raw)) {
44
+ if (k.toLowerCase() !== "authorization" && v !== undefined)
45
+ result[k] = v;
46
+ }
47
+ }
48
+ if (Object.keys(result).length > 0) extraHeaders = result;
49
+ }
50
+
51
+ // Extract body
52
+ let reqBody: unknown | undefined;
53
+ if (options?.body) {
54
+ if (typeof options.body === "string") {
55
+ try {
56
+ reqBody = JSON.parse(options.body);
57
+ } catch {
58
+ reqBody = options.body;
59
+ }
60
+ } else {
61
+ reqBody = options.body;
62
+ }
63
+ }
64
+
65
+ const resp = await connection.request({
66
+ method,
67
+ path,
68
+ baseUrl: GOOGLE_CALENDAR_BASE_URL,
30
69
  headers: {
31
- Authorization: `Bearer ${token}`,
32
70
  "Content-Type": "application/json",
33
- ...options?.headers,
71
+ ...extraHeaders,
34
72
  },
73
+ body: reqBody,
35
74
  });
36
- if (!resp.ok) {
37
- const body = await resp.text().catch(() => "");
75
+
76
+ if (resp.status < 200 || resp.status >= 300) {
77
+ const bodyStr =
78
+ typeof resp.body === "string"
79
+ ? resp.body
80
+ : JSON.stringify(resp.body ?? "");
38
81
  throw new CalendarApiError(
39
82
  resp.status,
40
- resp.statusText,
41
- `Calendar API ${resp.status}: ${body}`,
83
+ "",
84
+ `Calendar API ${resp.status}: ${bodyStr}`,
42
85
  );
43
86
  }
44
- const contentLength = resp.headers.get("content-length");
45
- if (resp.status === 204 || contentLength === "0") {
87
+
88
+ if (resp.status === 204 || resp.body === undefined) {
46
89
  return undefined as T;
47
90
  }
48
- const text = await resp.text();
49
- if (!text) return undefined as T;
50
- return JSON.parse(text) as T;
91
+ return resp.body as T;
51
92
  }
52
93
 
53
94
  /** List events from a calendar. */
54
95
  export async function listEvents(
55
- token: string,
96
+ connection: OAuthConnection,
56
97
  calendarId = "primary",
57
98
  options?: {
58
99
  timeMin?: string;
@@ -86,19 +127,19 @@ export async function listEvents(
86
127
  if (options?.syncToken) params.set("syncToken", options.syncToken);
87
128
 
88
129
  return request<CalendarEventsListResponse>(
89
- token,
130
+ connection,
90
131
  `/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
91
132
  );
92
133
  }
93
134
 
94
135
  /** Get a single event by ID. */
95
136
  export async function getEvent(
96
- token: string,
137
+ connection: OAuthConnection,
97
138
  eventId: string,
98
139
  calendarId = "primary",
99
140
  ): Promise<CalendarEvent> {
100
141
  return request<CalendarEvent>(
101
- token,
142
+ connection,
102
143
  `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(
103
144
  eventId,
104
145
  )}`,
@@ -107,7 +148,7 @@ export async function getEvent(
107
148
 
108
149
  /** Create a new event. */
109
150
  export async function createEvent(
110
- token: string,
151
+ connection: OAuthConnection,
111
152
  event: {
112
153
  summary: string;
113
154
  start: { dateTime?: string; date?: string; timeZone?: string };
@@ -121,7 +162,7 @@ export async function createEvent(
121
162
  ): Promise<CalendarEvent> {
122
163
  const params = new URLSearchParams({ sendUpdates });
123
164
  return request<CalendarEvent>(
124
- token,
165
+ connection,
125
166
  `/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
126
167
  {
127
168
  method: "POST",
@@ -132,7 +173,7 @@ export async function createEvent(
132
173
 
133
174
  /** Update an event (patch). */
134
175
  export async function patchEvent(
135
- token: string,
176
+ connection: OAuthConnection,
136
177
  eventId: string,
137
178
  updates: Partial<{
138
179
  summary: string;
@@ -147,7 +188,7 @@ export async function patchEvent(
147
188
  ): Promise<CalendarEvent> {
148
189
  const params = new URLSearchParams({ sendUpdates });
149
190
  return request<CalendarEvent>(
150
- token,
191
+ connection,
151
192
  `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(
152
193
  eventId,
153
194
  )}?${params}`,
@@ -160,10 +201,10 @@ export async function patchEvent(
160
201
 
161
202
  /** Query free/busy information. */
162
203
  export async function freeBusy(
163
- token: string,
204
+ connection: OAuthConnection,
164
205
  query: FreeBusyRequest,
165
206
  ): Promise<FreeBusyResponse> {
166
- return request<FreeBusyResponse>(token, "/freeBusy", {
207
+ return request<FreeBusyResponse>(connection, "/freeBusy", {
167
208
  method: "POST",
168
209
  body: JSON.stringify(query),
169
210
  });
@@ -171,7 +212,7 @@ export async function freeBusy(
171
212
 
172
213
  /** List calendars the user has access to. */
173
214
  export async function listCalendars(
174
- token: string,
215
+ connection: OAuthConnection,
175
216
  ): Promise<CalendarListResponse> {
176
- return request<CalendarListResponse>(token, "/users/me/calendarList");
217
+ return request<CalendarListResponse>(connection, "/users/me/calendarList");
177
218
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
5
  import * as calendar from "../calendar-client.js";
6
- import { ok, withCalendarToken } from "./shared.js";
6
+ import { getCalendarConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -14,14 +14,13 @@ export async function run(
14
14
  const calendarIds = (input.calendar_ids as string[]) ?? ["primary"];
15
15
  const timezone = input.timezone as string | undefined;
16
16
 
17
- return withCalendarToken(async (token) => {
18
- const result = await calendar.freeBusy(token, {
19
- timeMin,
20
- timeMax,
21
- timeZone: timezone,
22
- items: calendarIds.map((id) => ({ id })),
23
- });
24
-
25
- return ok(JSON.stringify(result, null, 2));
17
+ const connection = getCalendarConnection();
18
+ const result = await calendar.freeBusy(connection, {
19
+ timeMin,
20
+ timeMax,
21
+ timeZone: timezone,
22
+ items: calendarIds.map((id) => ({ id })),
26
23
  });
24
+
25
+ return ok(JSON.stringify(result, null, 2));
27
26
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
5
  import * as calendar from "../calendar-client.js";
6
- import { ok, withCalendarToken } from "./shared.js";
6
+ import { getCalendarConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -40,9 +40,8 @@ export async function run(
40
40
  eventBody.attendees = attendees.map((email) => ({ email }));
41
41
  }
42
42
 
43
- return withCalendarToken(async (token) => {
44
- const event = await calendar.createEvent(token, eventBody, calendarId);
45
- const link = event.htmlLink ? ` View it here: ${event.htmlLink}` : "";
46
- return ok(`Event created (ID: ${event.id}).${link}`);
47
- });
43
+ const connection = getCalendarConnection();
44
+ const event = await calendar.createEvent(connection, eventBody, calendarId);
45
+ const link = event.htmlLink ? ` View it here: ${event.htmlLink}` : "";
46
+ return ok(`Event created (ID: ${event.id}).${link}`);
48
47
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
5
  import * as calendar from "../calendar-client.js";
6
- import { ok, withCalendarToken } from "./shared.js";
6
+ import { getCalendarConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -12,8 +12,7 @@ export async function run(
12
12
  const eventId = input.event_id as string;
13
13
  const calendarId = (input.calendar_id as string) ?? "primary";
14
14
 
15
- return withCalendarToken(async (token) => {
16
- const event = await calendar.getEvent(token, eventId, calendarId);
17
- return ok(JSON.stringify(event, null, 2));
18
- });
15
+ const connection = getCalendarConnection();
16
+ const event = await calendar.getEvent(connection, eventId, calendarId);
17
+ return ok(JSON.stringify(event, null, 2));
19
18
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
5
  import * as calendar from "../calendar-client.js";
6
- import { ok, withCalendarToken } from "./shared.js";
6
+ import { getCalendarConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -17,20 +17,19 @@ export async function run(
17
17
  const singleEvents = (input.single_events as boolean) ?? true;
18
18
  const orderBy = input.order_by as "startTime" | "updated" | undefined;
19
19
 
20
- return withCalendarToken(async (token) => {
21
- const result = await calendar.listEvents(token, calendarId, {
22
- timeMin,
23
- timeMax,
24
- maxResults,
25
- query,
26
- singleEvents,
27
- orderBy,
28
- });
20
+ const connection = getCalendarConnection();
21
+ const result = await calendar.listEvents(connection, calendarId, {
22
+ timeMin,
23
+ timeMax,
24
+ maxResults,
25
+ query,
26
+ singleEvents,
27
+ orderBy,
28
+ });
29
29
 
30
- if (!result.items?.length) {
31
- return ok("No events found in the specified time range.");
32
- }
30
+ if (!result.items?.length) {
31
+ return ok("No events found in the specified time range.");
32
+ }
33
33
 
34
- return ok(JSON.stringify(result, null, 2));
35
- });
34
+ return ok(JSON.stringify(result, null, 2));
36
35
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
5
  import * as calendar from "../calendar-client.js";
6
- import { ok, withCalendarToken } from "./shared.js";
6
+ import { getCalendarConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -13,45 +13,45 @@ export async function run(
13
13
  const response = input.response as "accepted" | "declined" | "tentative";
14
14
  const calendarId = (input.calendar_id as string) ?? "primary";
15
15
 
16
- return withCalendarToken(async (token) => {
17
- // First get the event to find the user's attendee entry
18
- const event = await calendar.getEvent(token, eventId, calendarId);
19
- const selfAttendee = event.attendees?.find((a) => a.self);
16
+ const connection = getCalendarConnection();
20
17
 
21
- if (!selfAttendee) {
22
- // If the user is the organizer and not in the attendees list,
23
- // they don't need to RSVP
24
- if (event.organizer?.self) {
25
- return ok("You are the organizer of this event. No RSVP needed.");
26
- }
27
- return ok(
28
- "Could not find your attendee entry for this event. You may not be invited.",
29
- );
30
- }
18
+ // First get the event to find the user's attendee entry
19
+ const event = await calendar.getEvent(connection, eventId, calendarId);
20
+ const selfAttendee = event.attendees?.find((a) => a.self);
31
21
 
32
- // Update the attendee's response status
33
- const updatedAttendees = event.attendees!.map((a) =>
34
- a.self ? { ...a, responseStatus: response } : a,
22
+ if (!selfAttendee) {
23
+ // If the user is the organizer and not in the attendees list,
24
+ // they don't need to RSVP
25
+ if (event.organizer?.self) {
26
+ return ok("You are the organizer of this event. No RSVP needed.");
27
+ }
28
+ return ok(
29
+ "Could not find your attendee entry for this event. You may not be invited.",
35
30
  );
31
+ }
36
32
 
37
- await calendar.patchEvent(
38
- token,
39
- eventId,
40
- {
41
- attendees: updatedAttendees as Array<{
42
- email: string;
43
- responseStatus?: string;
44
- }>,
45
- },
46
- calendarId,
47
- );
33
+ // Update the attendee's response status
34
+ const updatedAttendees = event.attendees!.map((a) =>
35
+ a.self ? { ...a, responseStatus: response } : a,
36
+ );
37
+
38
+ await calendar.patchEvent(
39
+ connection,
40
+ eventId,
41
+ {
42
+ attendees: updatedAttendees as Array<{
43
+ email: string;
44
+ responseStatus?: string;
45
+ }>,
46
+ },
47
+ calendarId,
48
+ );
48
49
 
49
- const responseLabel =
50
- response === "accepted"
51
- ? "Accepted"
52
- : response === "declined"
53
- ? "Declined"
54
- : "Tentatively accepted";
55
- return ok(`${responseLabel} the event "${event.summary ?? eventId}".`);
56
- });
50
+ const responseLabel =
51
+ response === "accepted"
52
+ ? "Accepted"
53
+ : response === "declined"
54
+ ? "Declined"
55
+ : "Tentatively accepted";
56
+ return ok(`${responseLabel} the event "${event.summary ?? eventId}".`);
57
57
  }
@@ -1,20 +1,15 @@
1
- import { withValidToken } from "../../../../security/token-manager.js";
1
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
2
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
2
3
  import type { ToolExecutionResult } from "../../../../tools/types.js";
3
4
 
4
5
  export function ok(content: string): ToolExecutionResult {
5
6
  return { content, isError: false };
6
7
  }
7
8
 
8
- export function err(message: string): ToolExecutionResult {
9
- return { content: message, isError: true };
10
- }
11
-
12
9
  /**
13
10
  * Calendar uses the same OAuth credential service as Gmail since both
14
11
  * scopes are granted in a single OAuth consent flow.
15
12
  */
16
- export async function withCalendarToken<T>(
17
- fn: (token: string) => Promise<T>,
18
- ): Promise<T> {
19
- return withValidToken("integration:gmail", fn);
13
+ export function getCalendarConnection(): OAuthConnection {
14
+ return resolveOAuthConnection("integration:gmail");
20
15
  }
@@ -5,10 +5,15 @@ import {
5
5
  } from "../../../../daemon/media-visibility-policy.js";
6
6
  import {
7
7
  generateImage,
8
+ type ImageGenCredentials,
8
9
  mapGeminiError,
9
10
  } from "../../../../media/gemini-image-service.js";
10
11
  import { getAttachmentsByIds } from "../../../../memory/attachments-store.js";
11
12
  import { getConversationThreadType } from "../../../../memory/conversation-crud.js";
13
+ import {
14
+ buildManagedBaseUrl,
15
+ resolveManagedProxyContext,
16
+ } from "../../../../providers/managed-proxy/context.js";
12
17
  import type { ImageContent } from "../../../../providers/types.js";
13
18
  import { getAttachmentSourceConversations } from "../../../../tools/assets/search.js";
14
19
  import type {
@@ -46,9 +51,25 @@ export async function run(
46
51
  context: ToolContext,
47
52
  ): Promise<ToolExecutionResult> {
48
53
  const config = getConfig();
49
- const apiKey = config.apiKeys.gemini;
54
+ const apiKey = config.apiKeys.gemini ?? process.env.GEMINI_API_KEY;
55
+
56
+ // Resolve credentials: prefer direct API key, fall back to managed proxy
57
+ let credentials: ImageGenCredentials | undefined;
58
+ if (apiKey) {
59
+ credentials = { type: "direct", apiKey };
60
+ } else {
61
+ const managedBaseUrl = buildManagedBaseUrl("vertex");
62
+ if (managedBaseUrl) {
63
+ const ctx = resolveManagedProxyContext();
64
+ credentials = {
65
+ type: "managed-proxy",
66
+ assistantApiKey: ctx.assistantApiKey,
67
+ baseUrl: managedBaseUrl,
68
+ };
69
+ }
70
+ }
50
71
 
51
- if (!apiKey) {
72
+ if (!credentials) {
52
73
  return {
53
74
  content:
54
75
  "No Gemini API key configured. Please set your Gemini API key to use image generation.",
@@ -95,7 +116,7 @@ export async function run(
95
116
  }
96
117
 
97
118
  try {
98
- const result = await generateImage(apiKey, {
119
+ const result = await generateImage(credentials, {
99
120
  prompt,
100
121
  mode,
101
122
  sourceImages,
@@ -33,9 +33,9 @@ When a platform is connected (auth test succeeds), always use the messaging API
33
33
 
34
34
  Before using any messaging tool, verify that the platform is connected by calling `messaging_auth_test` with the appropriate `platform` parameter. If the call fails with a token/authorization error, follow the steps below.
35
35
 
36
- ### Public Ingress (required for all platforms)
36
+ ### Public Ingress (required for Slack and Telegram)
37
37
 
38
- Gmail, Slack, and Telegram setup all require a publicly reachable URL for OAuth callbacks or webhook delivery. The **public-ingress** skill handles ngrok tunnel setup and persists the URL as `ingress.publicBaseUrl`. Each setup skill below declares `public-ingress` as a dependency and will prompt you to run it if `ingress.publicBaseUrl` is not configured.
38
+ Slack and Telegram setup require a publicly reachable URL for OAuth callbacks or webhook delivery. The **public-ingress** skill handles ngrok tunnel setup and persists the URL as `ingress.publicBaseUrl`. Gmail on the desktop app uses a loopback callback and does not require public ingress; the channel path (Path B in the google-oauth-applescript skill) handles public ingress internally when needed.
39
39
 
40
40
  ### Email Connection Flow
41
41
 
@@ -48,11 +48,11 @@ When the user asks to "connect my email", "set up email", "manage my email", or
48
48
  ### Gmail
49
49
 
50
50
  1. **Try connecting directly first.** Call `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. The tool auto-fills Google's OAuth endpoints and looks up any previously stored client credentials — so this single call may be all that's needed.
51
- 2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-setup** skill (which depends on **public-ingress** for the redirect URI):
52
- - Call `skill_load` with `skill: "google-oauth-setup"` to load the dependency skill.
51
+ 2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-applescript** skill:
52
+ - Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
53
53
  - Tell the user Gmail isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
54
54
  - **message:** "Ready to set up Gmail?"
55
- - **detail:** "I'll open a browser where you sign in to Google, then automate everything else — creating a project, enabling APIs, and connecting your account. Takes 2-3 minutes and you can watch in the browser preview panel."
55
+ - **detail:** "I'll open a few pages in your browser and walk you through setting up Google Cloud credentials — creating a project, enabling APIs, and connecting your account. Takes about 5 minutes."
56
56
  - **confirmLabel:** "Get Started"
57
57
  - **cancelLabel:** "Not Now"
58
58
  - If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
@@ -95,7 +95,7 @@ The guardian-verify-setup skill handles the full outbound verification flow for
95
95
  When a messaging tool fails with a token or authorization error:
96
96
 
97
97
  1. **Try to reconnect silently.** Call `credential_store` with `action: "oauth2_connect"` and the appropriate `service`. This often resolves expired tokens automatically.
98
- 2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected — let me set that up") and immediately follow the connection setup flow for that platform (e.g., install and load **google-oauth-setup** for Gmail). The user came to you to get something done, not to troubleshoot OAuth — make it seamless.
98
+ 2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected — let me set that up") and immediately follow the connection setup flow for that platform (e.g., install and load **google-oauth-applescript** for Gmail). The user came to you to get something done, not to troubleshoot OAuth — make it seamless.
99
99
  3. **Never try alternative approaches.** Don't use bash, curl, browser automation, or any workaround. If the messaging tools can't do it, the reconnection flow is the answer.
100
100
  4. **Never expose error details.** The user doesn't need to see error messages about tokens, OAuth, or API failures. Translate errors into plain language.
101
101