@chibitek/chibi 0.1.173 → 0.1.175

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 (83) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-DaA9WpZw.js → abnfDiagram-O67JEVCF-CjXTIXBz.js} +1 -1
  2. package/dist/assets/{arc-DJ4Kdvv6.js → arc-1QgG2iO9.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-B975BUTv.js → architectureDiagram-NJMV4G6O-CzCo4hRJ.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-CXOOfLy7.js → blockDiagram-BEXU5L5S-DhrHIUjL.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-CorqQ94E.js → c4Diagram-YGBWAQC7-gOjFoY7Z.js} +1 -1
  6. package/dist/assets/channel-DB0DMkPD.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-CPFHpKez.js → chunk-2Q5K7J3B-B9bsMZTt.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-CrzBuB3A.js → chunk-3FUC2YCW-D6qIOfLe.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-DXJTBdvO.js → chunk-5VM5RSS4-DCixOiOi.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-B0TxALG4.js → chunk-GTNCS2PH-Bh52Az-G.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-BL44uVOP.js → chunk-GWA4HPMP-Dbs4EZ88.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-CxSklH7P.js → chunk-JWPE2WC7-jReoeHz2.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-_Lb-D8AY.js → chunk-NETBCI7D-CF3H0lZo.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-Dz5jp0xb.js → chunk-WEXAMYUT-DetCJmJX.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-C-cd9N0r.js → chunk-XXDRQBXY-CigsN-8U.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-DtQ1781-.js → classDiagram-v2-NBCMYWYE-D8ZcMUWM.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-CHzWXNdH.js → cose-bilkent-JH36ORCC-BnqFoYIa.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-lHbKcf1S.js → cynefin-OW5HDTMX-DWSSGPqP.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-BxY6MCKD.js → cynefinDiagram-VND7K2PF-Doo-ZQ5E.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-C2SBw-Mo.js → dagre-6A5THRUB-Bje_N-ue.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-BwIAlmAU.js → diagram-22UHCM2B-osEfxfGj.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-i6fBkckD.js → diagram-3UASUU5V-D0ybINw1.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-DwedCbB_.js → diagram-ATOU4E4O-DwS59waF.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-BISF-AQC.js → diagram-CDSNMT55-stDTabsA.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-ImgdRRxp.js → diagram-MLGK6HIB-COdtBBj4.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-CxlAwPCS.js → diagram-MPIPVDR6-S32r0-4f.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-sVJkymKa.js → ebnfDiagram-ZINNZB2B-CZxbMOzV.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-DU1vyX4w.js → elk-276RUBZZ-PzYOam0u.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-CnCmL1Ve.js → erDiagram-OPXOYQCR-D0HBOTqF.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-DME2uLJf.js → flowDiagram-KWPJA3E3-1d8rftXL.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-CJQNW9ts.js → ganttDiagram-FUAMR5RP-CWn83VOd.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-D88_Y9tg.js → gitGraphDiagram-X574FWY7-CkLyRxvr.js} +1 -1
  33. package/dist/assets/{index-U9LN4GhF.js → index-BcBNl1Sc.js} +175 -175
  34. package/dist/assets/index-D17LLrYN.css +1 -0
  35. package/dist/assets/{index-ChmZZe7i.js → index-DfdDXEfq.js} +1 -1
  36. package/dist/assets/{infoDiagram-VRGFBTTK-CxWEf3NI.js → infoDiagram-VRGFBTTK-BmAsjOaa.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-OU5B5YK6-B_YoNVjF.js → ishikawaDiagram-OU5B5YK6-B9d57hp_.js} +1 -1
  38. package/dist/assets/{journeyDiagram-ZHPQQLJL-ZYuBE2Bm.js → journeyDiagram-ZHPQQLJL-II3Kse3X.js} +1 -1
  39. package/dist/assets/{kanban-definition-PNTS6WVX-jmY_M4Qf.js → kanban-definition-PNTS6WVX-DtovCoCx.js} +1 -1
  40. package/dist/assets/{linear-kKUF6ayN.js → linear-BnH8vI1-.js} +1 -1
  41. package/dist/assets/{mermaid.core-DC5051S_.js → mermaid.core-BExpGmRM.js} +5 -5
  42. package/dist/assets/{mindmap-definition-NLK3R4M7-C5knfygJ.js → mindmap-definition-NLK3R4M7-CSGJyaor.js} +1 -1
  43. package/dist/assets/{pegDiagram-GJSIUBJH-Ex6iedL2.js → pegDiagram-GJSIUBJH-BFgKWA7i.js} +1 -1
  44. package/dist/assets/{pieDiagram-5QR66LMP-Dv38Ox0D.js → pieDiagram-5QR66LMP-Dl3FAvDB.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-O4NWA36T-CwOEor8N.js → quadrantDiagram-O4NWA36T-DafRWmMB.js} +1 -1
  46. package/dist/assets/{railroadDiagram-XR7U4H2S-DVgqz5MR.js → railroadDiagram-XR7U4H2S-CHJ7SyyJ.js} +1 -1
  47. package/dist/assets/{requirementDiagram-PLB6GJNP-BxiuYo52.js → requirementDiagram-PLB6GJNP-DJzpaN8z.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-IPEJSGJF--vq_1flP.js → sankeyDiagram-IPEJSGJF-BKqi0qaK.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-PO4LG4MO-9hLpfPA6.js → sequenceDiagram-PO4LG4MO-Bdc2LKBx.js} +1 -1
  50. package/dist/assets/{sizeCapture-INFHLROL-BeGMkj9s.js → sizeCapture-INFHLROL-CIuw2Jav.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-GCMORJYK-BnNOMugb.js → stateDiagram-v2-GCMORJYK-CocMnCmt.js} +1 -1
  52. package/dist/assets/{swimlanes-2SLR337P-C1YG6Jq-.js → swimlanes-2SLR337P-D7Kw4SfT.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-TC7HE7FX-Bq7WUTaZ.js +8 -0
  54. package/dist/assets/{timeline-definition-EJHVYXUP-Co7OtwGA.js → timeline-definition-EJHVYXUP-DEXnhdui.js} +1 -1
  55. package/dist/assets/{usecaseDiagram-POWQR4AR-BYHFARv3.js → usecaseDiagram-POWQR4AR-CJTa0bWM.js} +1 -1
  56. package/dist/assets/{vennDiagram-UO4OBE2U-C-sdhL3h.js → vennDiagram-UO4OBE2U-CKphfio6.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-VNRHLVJA-wf8ZNrmQ.js → wardleyDiagram-VNRHLVJA-COQ5I4oG.js} +1 -1
  58. package/dist/assets/{xychartDiagram-PMCCYNJV-BjRlkVGv.js → xychartDiagram-PMCCYNJV-BqlgNUIm.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/dist-server/container-mcp.js +1 -0
  61. package/dist-server/index.js +1305 -747
  62. package/dist-server/openmausbot.js +3 -2
  63. package/dist-server/pair-cli.js +3 -2
  64. package/dist-server/server/about-me-sync.js +172 -0
  65. package/dist-server/server/config.js +1 -1
  66. package/dist-server/server/dictation.js +8 -0
  67. package/dist-server/server/drivers/claude.js +4 -4
  68. package/dist-server/server/icm-context.js +0 -22
  69. package/dist-server/server/index.js +26 -31
  70. package/dist-server/server/oauth-account-core.js +153 -0
  71. package/dist-server/server/oauth-account-tools.js +7 -348
  72. package/dist-server/server/oauth-calendar-tools.js +551 -0
  73. package/dist-server/server/paw/service.js +2 -1
  74. package/dist-server/server/paw/workspace.js +2 -1
  75. package/dist-server/server/vault/routes.js +20 -0
  76. package/dist-server/server/vault/store.js +24 -0
  77. package/dist-server/shared/oauth-providers.js +2 -1
  78. package/dist-server/shared/vault-csv.js +94 -0
  79. package/dist-server/vps-container-mcp.js +1 -0
  80. package/package.json +1 -1
  81. package/dist/assets/channel-sBYHnjxz.js +0 -1
  82. package/dist/assets/index-COwCaN7p.css +0 -1
  83. package/dist/assets/swimlanesDiagram-TC7HE7FX-9DghUs7S.js +0 -8
@@ -4,29 +4,15 @@
4
4
  // after the internal route has re-checked that this bot may use it.
5
5
  import { z } from "zod";
6
6
  import { OAUTH_PROVIDERS, } from "../shared/oauth-providers.js";
7
+ import { CREATES, GRAPH, MAX_DOWNLOAD_BYTES, READS, SENDS, ToolInputError, accountArg, addresses, base, bounded, call, graphRecipient, htmlToText, json, noNewlines, parse, recipient, } from "./oauth-account-core.js";
7
8
  import { familyGranted, missingFamilies } from "./oauth-accounts.js";
9
+ import { CALENDAR_RUNNERS, GOOGLE_CALENDAR_TOOLS, OUTLOOK_CALENDAR_TOOLS } from "./oauth-calendar-tools.js";
8
10
  import { officeKind, officeText } from "./office-text.js";
9
- const HTTP_TIMEOUT_MS = 30_000;
10
- const MAX_RESULT_CHARS = 48_000;
11
+ export { ToolInputError } from "./oauth-account-core.js";
12
+ export { graphDateTime } from "./oauth-calendar-tools.js";
11
13
  const MAX_BODY_CHARS = 20_000;
12
14
  const MAX_FILE_TEXT_CHARS = 40_000;
13
- const MAX_JSON_BYTES = 4 * 1024 * 1024;
14
- const MAX_DOWNLOAD_BYTES = 8 * 1024 * 1024;
15
- const READS = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
16
- const CREATES = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
17
- /** Sends mail or invitations to other people: cannot be taken back. */
18
- const SENDS = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
19
- const accountArg = { type: "string", description: "Email address of the account to use. Optional only when this bot has exactly one account of this provider." };
20
15
  const emailList = z.union([z.string(), z.array(z.string()).max(50)]);
21
- const EMAIL = /^[^\s@<>,;"]+@[^\s@<>,;"]+\.[^\s@<>,;"]+$/;
22
- const noNewlines = (value) => !/[\r\n]/.test(value);
23
- function isoInstant(value) {
24
- return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && Number.isFinite(Date.parse(value));
25
- }
26
- function isoDateOrTime(value) {
27
- return /^\d{4}-\d{2}-\d{2}$/.test(value) || /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?$/.test(value);
28
- }
29
- const base = z.object({ account: z.string().max(320).optional() });
30
16
  const searchArgs = base.extend({ q: z.string().min(1).max(500), max: z.number().int().min(1).max(25).optional() }).strict();
31
17
  const idArgs = (key) => base.extend({ [key]: z.string().min(1).max(512).regex(/^[A-Za-z0-9!_.=+\-/~:]+$/, "not a valid id") }).strict();
32
18
  const draftArgs = base.extend({
@@ -35,22 +21,6 @@ const draftArgs = base.extend({
35
21
  body: z.string().max(200_000),
36
22
  cc: emailList.optional(),
37
23
  }).strict();
38
- const listEventsArgs = base.extend({
39
- timeMin: z.string().refine(isoInstant, "timeMin must be an ISO 8601 date-time with a time zone, e.g. 2026-10-01T00:00:00Z"),
40
- timeMax: z.string().refine(isoInstant, "timeMax must be an ISO 8601 date-time with a time zone, e.g. 2026-10-08T00:00:00Z"),
41
- calendarId: z.string().min(1).max(320).optional(),
42
- max: z.number().int().min(1).max(100).optional(),
43
- }).strict();
44
- const createEventArgs = base.extend({
45
- summary: z.string().min(1).max(1_000).refine(noNewlines, "summary must be one line"),
46
- start: z.string().refine(isoDateOrTime, "start must be YYYY-MM-DD or an ISO 8601 date-time"),
47
- end: z.string().refine(isoDateOrTime, "end must be YYYY-MM-DD or an ISO 8601 date-time"),
48
- timeZone: z.string().min(1).max(64).regex(/^[A-Za-z0-9_+\-/ ]+$/).optional(),
49
- description: z.string().max(8_000).optional(),
50
- location: z.string().max(1_000).optional(),
51
- attendees: z.array(z.string()).max(50).optional(),
52
- calendarId: z.string().min(1).max(320).optional(),
53
- }).strict();
54
24
  const listAccountsArgs = z.object({}).strict();
55
25
  const searchSchema = (what) => ({
56
26
  type: "object",
@@ -71,50 +41,20 @@ const draftSchema = {
71
41
  required: ["to", "subject", "body"],
72
42
  additionalProperties: false,
73
43
  };
74
- const listEventsSchema = (withCalendar) => ({
75
- type: "object",
76
- properties: {
77
- account: accountArg,
78
- timeMin: { type: "string", description: "Start of the window, ISO 8601 with a time zone (e.g. 2026-10-01T00:00:00Z)." },
79
- timeMax: { type: "string", description: "End of the window, ISO 8601 with a time zone." },
80
- ...(withCalendar ? { calendarId: { type: "string", description: "Calendar id; defaults to the primary calendar." } } : {}),
81
- max: { type: "integer", minimum: 1, maximum: 100, description: "Most events to return (default 25)." },
82
- },
83
- required: ["timeMin", "timeMax"],
84
- additionalProperties: false,
85
- });
86
- const createEventSchema = (withCalendar) => ({
87
- type: "object",
88
- properties: {
89
- account: accountArg,
90
- summary: { type: "string", description: "Event title." },
91
- start: { type: "string", description: "YYYY-MM-DD for an all-day event, or an ISO 8601 date-time. Without an offset, pass timeZone." },
92
- end: { type: "string", description: "Same format as start. For all-day events this is the day after the last day." },
93
- timeZone: { type: "string", description: "IANA time zone such as America/Los_Angeles, used when start/end have no offset." },
94
- description: { type: "string" },
95
- location: { type: "string" },
96
- attendees: { type: "array", items: { type: "string" }, description: "Guest email addresses. They receive an invitation." },
97
- ...(withCalendar ? { calendarId: { type: "string", description: "Calendar id; defaults to the primary calendar." } } : {}),
98
- },
99
- required: ["summary", "start", "end"],
100
- additionalProperties: false,
101
- });
102
44
  export const ACCOUNT_TOOLS = [
103
45
  { name: "list_accounts", provider: null, description: "List the Google and Microsoft accounts this bot may use, by email.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, annotations: READS, args: listAccountsArgs },
104
46
  { name: "gmail_search", provider: "google", family: "gmail", description: "Search Gmail with Gmail search syntax (e.g. from:alex newer_than:7d). Returns message ids, senders, subjects, dates and snippets.", inputSchema: searchSchema("Gmail search query."), annotations: READS, args: searchArgs },
105
47
  { name: "gmail_read", provider: "google", family: "gmail", description: "Read one Gmail message by id: headers, plain-text body and attachment names.", inputSchema: idSchema("id", "Message id from gmail_search."), annotations: READS, args: idArgs("id") },
106
48
  { name: "gmail_create_draft", provider: "google", family: "gmailSend", description: "Create a plain-text Gmail draft. Nothing is sent until gmail_send_draft.", inputSchema: draftSchema, annotations: CREATES, args: draftArgs },
107
49
  { name: "gmail_send_draft", provider: "google", family: "gmailSend", description: "Send an existing Gmail draft by its draft id.", inputSchema: idSchema("draftId", "Draft id from gmail_create_draft."), annotations: SENDS, args: idArgs("draftId") },
108
- { name: "calendar_list_events", provider: "google", family: "calendar", description: "List Google Calendar events between two times.", inputSchema: listEventsSchema(true), annotations: READS, args: listEventsArgs },
109
- { name: "calendar_create_event", provider: "google", family: "calendar", description: "Create a Google Calendar event. Attendees receive an invitation.", inputSchema: createEventSchema(true), annotations: SENDS, args: createEventArgs },
50
+ ...GOOGLE_CALENDAR_TOOLS,
110
51
  { name: "drive_search", provider: "google", family: "drive", description: "Search Google Drive files by words in their name or content.", inputSchema: searchSchema("Words to look for."), annotations: READS, args: searchArgs },
111
52
  { name: "drive_read", provider: "google", family: "drive", description: "Read a Google Drive file by id: text for Docs, Sheets (CSV), Slides, Office and text files; details and a link otherwise.", inputSchema: idSchema("fileId", "File id from drive_search."), annotations: READS, args: idArgs("fileId") },
112
53
  { name: "outlook_search", provider: "microsoft", family: "outlook", description: "Search Outlook mail by words (sender, subject, body). Returns message ids, senders, subjects, dates and previews.", inputSchema: searchSchema("Words to search for."), annotations: READS, args: searchArgs },
113
54
  { name: "outlook_read", provider: "microsoft", family: "outlook", description: "Read one Outlook message by id: headers and plain-text body.", inputSchema: idSchema("id", "Message id from outlook_search."), annotations: READS, args: idArgs("id") },
114
55
  { name: "outlook_create_draft", provider: "microsoft", family: "outlook", description: "Create a plain-text Outlook draft. Nothing is sent until outlook_send_draft.", inputSchema: draftSchema, annotations: CREATES, args: draftArgs },
115
56
  { name: "outlook_send_draft", provider: "microsoft", family: "outlook", description: "Send an existing Outlook draft by its id.", inputSchema: idSchema("draftId", "Draft id from outlook_create_draft."), annotations: SENDS, args: idArgs("draftId") },
116
- { name: "outlook_calendar_list_events", provider: "microsoft", family: "outlookCalendar", description: "List Outlook calendar events between two times (times are returned in UTC).", inputSchema: listEventsSchema(false), annotations: READS, args: listEventsArgs.omit({ calendarId: true }) },
117
- { name: "outlook_calendar_create_event", provider: "microsoft", family: "outlookCalendar", description: "Create an Outlook calendar event. Attendees receive an invitation.", inputSchema: createEventSchema(false), annotations: SENDS, args: createEventArgs.omit({ calendarId: true }) },
57
+ ...OUTLOOK_CALENDAR_TOOLS,
118
58
  { name: "onedrive_search", provider: "microsoft", family: "onedrive", description: "Search OneDrive files by words in their name or content.", inputSchema: searchSchema("Words to look for."), annotations: READS, args: searchArgs },
119
59
  { name: "onedrive_read", provider: "microsoft", family: "onedrive", description: "Read a OneDrive file by id: text for small text and Office files, details and a link otherwise.", inputSchema: idSchema("fileId", "File id from onedrive_search."), annotations: READS, args: idArgs("fileId") },
120
60
  ];
@@ -129,11 +69,6 @@ export function accountToolList(providers) {
129
69
  export function isReadOnlyAccountTool(name) {
130
70
  return TOOL_BY_NAME.get(name)?.annotations.readOnlyHint === true;
131
71
  }
132
- export class ToolInputError extends Error {
133
- }
134
- function bounded(text, limit = MAX_RESULT_CHARS) {
135
- return text.length <= limit ? text : `${text.slice(0, limit)}\n[truncated: ask for less]`;
136
- }
137
72
  function describeIssues(error) {
138
73
  return error.issues.slice(0, 5).map((issue) => `${issue.path.join(".") || "arguments"}: ${issue.message}`).join("; ");
139
74
  }
@@ -154,134 +89,9 @@ export function resolveAccount(provider, allowed, requested) {
154
89
  return candidates[0];
155
90
  throw new ToolInputError(`Pass account: this bot has more than one ${label} account (${candidates.map((account) => account.email).join(", ")}).`);
156
91
  }
157
- function addresses(value, field) {
158
- if (value === undefined)
159
- return [];
160
- const list = (Array.isArray(value) ? value : value.split(",")).map((item) => item.trim()).filter(Boolean);
161
- if (list.length > 50)
162
- throw new ToolInputError(`${field}: at most 50 addresses`);
163
- for (const item of list) {
164
- if (!EMAIL.test(item))
165
- throw new ToolInputError(`${field}: "${item.slice(0, 80)}" is not an email address`);
166
- }
167
- return list;
168
- }
169
- class ProviderApiError extends Error {
170
- status;
171
- constructor(status, message) {
172
- super(message);
173
- this.status = status;
174
- }
175
- }
176
- async function readLimited(response, limit) {
177
- const declared = Number(response.headers.get("content-length") ?? "0");
178
- if (declared > limit) {
179
- await response.body?.cancel().catch(() => { });
180
- return null;
181
- }
182
- if (!response.body)
183
- return Buffer.alloc(0);
184
- const reader = response.body.getReader();
185
- const chunks = [];
186
- let size = 0;
187
- for (;;) {
188
- const { done, value } = await reader.read();
189
- if (done)
190
- break;
191
- size += value.byteLength;
192
- if (size > limit) {
193
- await reader.cancel().catch(() => { });
194
- return null;
195
- }
196
- chunks.push(value);
197
- }
198
- return Buffer.concat(chunks);
199
- }
200
- function providerErrorMessage(body, status) {
201
- const error = body?.error;
202
- const message = typeof error === "string"
203
- ? error
204
- : error && typeof error === "object" && typeof error.message === "string"
205
- ? error.message
206
- : "";
207
- return (message || `request failed with HTTP ${status}`).split(/\r?\n/)[0].slice(0, 300);
208
- }
209
- async function call(ctx, account, url, init) {
210
- const fetcher = ctx.fetch ?? fetch;
211
- const { expect, ...request } = init;
212
- for (let attempt = 0; attempt < 2; attempt += 1) {
213
- const token = await ctx.accessToken(account.id);
214
- const headers = new Headers(request.headers);
215
- headers.set("authorization", `Bearer ${token}`);
216
- if (!headers.has("accept") && expect === "json")
217
- headers.set("accept", "application/json");
218
- const response = await fetcher(url, { ...request, headers, signal: AbortSignal.timeout(HTTP_TIMEOUT_MS) });
219
- if (response.status === 401 && attempt === 0) {
220
- await response.body?.cancel().catch(() => { });
221
- ctx.invalidate(account.id);
222
- continue;
223
- }
224
- if (!response.ok) {
225
- const raw = await readLimited(response, 64 * 1024);
226
- let body = null;
227
- try {
228
- body = raw ? JSON.parse(raw.toString("utf8")) : null;
229
- }
230
- catch {
231
- body = null;
232
- }
233
- const message = providerErrorMessage(body, response.status);
234
- if (response.status === 403)
235
- throw new ProviderApiError(403, `${account.email} does not allow this: ${message}`);
236
- if (response.status === 404)
237
- throw new ProviderApiError(404, `Not found in ${account.email}: ${message}`);
238
- throw new ProviderApiError(response.status, `${OAUTH_PROVIDERS[account.provider].label} refused the request for ${account.email}: ${message}`);
239
- }
240
- if (expect === "none") {
241
- await response.body?.cancel().catch(() => { });
242
- return undefined;
243
- }
244
- const bytes = await readLimited(response, expect === "json" ? MAX_JSON_BYTES : MAX_DOWNLOAD_BYTES);
245
- if (expect === "bytes")
246
- return bytes;
247
- if (!bytes)
248
- throw new ProviderApiError(502, "The response was too large");
249
- try {
250
- return JSON.parse(bytes.toString("utf8"));
251
- }
252
- catch {
253
- throw new ProviderApiError(502, "The service returned an unreadable response");
254
- }
255
- }
256
- throw new ProviderApiError(401, `${account.email} needs to sign in again: open Settings → Accounts and connect it again.`);
257
- }
258
- function parse(schema, value, what) {
259
- const parsed = schema.safeParse(value);
260
- if (!parsed.success)
261
- throw new ProviderApiError(502, `Unexpected ${what} response`);
262
- return parsed.data;
263
- }
264
- function json(value) {
265
- return JSON.stringify(value, null, 1);
266
- }
267
92
  function decodeBase64Url(data) {
268
93
  return data ? Buffer.from(data, "base64url").toString("utf8") : "";
269
94
  }
270
- function htmlToText(html) {
271
- return html
272
- .replace(/<(script|style)[\s\S]*?<\/\1>/gi, "")
273
- .replace(/<br\s*\/?>/gi, "\n")
274
- .replace(/<\/(p|div|tr|li|h\d)>/gi, "\n")
275
- .replace(/<[^>]+>/g, "")
276
- .replace(/&nbsp;/g, " ")
277
- .replace(/&amp;/g, "&")
278
- .replace(/&lt;/g, "<")
279
- .replace(/&gt;/g, ">")
280
- .replace(/&quot;/g, '"')
281
- .replace(/&#39;/g, "'")
282
- .replace(/\n{3,}/g, "\n\n")
283
- .trim();
284
- }
285
95
  function encodeHeader(value) {
286
96
  return /^[\x20-\x7e]*$/.test(value) ? value : `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
287
97
  }
@@ -317,7 +127,6 @@ function readable(name, mimeType) {
317
127
  }
318
128
  // ── Google ────────────────────────────────────────────────────────────────
319
129
  const GMAIL = "https://gmail.googleapis.com/gmail/v1/users/me";
320
- const CALENDAR = "https://www.googleapis.com/calendar/v3";
321
130
  const DRIVE = "https://www.googleapis.com/drive/v3";
322
131
  const gmailPartSchema = z.lazy(() => z.object({
323
132
  mimeType: z.string().optional(),
@@ -405,69 +214,6 @@ async function gmailSendDraft(ctx, account, args) {
405
214
  const sent = parse(z.object({ id: z.string(), threadId: z.string().optional() }), await call(ctx, account, `${GMAIL}/drafts/send`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: args.draftId }), expect: "json" }), "Gmail send");
406
215
  return json({ account: account.email, sent: true, messageId: sent.id, threadId: sent.threadId });
407
216
  }
408
- const googleEventSchema = z.object({
409
- id: z.string(),
410
- summary: z.string().optional(),
411
- description: z.string().optional(),
412
- location: z.string().optional(),
413
- status: z.string().optional(),
414
- htmlLink: z.string().optional(),
415
- start: z.object({ dateTime: z.string().optional(), date: z.string().optional(), timeZone: z.string().optional() }).optional(),
416
- end: z.object({ dateTime: z.string().optional(), date: z.string().optional(), timeZone: z.string().optional() }).optional(),
417
- organizer: z.object({ email: z.string().optional() }).optional(),
418
- attendees: z.array(z.object({ email: z.string().optional(), responseStatus: z.string().optional() })).optional(),
419
- });
420
- function googleEvent(event) {
421
- return {
422
- id: event.id,
423
- summary: event.summary,
424
- start: event.start?.dateTime ?? event.start?.date,
425
- end: event.end?.dateTime ?? event.end?.date,
426
- location: event.location,
427
- organizer: event.organizer?.email,
428
- attendees: event.attendees?.map((attendee) => `${attendee.email ?? ""}${attendee.responseStatus ? ` (${attendee.responseStatus})` : ""}`),
429
- description: event.description ? bounded(event.description, 2_000) : undefined,
430
- link: event.htmlLink,
431
- };
432
- }
433
- async function calendarListEvents(ctx, account, args) {
434
- const calendarId = encodeURIComponent(args.calendarId ?? "primary");
435
- const query = new URLSearchParams({ timeMin: args.timeMin, timeMax: args.timeMax, singleEvents: "true", orderBy: "startTime", maxResults: String(args.max ?? 25) });
436
- const list = parse(z.object({ items: z.array(googleEventSchema).optional(), timeZone: z.string().optional() }), await call(ctx, account, `${CALENDAR}/calendars/${calendarId}/events?${query}`, { expect: "json" }), "Calendar");
437
- return json({ account: account.email, timeZone: list.timeZone, events: (list.items ?? []).map(googleEvent) });
438
- }
439
- function eventTime(value, timeZone, field) {
440
- if (/^\d{4}-\d{2}-\d{2}$/.test(value))
441
- return { allDay: true, date: value };
442
- const hasOffset = /(?:Z|[+-]\d{2}:\d{2})$/.test(value);
443
- if (!hasOffset && !timeZone)
444
- throw new ToolInputError(`${field}: add a UTC offset (e.g. 2026-10-01T15:00:00-07:00) or pass timeZone`);
445
- return { allDay: false, dateTime: value, hasOffset };
446
- }
447
- async function calendarCreateEvent(ctx, account, args) {
448
- const start = eventTime(args.start, args.timeZone, "start");
449
- const end = eventTime(args.end, args.timeZone, "end");
450
- if (start.allDay !== end.allDay)
451
- throw new ToolInputError("start and end must both be dates or both be date-times");
452
- const attendees = addresses(args.attendees, "attendees");
453
- const time = (value) => value.allDay
454
- ? { date: value.date }
455
- : { dateTime: value.dateTime, ...(args.timeZone ? { timeZone: args.timeZone } : {}) };
456
- const body = {
457
- summary: args.summary,
458
- ...(args.description ? { description: args.description } : {}),
459
- ...(args.location ? { location: args.location } : {}),
460
- start: time(start),
461
- end: time(end),
462
- ...(attendees.length ? { attendees: attendees.map((email) => ({ email })) } : {}),
463
- };
464
- const calendarId = encodeURIComponent(args.calendarId ?? "primary");
465
- const query = new URLSearchParams({ sendUpdates: attendees.length ? "all" : "none" });
466
- const created = parse(googleEventSchema, await call(ctx, account, `${CALENDAR}/calendars/${calendarId}/events?${query}`, {
467
- method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), expect: "json",
468
- }), "Calendar");
469
- return json({ account: account.email, created: googleEvent(created) });
470
- }
471
217
  const driveFileSchema = z.object({
472
218
  id: z.string(),
473
219
  name: z.string().optional(),
@@ -516,8 +262,6 @@ async function driveRead(ctx, account, args) {
516
262
  : { ...details, text: bounded(text, MAX_FILE_TEXT_CHARS) });
517
263
  }
518
264
  // ── Microsoft Graph ───────────────────────────────────────────────────────
519
- const GRAPH = "https://graph.microsoft.com/v1.0";
520
- const graphRecipient = z.object({ emailAddress: z.object({ address: z.string().optional(), name: z.string().optional() }).optional() });
521
265
  const graphMessageSchema = z.object({
522
266
  id: z.string(),
523
267
  conversationId: z.string().optional(),
@@ -531,11 +275,6 @@ const graphMessageSchema = z.object({
531
275
  hasAttachments: z.boolean().optional(),
532
276
  body: z.object({ contentType: z.string().optional(), content: z.string().optional() }).optional(),
533
277
  });
534
- function recipient(value) {
535
- const address = value?.emailAddress?.address;
536
- const name = value?.emailAddress?.name;
537
- return address ? (name && name !== address ? `${name} <${address}>` : address) : undefined;
538
- }
539
278
  async function outlookSearch(ctx, account, args) {
540
279
  const query = new URLSearchParams({
541
280
  $search: `"${args.q.replace(/["\\]/g, " ")}"`,
@@ -598,83 +337,6 @@ async function outlookSendDraft(ctx, account, args) {
598
337
  await call(ctx, account, `${GRAPH}/me/messages/${encodeURIComponent(args.draftId)}/send`, { method: "POST", expect: "none" });
599
338
  return json({ account: account.email, sent: true, draftId: args.draftId });
600
339
  }
601
- const graphTime = z.object({ dateTime: z.string().optional(), timeZone: z.string().optional() });
602
- const graphEventSchema = z.object({
603
- id: z.string(),
604
- subject: z.string().nullable().optional(),
605
- start: graphTime.optional(),
606
- end: graphTime.optional(),
607
- isAllDay: z.boolean().optional(),
608
- location: z.object({ displayName: z.string().optional() }).nullable().optional(),
609
- organizer: graphRecipient.nullable().optional(),
610
- attendees: z.array(graphRecipient.extend({ status: z.object({ response: z.string().optional() }).optional() })).optional(),
611
- webLink: z.string().optional(),
612
- bodyPreview: z.string().optional(),
613
- });
614
- function graphEvent(event) {
615
- return {
616
- id: event.id,
617
- summary: event.subject,
618
- start: event.start?.dateTime ? `${event.start.dateTime}${event.start.timeZone === "UTC" ? "Z" : ` (${event.start.timeZone ?? ""})`}` : undefined,
619
- end: event.end?.dateTime ? `${event.end.dateTime}${event.end.timeZone === "UTC" ? "Z" : ` (${event.end.timeZone ?? ""})`}` : undefined,
620
- allDay: event.isAllDay,
621
- location: event.location?.displayName || undefined,
622
- organizer: recipient(event.organizer),
623
- attendees: event.attendees?.map((attendee) => `${recipient(attendee) ?? ""}${attendee.status?.response ? ` (${attendee.status.response})` : ""}`),
624
- preview: event.bodyPreview ? bounded(event.bodyPreview, 1_000) : undefined,
625
- link: event.webLink,
626
- };
627
- }
628
- async function outlookListEvents(ctx, account, args) {
629
- const query = new URLSearchParams({
630
- startDateTime: new Date(args.timeMin).toISOString(),
631
- endDateTime: new Date(args.timeMax).toISOString(),
632
- $top: String(args.max ?? 25),
633
- $orderby: "start/dateTime",
634
- $select: "id,subject,start,end,isAllDay,location,organizer,attendees,webLink,bodyPreview",
635
- });
636
- const list = parse(z.object({ value: z.array(graphEventSchema) }), await call(ctx, account, `${GRAPH}/me/calendarView?${query}`, {
637
- headers: { prefer: 'outlook.timezone="UTC"' }, expect: "json",
638
- }), "Outlook calendar");
639
- return json({ account: account.email, timeZone: "UTC", events: list.value.map(graphEvent) });
640
- }
641
- /** Graph wants a wall-clock time plus a zone: a time with an offset is
642
- * converted to UTC, one without keeps the zone the bot named. */
643
- export function graphDateTime(value, timeZone, field) {
644
- if (/^\d{4}-\d{2}-\d{2}$/.test(value))
645
- return { dateTime: `${value}T00:00:00`, timeZone: timeZone ?? "UTC", allDay: true };
646
- if (/(?:Z|[+-]\d{2}:\d{2})$/.test(value)) {
647
- const parsed = Date.parse(value);
648
- if (!Number.isFinite(parsed))
649
- throw new ToolInputError(`${field}: not a valid date-time`);
650
- return { dateTime: new Date(parsed).toISOString().replace(/\.\d{3}Z$/, ""), timeZone: "UTC", allDay: false };
651
- }
652
- if (!timeZone)
653
- throw new ToolInputError(`${field}: add a UTC offset (e.g. 2026-10-01T15:00:00-07:00) or pass timeZone`);
654
- return { dateTime: value, timeZone, allDay: false };
655
- }
656
- async function outlookCreateEvent(ctx, account, args) {
657
- const start = graphDateTime(args.start, args.timeZone, "start");
658
- const end = graphDateTime(args.end, args.timeZone, "end");
659
- if (start.allDay !== end.allDay)
660
- throw new ToolInputError("start and end must both be dates or both be date-times");
661
- const attendees = addresses(args.attendees, "attendees");
662
- const created = parse(graphEventSchema, await call(ctx, account, `${GRAPH}/me/events`, {
663
- method: "POST",
664
- headers: { "content-type": "application/json", prefer: 'outlook.timezone="UTC"' },
665
- body: JSON.stringify({
666
- subject: args.summary,
667
- ...(args.description ? { body: { contentType: "Text", content: args.description } } : {}),
668
- start: { dateTime: start.dateTime, timeZone: start.timeZone },
669
- end: { dateTime: end.dateTime, timeZone: end.timeZone },
670
- ...(start.allDay ? { isAllDay: true } : {}),
671
- ...(args.location ? { location: { displayName: args.location } } : {}),
672
- ...(attendees.length ? { attendees: attendees.map((address) => ({ emailAddress: { address }, type: "required" })) } : {}),
673
- }),
674
- expect: "json",
675
- }), "Outlook calendar");
676
- return json({ account: account.email, created: graphEvent(created) });
677
- }
678
340
  const driveItemSchema = z.object({
679
341
  id: z.string(),
680
342
  name: z.string().optional(),
@@ -713,16 +375,13 @@ const RUNNERS = {
713
375
  gmail_read: gmailRead,
714
376
  gmail_create_draft: gmailCreateDraft,
715
377
  gmail_send_draft: gmailSendDraft,
716
- calendar_list_events: calendarListEvents,
717
- calendar_create_event: calendarCreateEvent,
378
+ ...CALENDAR_RUNNERS,
718
379
  drive_search: driveSearch,
719
380
  drive_read: driveRead,
720
381
  outlook_search: outlookSearch,
721
382
  outlook_read: outlookRead,
722
383
  outlook_create_draft: outlookCreateDraft,
723
384
  outlook_send_draft: outlookSendDraft,
724
- outlook_calendar_list_events: outlookListEvents,
725
- outlook_calendar_create_event: outlookCreateEvent,
726
385
  onedrive_search: onedriveSearch,
727
386
  onedrive_read: onedriveRead,
728
387
  };