deepspace 0.23.1 → 0.24.0

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.
package/dist/worker.js CHANGED
@@ -105,11 +105,11 @@ function decodeRoomIdentityHeader(value) {
105
105
  return void 0;
106
106
  }
107
107
  }
108
- function authenticatedRoomRequest(request, auth, identity) {
109
- const url = new URL(request.url);
108
+ function authenticatedRoomRequest(request2, auth, identity) {
109
+ const url = new URL(request2.url);
110
110
  url.searchParams.delete("token");
111
111
  for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key);
112
- const forwarded = new Request(url.toString(), request);
112
+ const forwarded = new Request(url.toString(), request2);
113
113
  for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header);
114
114
  if (!auth) return forwarded;
115
115
  forwarded.headers.set("x-user-id", encodeRoomIdentityHeader(auth.userId));
@@ -129,13 +129,18 @@ function authenticatedRoomRequest(request, auth, identity) {
129
129
  }
130
130
 
131
131
  // src/server/rooms/base-room.ts
132
- function connectionAttachmentFromRequest(request) {
132
+ function connectionAttachmentFromRequest(request2) {
133
+ if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
134
+ console.warn(
135
+ "[BaseRoom] Connection carries identity in the URL (?userId=) but no x-user-id header, so this user joins as anonymous. The app worker's room proxy predates 0.19 or forwards the client's URL unstripped: forward with authenticatedRoomRequest() from deepspace/worker."
136
+ );
137
+ }
133
138
  return {
134
- userId: decodeRoomIdentityHeader(request.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
135
- userName: decodeRoomIdentityHeader(request.headers.get("x-user-name")) || "Anonymous",
136
- userEmail: decodeRoomIdentityHeader(request.headers.get("x-user-email")) || "",
137
- userImageUrl: decodeRoomIdentityHeader(request.headers.get("x-user-image-url")),
138
- role: decodeRoomIdentityHeader(request.headers.get("x-user-role"))
139
+ userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
140
+ userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
141
+ userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
142
+ userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
143
+ role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
139
144
  };
140
145
  }
141
146
  var BaseRoom = class {
@@ -151,15 +156,15 @@ var BaseRoom = class {
151
156
  // ==========================================================================
152
157
  // HTTP Entry Point
153
158
  // ==========================================================================
154
- async fetch(request) {
155
- const url = new URL(request.url);
156
- const internal = await this.handleInternalRequest(request, url);
159
+ async fetch(request2) {
160
+ const url = new URL(request2.url);
161
+ const internal = await this.handleInternalRequest(request2, url);
157
162
  if (internal) return internal;
158
- if (request.headers.get("Upgrade") === "websocket") {
159
- return this.handleWebSocketUpgrade(request, url);
163
+ if (request2.headers.get("Upgrade") === "websocket") {
164
+ return this.handleWebSocketUpgrade(request2, url);
160
165
  }
161
166
  if (this.onRequest) {
162
- return this.onRequest(request);
167
+ return this.onRequest(request2);
163
168
  }
164
169
  return new Response("Not Found", { status: 404 });
165
170
  }
@@ -180,11 +185,11 @@ var BaseRoom = class {
180
185
  * See the security note on `fetch()`: this path is only reachable via DO
181
186
  * stub fetch from the app worker, never from the public internet.
182
187
  */
183
- async handleInternalRequest(request, url) {
184
- if (request.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
188
+ async handleInternalRequest(request2, url) {
189
+ if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
185
190
  let options;
186
191
  try {
187
- const text = await request.text();
192
+ const text = await request2.text();
188
193
  if (text) options = JSON.parse(text);
189
194
  } catch {
190
195
  }
@@ -196,11 +201,11 @@ var BaseRoom = class {
196
201
  // ==========================================================================
197
202
  // WebSocket Connection
198
203
  // ==========================================================================
199
- async handleWebSocketUpgrade(request, _url) {
204
+ async handleWebSocketUpgrade(request2, _url) {
200
205
  const pair = new WebSocketPair();
201
206
  const [client, server] = Object.values(pair);
202
207
  this.state.acceptWebSocket(server);
203
- const attachment = connectionAttachmentFromRequest(request);
208
+ const attachment = connectionAttachmentFromRequest(request2);
204
209
  const augmented = await this.onConnect(server, attachment) ?? attachment;
205
210
  server.serializeAttachment(augmented);
206
211
  return new Response(null, { status: 101, webSocket: client });
@@ -436,7 +441,8 @@ function dataToColumnValues(data, columns) {
436
441
  return values;
437
442
  }
438
443
  function coerceValue(value, storage, interpretation) {
439
- if (value === null || value === void 0 || value === "") return null;
444
+ if (value === null || value === void 0) return null;
445
+ if (value === "") return storage === "text" ? "" : null;
440
446
  if (storage === "text") {
441
447
  if (interpretation.kind === "json") {
442
448
  if (typeof value === "string") return value;
@@ -470,7 +476,7 @@ function coerceValue(value, storage, interpretation) {
470
476
  }
471
477
  if (interpretation.kind === "date" || interpretation.kind === "datetime") {
472
478
  const d = new Date(s);
473
- if (!isNaN(d.getTime())) return Math.floor(d.getTime() / 1e3);
479
+ if (!isNaN(d.getTime())) return epochSeconds(d);
474
480
  return s;
475
481
  }
476
482
  const cleaned = s.replace(/[$€£¥,\s]/g, "");
@@ -478,6 +484,9 @@ function coerceValue(value, storage, interpretation) {
478
484
  if (!isNaN(n) && isFinite(n)) return n;
479
485
  return s;
480
486
  }
487
+ function epochSeconds(date = /* @__PURE__ */ new Date()) {
488
+ return Math.floor(date.getTime() / 1e3);
489
+ }
481
490
  function buildTableSelect(collectionName, columns) {
482
491
  const tbl = collectionTableName(collectionName);
483
492
  const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
@@ -1072,7 +1081,7 @@ var BUILT_IN_TOOLS = [
1072
1081
  description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
1073
1082
  params: {
1074
1083
  collection: { type: "string", description: "Collection name to query", required: true },
1075
- where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
1084
+ where: { type: "object", description: "Filter object with field=value equality conditions; every key must be recordId, createdBy, or a schema column (an unknown key is refused, not ignored)", required: false },
1076
1085
  orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
1077
1086
  orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
1078
1087
  limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
@@ -1142,7 +1151,7 @@ var BUILT_IN_TOOLS = [
1142
1151
  },
1143
1152
  {
1144
1153
  name: "user.list",
1145
- description: "List all users in this room",
1154
+ description: "List the users this caller may see, exactly as the room's roster does: an admin gets whole rows (including email and any app-defined column) through the users collection's read policy; everyone else gets the public identity projection (id, name, imageUrl, role, lastSeenAt) of the roster the users policy allows them \u2014 nothing if that policy denies reads, and only their own scope if the schema sets `roster: 'read-policy'`. A caller with no identity gets an empty list. Called from a server action (X-App-Action), it returns whole rows like the other RBAC-off tools.",
1146
1155
  params: {}
1147
1156
  },
1148
1157
  // Yjs tools (collaborative document access)
@@ -1316,6 +1325,9 @@ function encode(message) {
1316
1325
  function getResolvedColumns(schema) {
1317
1326
  return (schema.columns ?? []).map(resolveColumn);
1318
1327
  }
1328
+ function triggerTimestamp(col) {
1329
+ return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
1330
+ }
1319
1331
  function systemManagedColumnError(column, isUpdate) {
1320
1332
  const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
1321
1333
  const remedy = isUpdate ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.` : `A users row is created by registration, so no value for '${column}' is accepted here \u2014 omit it.`;
@@ -1434,7 +1446,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1434
1446
  if (!isUpdate) {
1435
1447
  if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
1436
1448
  if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
1437
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
1449
+ mergedData[col.name] = triggerTimestamp(col);
1438
1450
  }
1439
1451
  }
1440
1452
  } else {
@@ -1442,7 +1454,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1442
1454
  const newVal = mergedData[triggerField];
1443
1455
  if (newVal !== oldVal) {
1444
1456
  if (triggerValue === void 0 || newVal === triggerValue) {
1445
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
1457
+ mergedData[col.name] = triggerTimestamp(col);
1446
1458
  }
1447
1459
  }
1448
1460
  }
@@ -1487,27 +1499,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1487
1499
  if (SYSTEM_ASSIGNED_COLUMNS.has(col.name) && Object.hasOwn(data, col.name) && coerceValue(data[col.name], col.storage, col.interpretation) !== coerceValue(current, col.storage, col.interpretation)) {
1488
1500
  return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
1489
1501
  }
1490
- if (current !== void 0) finalData[col.name] = current;
1502
+ if (current !== void 0)
1503
+ finalData[col.name] = current;
1491
1504
  else delete finalData[col.name];
1492
1505
  }
1493
1506
  }
1494
1507
  const colValues = dataToColumnValues(finalData, columns);
1495
1508
  const now = (/* @__PURE__ */ new Date()).toISOString();
1496
1509
  const tbl = collectionTableName(collection);
1497
- if (!isUpdate && schema.uniqueOn && schema.uniqueOn.length > 0) {
1498
- const uniqueWhere = schema.uniqueOn.map((fieldName) => {
1499
- const colSqlId = columnId(fieldName);
1500
- return `"${colSqlId}" = ?`;
1501
- });
1502
- const uniqueParams = schema.uniqueOn.map((fieldName) => {
1503
- const val = finalData[fieldName];
1504
- return val !== void 0 ? val : null;
1510
+ if (schema.uniqueOn && schema.uniqueOn.length > 0) {
1511
+ const uniqueColumns = schema.uniqueOn.map((fieldName) => {
1512
+ const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
1513
+ return { id, value: colValues[id] ?? null };
1505
1514
  });
1506
- const existing2 = ctx.sql.exec(
1507
- `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")} LIMIT 1`,
1515
+ const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
1516
+ const uniqueParams = uniqueColumns.map(({ value }) => value);
1517
+ if (isUpdate) uniqueParams.push(recordId);
1518
+ const conflict = ctx.sql.exec(
1519
+ `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
1508
1520
  ...uniqueParams
1509
1521
  );
1510
- if (existing2.toArray().length > 0) {
1522
+ if (conflict.toArray().length > 0) {
1511
1523
  const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
1512
1524
  return {
1513
1525
  success: false,
@@ -1573,21 +1585,22 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
1573
1585
  notifyTeamMembershipChange(ctx, collection, record);
1574
1586
  return { success: true, data: { deleted: true } };
1575
1587
  }
1576
- function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
1577
- const resolved = resolveCollection(ctx, collection);
1578
- if (!resolved.ok) return { success: false, error: resolved.error };
1579
- const schema = resolved.schema;
1580
- if (!schema) {
1588
+ function refuseUnknownWhere(collection, schema, where) {
1589
+ const columns = (schema?.columns ?? []).map(resolveColumn);
1590
+ const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
1591
+ if (typeof where !== "object" || where === null || Array.isArray(where)) {
1592
+ const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
1581
1593
  return {
1582
1594
  success: false,
1583
- error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
1595
+ error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
1584
1596
  };
1585
1597
  }
1586
- if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
1587
- return { success: false, error: "Missing required param: where (must match at least one field)" };
1598
+ if (!schema && Object.keys(where).length > 0) {
1599
+ return {
1600
+ success: false,
1601
+ error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
1602
+ };
1588
1603
  }
1589
- const columns = (schema.columns ?? []).map(resolveColumn);
1590
- const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
1591
1604
  const unknown = Object.keys(where).filter(
1592
1605
  (key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
1593
1606
  );
@@ -1604,6 +1617,26 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
1604
1617
  error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(", ")}`
1605
1618
  };
1606
1619
  }
1620
+ return null;
1621
+ }
1622
+ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
1623
+ const resolved = resolveCollection(ctx, collection);
1624
+ if (!resolved.ok) return { success: false, error: resolved.error };
1625
+ const schema = resolved.schema;
1626
+ if (!schema) {
1627
+ return {
1628
+ success: false,
1629
+ error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
1630
+ };
1631
+ }
1632
+ const whereRefusal = refuseUnknownWhere(collection, schema, where);
1633
+ if (whereRefusal) return whereRefusal;
1634
+ if (Object.keys(where).length === 0) {
1635
+ return {
1636
+ success: false,
1637
+ error: "Missing required param: where (must match at least one field)"
1638
+ };
1639
+ }
1607
1640
  if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
1608
1641
  return { success: false, error: "limit must be a finite number" };
1609
1642
  }
@@ -1611,32 +1644,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
1611
1644
  Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
1612
1645
  MAX_DELETE_WHERE_LIMIT
1613
1646
  );
1614
- const scan = { capped: false };
1615
1647
  const matched = executeQuery(
1616
1648
  ctx,
1617
1649
  { collection, where, limit: page },
1618
1650
  userId,
1619
1651
  userRole,
1620
- skipUserRbac,
1621
- scan
1652
+ true
1622
1653
  );
1623
- if (scan.capped && matched.length < page) {
1624
- return {
1625
- success: false,
1626
- error: `records.deleteWhere scanned ${MAX_FILTERED_SCAN_ROWS} rows without filling a page of readable matches in "${collection}" \u2014 narrow \`where\` (or delete by recordId) and retry`
1627
- };
1628
- }
1629
- if (!skipUserRbac && schema) {
1654
+ if (!skipUserRbac) {
1630
1655
  const permCtx = ctx.getPermissionContext();
1631
1656
  for (const record of matched) {
1632
1657
  if (!canDelete(schema, userRole, record, userId, permCtx)) {
1633
- return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` };
1658
+ return {
1659
+ success: false,
1660
+ error: `DELETE DENIED: role=${userRole}, collection=${collection}`
1661
+ };
1634
1662
  }
1635
1663
  }
1636
1664
  }
1637
1665
  let deleted = 0;
1638
1666
  for (const record of matched) {
1639
- if (deleteRecord(ctx, collection, record.recordId, userId, userRole, skipUserRbac).success) {
1667
+ if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
1640
1668
  deleted++;
1641
1669
  }
1642
1670
  }
@@ -2335,17 +2363,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
2335
2363
  );
2336
2364
  return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
2337
2365
  }
2338
- function rosterFor(ctx, schema, records, publicRoster, attachment) {
2339
- if (isAnonymousUserId(attachment.userId)) return [];
2340
- if (attachment.role === "admin") {
2366
+ function rosterFor(ctx, schema, records, publicRoster, userId, role) {
2367
+ if (!userId || isAnonymousUserId(userId)) return [];
2368
+ if (role === "admin") {
2341
2369
  const permissionContext = ctx.getPermissionContext();
2342
- return records.filter((record) => canRead(schema, "admin", record, attachment.userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
2370
+ return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
2343
2371
  }
2344
- if (!getRolePermissions(schema, attachment.role).read) return [];
2372
+ if (!getRolePermissions(schema, role).read) return [];
2345
2373
  if (schema.roster === "read-policy") {
2346
2374
  const permissionContext = ctx.getPermissionContext();
2347
2375
  const readable = new Set(
2348
- records.filter((record) => canRead(schema, attachment.role, record, attachment.userId, permissionContext)).map((record) => record.recordId)
2376
+ records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
2349
2377
  );
2350
2378
  return publicRoster.filter((user) => readable.has(user.id));
2351
2379
  }
@@ -2356,17 +2384,18 @@ function publicIdentities(records) {
2356
2384
  id: recordId,
2357
2385
  name: data.name,
2358
2386
  ...data.imageUrl ? { imageUrl: data.imageUrl } : {},
2359
- role: data.role
2387
+ role: data.role,
2388
+ lastSeenAt: data.lastSeenAt
2360
2389
  }));
2361
2390
  }
2362
- function handleUserList(ctx, ws, attachment) {
2391
+ function rosterForCaller(ctx, userId, role) {
2363
2392
  const schema = ctx.schemaRegistry.get("users");
2364
- if (!schema) {
2365
- ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
2366
- return;
2367
- }
2393
+ if (!schema) return [];
2368
2394
  const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
2369
- const users = rosterFor(ctx, schema, records, publicIdentities(records), attachment);
2395
+ return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
2396
+ }
2397
+ function handleUserList(ctx, ws, attachment) {
2398
+ const users = rosterForCaller(ctx, attachment.userId, attachment.role);
2370
2399
  ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
2371
2400
  }
2372
2401
  function broadcastUserList(ctx, except) {
@@ -2381,7 +2410,9 @@ function broadcastUserList(ctx, except) {
2381
2410
  if (!attachment) continue;
2382
2411
  ctx.send(other, {
2383
2412
  type: MSG.USER_LIST,
2384
- payload: { users: rosterFor(ctx, schema, records, publicRoster, attachment) }
2413
+ payload: {
2414
+ users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
2415
+ }
2385
2416
  });
2386
2417
  }
2387
2418
  }
@@ -2434,8 +2465,8 @@ var CORS_HEADERS = {
2434
2465
  "Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
2435
2466
  "Content-Type": "application/json"
2436
2467
  };
2437
- async function handleToolsRequest(ctx, request, path) {
2438
- if (request.method === "OPTIONS") {
2468
+ async function handleToolsRequest(ctx, request2, path) {
2469
+ if (request2.method === "OPTIONS") {
2439
2470
  return new Response(null, { headers: CORS_HEADERS });
2440
2471
  }
2441
2472
  if (path === "tools/list") {
@@ -2452,15 +2483,15 @@ async function handleToolsRequest(ctx, request, path) {
2452
2483
  }
2453
2484
  return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
2454
2485
  }
2455
- if (path.startsWith("tools/execute") && request.method === "POST") {
2456
- return handleToolExecute(ctx, request);
2486
+ if (path.startsWith("tools/execute") && request2.method === "POST") {
2487
+ return handleToolExecute(ctx, request2);
2457
2488
  }
2458
2489
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
2459
2490
  }
2460
- async function handleToolExecute(ctx, request) {
2491
+ async function handleToolExecute(ctx, request2) {
2461
2492
  let body;
2462
2493
  try {
2463
- body = await request.json();
2494
+ body = await request2.json();
2464
2495
  } catch {
2465
2496
  return Response.json(
2466
2497
  { success: false, error: "Invalid JSON body" },
@@ -2474,8 +2505,8 @@ async function handleToolExecute(ctx, request) {
2474
2505
  { status: 400, headers: CORS_HEADERS }
2475
2506
  );
2476
2507
  }
2477
- const userId = request.headers.get("x-user-id") ?? "";
2478
- const isAppAction = request.headers.get("x-app-action")?.toLowerCase() === "true";
2508
+ const userId = request2.headers.get("x-user-id") ?? "";
2509
+ const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
2479
2510
  let userRole = "viewer";
2480
2511
  if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
2481
2512
  userRole = "admin";
@@ -2510,9 +2541,14 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
2510
2541
  if (!collection) return { success: false, error: "Missing required param: collection" };
2511
2542
  const resolved = resolveCollection(ctx, collection);
2512
2543
  if (!resolved.ok) return { success: false, error: resolved.error };
2544
+ if (params.where !== void 0) {
2545
+ const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
2546
+ if (refusal) return refusal;
2547
+ }
2548
+ const where = params.where;
2513
2549
  const query = {
2514
2550
  collection,
2515
- where: params.where,
2551
+ where,
2516
2552
  orderBy: params.orderBy,
2517
2553
  orderDir: params.orderDir,
2518
2554
  limit: params.limit
@@ -2618,8 +2654,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
2618
2654
  if (!user) return { success: false, error: "User not found" };
2619
2655
  return { success: true, data: { user } };
2620
2656
  }
2657
+ // The same roster the socket's `user.list` returns, for the same caller.
2658
+ // Reading the users table directly here handed any member every peer's
2659
+ // email regardless of the room's users policy, while their own socket
2660
+ // roster and `useQuery('users')` obeyed it.
2661
+ //
2662
+ // An app action (`X-App-Action`) is the app's own server-side code, which
2663
+ // is already the trust boundary and reads every other collection RBAC-off;
2664
+ // it keeps the full rows.
2621
2665
  case "user.list": {
2622
- const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
2666
+ const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
2623
2667
  return { success: true, data: { users } };
2624
2668
  }
2625
2669
  default:
@@ -2754,25 +2798,25 @@ var CORS_HEADERS2 = {
2754
2798
  "Access-Control-Allow-Headers": "Content-Type",
2755
2799
  "Content-Type": "application/json"
2756
2800
  };
2757
- async function handleApiRequest(ctx, request, url) {
2758
- if (request.method === "OPTIONS") {
2801
+ async function handleApiRequest(ctx, request2, url) {
2802
+ if (request2.method === "OPTIONS") {
2759
2803
  return new Response(null, { headers: CORS_HEADERS2 });
2760
2804
  }
2761
2805
  const path = url.pathname.replace("/api/", "");
2762
2806
  if (path.startsWith("tools/")) {
2763
- return handleToolsRequest(ctx, request, path);
2807
+ return handleToolsRequest(ctx, request2, path);
2764
2808
  }
2765
2809
  if (path === "debug/status") {
2766
2810
  return handleDebugStatus(ctx);
2767
2811
  }
2768
2812
  if (path === "debug/sql") {
2769
- return handleDebugSql(ctx, request, url);
2813
+ return handleDebugSql(ctx, request2, url);
2770
2814
  }
2771
2815
  if (path === "debug/user-role") {
2772
2816
  return handleDebugUserRole(ctx, url);
2773
2817
  }
2774
- if (path === "debug/set-role" && request.method === "POST") {
2775
- return handleDebugSetRole(ctx, request);
2818
+ if (path === "debug/set-role" && request2.method === "POST") {
2819
+ return handleDebugSetRole(ctx, request2);
2776
2820
  }
2777
2821
  if (path.startsWith("debug/records/")) {
2778
2822
  return handleDebugRecords(ctx, path);
@@ -2803,8 +2847,8 @@ function handleDebugUserRole(ctx, url) {
2803
2847
  role: user.role
2804
2848
  }, { headers: CORS_HEADERS2 });
2805
2849
  }
2806
- async function handleDebugSetRole(ctx, request) {
2807
- const body = await request.json();
2850
+ async function handleDebugSetRole(ctx, request2) {
2851
+ const body = await request2.json();
2808
2852
  if (!body.role) {
2809
2853
  return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS2 });
2810
2854
  }
@@ -2912,11 +2956,11 @@ function handleDebugQuery(ctx, url) {
2912
2956
  records
2913
2957
  }, { headers: CORS_HEADERS2 });
2914
2958
  }
2915
- async function handleDebugSql(ctx, request, url) {
2959
+ async function handleDebugSql(ctx, request2, url) {
2916
2960
  let sql;
2917
2961
  let params = [];
2918
- if (request.method === "POST") {
2919
- const body = await request.json();
2962
+ if (request2.method === "POST") {
2963
+ const body = await request2.json();
2920
2964
  sql = body.sql;
2921
2965
  params = body.params ?? [];
2922
2966
  } else {
@@ -3036,10 +3080,10 @@ var RecordRoom = class extends BaseRoom {
3036
3080
  // ============================================================================
3037
3081
  // HTTP Entry Point (overrides BaseRoom.fetch)
3038
3082
  // ============================================================================
3039
- async fetch(request) {
3040
- const url = new URL(request.url);
3083
+ async fetch(request2) {
3084
+ const url = new URL(request2.url);
3041
3085
  if (url.pathname.startsWith("/internal/")) {
3042
- const internal = await this.handleInternalRequest(request, url);
3086
+ const internal = await this.handleInternalRequest(request2, url);
3043
3087
  if (internal) return internal;
3044
3088
  }
3045
3089
  const fetchStart = Date.now();
@@ -3052,11 +3096,11 @@ var RecordRoom = class extends BaseRoom {
3052
3096
  if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
3053
3097
  return new Response("Not Found", { status: 404 });
3054
3098
  }
3055
- return handleApiRequest(this.createHandlerContext(), request, url);
3099
+ return handleApiRequest(this.createHandlerContext(), request2, url);
3056
3100
  }
3057
- if (request.headers.get("Upgrade") === "websocket") {
3101
+ if (request2.headers.get("Upgrade") === "websocket") {
3058
3102
  this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
3059
- return super.fetch(request);
3103
+ return super.fetch(request2);
3060
3104
  }
3061
3105
  return new Response("Not Found", { status: 404 });
3062
3106
  }
@@ -3638,9 +3682,9 @@ var CanvasRoom = class extends BaseRoom {
3638
3682
  // --------------------------------------------------------------------------
3639
3683
  // BaseRoom Lifecycle
3640
3684
  // --------------------------------------------------------------------------
3641
- async fetch(request) {
3685
+ async fetch(request2) {
3642
3686
  this.ensureInitialized();
3643
- return super.fetch(request);
3687
+ return super.fetch(request2);
3644
3688
  }
3645
3689
  onConnect(ws, user) {
3646
3690
  this.ensureInitialized();
@@ -4119,6 +4163,7 @@ function cronMatches(parsed, timezone, date) {
4119
4163
  }
4120
4164
 
4121
4165
  // src/server/rooms/cron-room.ts
4166
+ var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
4122
4167
  var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
4123
4168
  MSG.CRON_TRIGGER,
4124
4169
  MSG.CRON_PAUSE,
@@ -4193,9 +4238,15 @@ var CronRoom = class extends BaseRoom {
4193
4238
  // ==========================================================================
4194
4239
  // BaseRoom Lifecycle
4195
4240
  // ==========================================================================
4196
- async fetch(request) {
4241
+ async fetch(request2) {
4197
4242
  this.ensureInitialized();
4198
- return super.fetch(request);
4243
+ return super.fetch(request2);
4244
+ }
4245
+ async handleInternalRequest(request2, url) {
4246
+ if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
4247
+ return Response.json({ tasks: this.getTaskStates() });
4248
+ }
4249
+ return super.handleInternalRequest(request2, url);
4199
4250
  }
4200
4251
  onConnect(ws, user) {
4201
4252
  this.ensureInitialized();
@@ -4343,15 +4394,22 @@ var CronRoom = class extends BaseRoom {
4343
4394
  const start = Date.now();
4344
4395
  let success = true;
4345
4396
  let error;
4397
+ let thrown;
4346
4398
  try {
4347
4399
  await this.onTask(taskName);
4348
4400
  } catch (e) {
4349
4401
  success = false;
4402
+ thrown = e;
4350
4403
  error = e instanceof Error ? e.message : String(e);
4351
- console.error(`[CronRoom] Task "${taskName}" failed:`, e);
4352
4404
  }
4353
4405
  const durationMs = Date.now() - start;
4354
4406
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
4407
+ if (success) {
4408
+ console.log(`[cron] ${taskName} ok ${durationMs}ms`);
4409
+ } else {
4410
+ const stack = thrown instanceof Error ? thrown.stack : void 0;
4411
+ console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
4412
+ }
4355
4413
  this.sql.exec(
4356
4414
  `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
4357
4415
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -4460,6 +4518,19 @@ var CronRoom = class extends BaseRoom {
4460
4518
  );
4461
4519
  }
4462
4520
  };
4521
+ var armedCronRooms = /* @__PURE__ */ new Set();
4522
+ function armCronRoom(ctx, namespace, roomId, tasks) {
4523
+ if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
4524
+ armedCronRooms.add(roomId);
4525
+ ctx.waitUntil(
4526
+ namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
4527
+ if (!response.ok) armedCronRooms.delete(roomId);
4528
+ return response;
4529
+ }).catch(() => {
4530
+ armedCronRooms.delete(roomId);
4531
+ })
4532
+ );
4533
+ }
4463
4534
  function computeNextRunAt(task, from) {
4464
4535
  if (task.interval_minutes) {
4465
4536
  return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
@@ -4532,9 +4603,9 @@ var JobRoom = class extends BaseRoom {
4532
4603
  // ==========================================================================
4533
4604
  // BaseRoom Lifecycle
4534
4605
  // ==========================================================================
4535
- async fetch(request) {
4606
+ async fetch(request2) {
4536
4607
  this.ensureInitialized();
4537
- return super.fetch(request);
4608
+ return super.fetch(request2);
4538
4609
  }
4539
4610
  async onConnect(ws, user) {
4540
4611
  this.ensureInitialized();
@@ -4621,13 +4692,13 @@ var JobRoom = class extends BaseRoom {
4621
4692
  * Subclasses overriding `onRequest` should call `super.onRequest(req)`
4622
4693
  * for paths they don't handle.
4623
4694
  */
4624
- async onRequest(request) {
4695
+ async onRequest(request2) {
4625
4696
  this.ensureInitialized();
4626
- const url = new URL(request.url);
4627
- if (request.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
4697
+ const url = new URL(request2.url);
4698
+ if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
4628
4699
  return new Response("Not Found", { status: 404 });
4629
4700
  }
4630
- const body = await request.json().catch(() => null);
4701
+ const body = await request2.json().catch(() => null);
4631
4702
  if (!body || typeof body.type !== "string" || !body.type) {
4632
4703
  return new Response(JSON.stringify({ error: "type required" }), {
4633
4704
  status: 400,
@@ -4793,7 +4864,8 @@ var JobRoom = class extends BaseRoom {
4793
4864
  } catch (e) {
4794
4865
  outcome = "failed";
4795
4866
  errorMessage = e instanceof Error ? e.message : String(e);
4796
- console.error(`[JobRoom] Job "${row.type}" (${row.id}) failed:`, e);
4867
+ const stack = e instanceof Error ? e.stack : void 0;
4868
+ console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
4797
4869
  } finally {
4798
4870
  this.inFlight.delete(row.id);
4799
4871
  }
@@ -5107,7 +5179,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
5107
5179
  "kv_namespace",
5108
5180
  "vectorize",
5109
5181
  "r2_bucket",
5110
- "queue"
5182
+ "queue",
5183
+ "ai_search"
5111
5184
  ]);
5112
5185
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
5113
5186
  function isAutoProvision(b) {
@@ -5122,6 +5195,8 @@ function isAutoProvision(b) {
5122
5195
  return b.bucket_name === AUTO_PROVISION_SENTINEL;
5123
5196
  case "queue":
5124
5197
  return b.queue_name === AUTO_PROVISION_SENTINEL;
5198
+ case "ai_search":
5199
+ return b.instance_name === AUTO_PROVISION_SENTINEL;
5125
5200
  default:
5126
5201
  return false;
5127
5202
  }
@@ -5136,7 +5211,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
5136
5211
  "browser_rendering",
5137
5212
  "analytics_engine",
5138
5213
  "hyperdrive",
5139
- "ratelimit"
5214
+ "ratelimit",
5215
+ "ai_search"
5140
5216
  ]);
5141
5217
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5142
5218
  "ASSETS",
@@ -5160,6 +5236,7 @@ function validateBindingManifest(manifest) {
5160
5236
  }
5161
5237
  const errors = [];
5162
5238
  const seenNames = /* @__PURE__ */ new Set();
5239
+ let aiSearchCount = 0;
5163
5240
  for (const entry of manifest) {
5164
5241
  if (!entry || typeof entry !== "object") {
5165
5242
  errors.push({
@@ -5190,14 +5267,23 @@ function validateBindingManifest(manifest) {
5190
5267
  }
5191
5268
  seenNames.add(name);
5192
5269
  const binding = entry;
5270
+ if (binding.type === "ai_search") aiSearchCount++;
5193
5271
  const fieldErr = requiredFieldError(binding);
5194
5272
  if (fieldErr) errors.push({ binding, reason: fieldErr });
5195
5273
  }
5274
+ if (aiSearchCount > 1) {
5275
+ errors.push({ reason: "Only one ai_search binding may be declared per app" });
5276
+ }
5196
5277
  if (errors.length) return { valid: false, errors };
5197
5278
  return { valid: true, bindings: manifest };
5198
5279
  }
5199
5280
  function requiredFieldError(b) {
5200
5281
  switch (b.type) {
5282
+ case "ai_search":
5283
+ if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
5284
+ return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
5285
+ }
5286
+ return null;
5201
5287
  case "vectorize": {
5202
5288
  if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
5203
5289
  if (b.index_name === AUTO_PROVISION_SENTINEL) {
@@ -5259,6 +5345,13 @@ function requiredFieldError(b) {
5259
5345
  }
5260
5346
  }
5261
5347
  function bindingManifestFromOutputConfig(outputConfig) {
5348
+ const aiSearchNamespaces = outputConfig.ai_search_namespaces;
5349
+ const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
5350
+ if (declaresAiSearchNamespace) {
5351
+ throw new Error(
5352
+ 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
5353
+ );
5354
+ }
5262
5355
  const out = [];
5263
5356
  const aiBinding = pluckString(outputConfig.ai, "binding");
5264
5357
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
@@ -5276,6 +5369,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
5276
5369
  ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
5277
5370
  });
5278
5371
  }
5372
+ for (const a of asObjectArray(outputConfig.ai_search)) {
5373
+ const name = pluckString(a, "binding");
5374
+ const instance_name = pluckString(a, "instance_name");
5375
+ if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
5376
+ }
5279
5377
  for (const r of asObjectArray(outputConfig.r2_buckets)) {
5280
5378
  const name = pluckString(r, "binding");
5281
5379
  const bucket_name = pluckString(r, "bucket_name");
@@ -5811,218 +5909,8 @@ var DIRECTORY_SCHEMAS = [
5811
5909
  postsSchema
5812
5910
  ];
5813
5911
 
5814
- // src/server/schemas/workspace.ts
5815
- var workspaceTeamsSchema = {
5816
- name: "teams",
5817
- columns: [
5818
- { name: "Name", storage: "text", interpretation: "plain" },
5819
- { name: "CreatedBy", storage: "text", interpretation: "plain" },
5820
- { name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
5821
- ],
5822
- teamField: "_rowId",
5823
- permissions: {
5824
- admin: { read: true, create: true, update: true, delete: true },
5825
- member: { read: "team", create: true, update: "team", delete: "own" }
5826
- }
5827
- };
5828
- var workspaceTeamMembersSchema = {
5829
- name: "team_members",
5830
- columns: [
5831
- { name: "TeamId", storage: "text", interpretation: "plain" },
5832
- { name: "UserId", storage: "text", interpretation: "plain" },
5833
- { name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
5834
- { name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
5835
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
5836
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
5837
- ],
5838
- teamField: "TeamId",
5839
- permissions: {
5840
- admin: { read: true, create: true, update: true, delete: true },
5841
- member: { read: "team", create: true, update: "team", delete: "team" }
5842
- }
5843
- };
5844
- var workspaceTasksSchema = {
5845
- name: "tasks",
5846
- columns: [
5847
- { name: "TeamId", storage: "text", interpretation: "plain" },
5848
- { name: "Title", storage: "text", interpretation: "plain" },
5849
- { name: "Notes", storage: "text", interpretation: "plain" },
5850
- { name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
5851
- { name: "CompletedAt", storage: "number", interpretation: "plain" },
5852
- { name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
5853
- { name: "DeletedAt", storage: "number", interpretation: "plain" },
5854
- { name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
5855
- { name: "DueDate", storage: "text", interpretation: "plain" },
5856
- { name: "ProjectId", storage: "text", interpretation: "plain" },
5857
- { name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
5858
- { name: "Order", storage: "number", interpretation: "plain" },
5859
- { name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
5860
- { name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
5861
- { name: "TagIds", storage: "text", interpretation: { kind: "json" } },
5862
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5863
- ],
5864
- teamField: "TeamId",
5865
- permissions: {
5866
- admin: { read: true, create: true, update: true, delete: true },
5867
- member: { read: "team", create: true, update: "team", delete: "team" },
5868
- viewer: { read: false, create: false, update: false, delete: false }
5869
- }
5870
- };
5871
- var workspaceProjectsSchema = {
5872
- name: "projects",
5873
- columns: [
5874
- { name: "TeamId", storage: "text", interpretation: "plain" },
5875
- { name: "Title", storage: "text", interpretation: "plain" },
5876
- { name: "Notes", storage: "text", interpretation: "plain" },
5877
- { name: "Color", storage: "text", interpretation: "plain" },
5878
- { name: "ParentId", storage: "text", interpretation: "plain" },
5879
- { name: "Order", storage: "number", interpretation: "plain" },
5880
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5881
- ],
5882
- teamField: "TeamId",
5883
- permissions: {
5884
- admin: { read: true, create: true, update: true, delete: true },
5885
- member: { read: "team", create: true, update: "team", delete: "team" },
5886
- viewer: { read: false, create: false, update: false, delete: false }
5887
- }
5888
- };
5889
- var workspaceTagsSchema = {
5890
- name: "tags",
5891
- columns: [
5892
- { name: "TeamId", storage: "text", interpretation: "plain" },
5893
- { name: "Name", storage: "text", interpretation: "plain" },
5894
- { name: "Color", storage: "text", interpretation: "plain" },
5895
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5896
- ],
5897
- teamField: "TeamId",
5898
- permissions: {
5899
- admin: { read: true, create: true, update: true, delete: true },
5900
- member: { read: "team", create: true, update: "team", delete: "team" },
5901
- viewer: { read: false, create: false, update: false, delete: false }
5902
- }
5903
- };
5904
- var workspacePeopleSchema = {
5905
- name: "people",
5906
- columns: [
5907
- { name: "Name", storage: "text", interpretation: "plain" },
5908
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
5909
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
5910
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
5911
- { name: "CompanyId", storage: "text", interpretation: "plain" },
5912
- { name: "Department", storage: "text", interpretation: "plain" },
5913
- { name: "Title", storage: "text", interpretation: "plain" },
5914
- { name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
5915
- { name: "Metadata", storage: "text", interpretation: { kind: "json" } }
5916
- ],
5917
- permissions: {
5918
- "*": { read: true, create: true, update: true, delete: false },
5919
- admin: { read: true, create: true, update: true, delete: true }
5920
- }
5921
- };
5922
- var workspaceTransactionsSchema = {
5923
- name: "transactions",
5924
- columns: [
5925
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
5926
- { name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
5927
- { name: "Currency", storage: "text", interpretation: "plain" },
5928
- { name: "Description", storage: "text", interpretation: "plain" },
5929
- { name: "Category", storage: "text", interpretation: "plain" },
5930
- { name: "CounterpartyRef", storage: "text", interpretation: "plain" },
5931
- { name: "OrderRef", storage: "text", interpretation: "plain" },
5932
- { name: "Account", storage: "text", interpretation: "plain" }
5933
- ],
5934
- permissions: {
5935
- "*": { read: true, create: true, update: true, delete: false },
5936
- admin: { read: true, create: true, update: true, delete: true }
5937
- }
5938
- };
5939
- var workspaceAccountsSchema = {
5940
- name: "accounts",
5941
- columns: [
5942
- { name: "Name", storage: "text", interpretation: "plain" },
5943
- { name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
5944
- { name: "Currency", storage: "text", interpretation: "plain" }
5945
- ],
5946
- permissions: {
5947
- "*": { read: true, create: true, update: true, delete: false },
5948
- admin: { read: true, create: true, update: true, delete: true }
5949
- }
5950
- };
5951
- var workspaceContentSharesSchema = {
5952
- name: "content_shares",
5953
- ownerField: "OwnerId",
5954
- columns: [
5955
- { name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
5956
- { name: "ContentId", storage: "text", interpretation: "plain" },
5957
- { name: "OwnerId", storage: "text", interpretation: "plain" },
5958
- { name: "OwnerName", storage: "text", interpretation: "plain" },
5959
- { name: "Title", storage: "text", interpretation: "plain" },
5960
- { name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
5961
- { name: "ShareTarget", storage: "text", interpretation: "plain" },
5962
- { name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
5963
- { name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
5964
- { name: "SharedBy", storage: "text", interpretation: "plain" },
5965
- { name: "SourceApp", storage: "text", interpretation: "plain" },
5966
- { name: "WordCount", storage: "number", interpretation: "plain" },
5967
- { name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
5968
- ],
5969
- permissions: {
5970
- "*": { read: true, create: true, update: true, delete: "own" },
5971
- admin: { read: true, create: true, update: true, delete: true }
5972
- }
5973
- };
5974
- var workspaceFormResponsesSchema = {
5975
- name: "form_responses",
5976
- columns: [
5977
- { name: "FormId", storage: "text", interpretation: "plain" },
5978
- { name: "FormTitle", storage: "text", interpretation: "plain" },
5979
- { name: "RespondentId", storage: "text", interpretation: "plain" },
5980
- { name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
5981
- { name: "RespondentName", storage: "text", interpretation: "plain" },
5982
- { name: "Data", storage: "text", interpretation: { kind: "json" } },
5983
- { name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
5984
- ],
5985
- permissions: {
5986
- "*": { read: true, create: true, update: false, delete: false },
5987
- admin: { read: true, create: true, update: true, delete: true }
5988
- }
5989
- };
5990
- var workspaceEmailHandlesSchema = {
5991
- name: "email_handles",
5992
- columns: [
5993
- { name: "UserId", storage: "text", interpretation: "plain" },
5994
- { name: "Handle", storage: "text", interpretation: "plain" },
5995
- { name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
5996
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
5997
- ],
5998
- uniqueOn: ["Handle"],
5999
- ownerField: "UserId",
6000
- permissions: {
6001
- "*": { read: true, create: true, update: "own", delete: false },
6002
- admin: { read: true, create: true, update: true, delete: true }
6003
- }
6004
- };
6005
- var WORKSPACE_SCHEMAS = [
6006
- workspaceTeamsSchema,
6007
- workspaceTeamMembersSchema,
6008
- workspaceTasksSchema,
6009
- workspaceProjectsSchema,
6010
- workspaceTagsSchema,
6011
- workspacePeopleSchema,
6012
- workspaceTransactionsSchema,
6013
- workspaceAccountsSchema,
6014
- workspaceContentSharesSchema,
6015
- workspaceFormResponsesSchema,
6016
- workspaceEmailHandlesSchema
6017
- ];
6018
-
6019
5912
  // src/server/schemas/shared-do.ts
6020
5913
  var GLOBAL_DO_TYPES = [
6021
- {
6022
- name: "workspace",
6023
- schemas: WORKSPACE_SCHEMAS,
6024
- description: "Shared workspace data (teams, tasks, projects, people, ledger)"
6025
- },
6026
5914
  {
6027
5915
  name: "conv",
6028
5916
  schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
@@ -6050,6 +5938,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
6050
5938
  var MAX_APP_MIGRATION_IDS = 256;
6051
5939
  var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
6052
5940
  var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
5941
+ var ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where";
5942
+ var BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id";
5943
+ var ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard";
6053
5944
  function validateAppMigrationIds(value) {
6054
5945
  if (!Array.isArray(value)) {
6055
5946
  return { valid: false, reason: "app migrations must be a JSON string array" };
@@ -6918,8 +6809,8 @@ function overPartBound(bytes) {
6918
6809
  if (bytes <= UPLOAD_PART_BYTES) return null;
6919
6810
  return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
6920
6811
  }
6921
- function declaredLength(request, what) {
6922
- const raw = request.headers.get("content-length");
6812
+ function declaredLength(request2, what) {
6813
+ const raw = request2.headers.get("content-length");
6923
6814
  const declared = raw === null ? NaN : Number(raw);
6924
6815
  if (!Number.isSafeInteger(declared) || declared < 0) {
6925
6816
  return fail2(
@@ -6997,7 +6888,7 @@ function multipartFailure(error) {
6997
6888
  }
6998
6889
  function createScopedR2Handler(config) {
6999
6890
  const requireAuthForMutations = config.requireAuthForMutations ?? true;
7000
- return async (request, url, bucket, auth) => {
6891
+ return async (request2, url, bucket, auth) => {
7001
6892
  let subpathRaw;
7002
6893
  try {
7003
6894
  subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
@@ -7026,26 +6917,26 @@ function createScopedR2Handler(config) {
7026
6917
  }
7027
6918
  const prefix = result.prefix;
7028
6919
  const excludedPrefixes = result.excludedPrefixes ?? [];
7029
- if (isFilesVerbPath(request.method, subpath)) {
6920
+ if (isFilesVerbPath(request2.method, subpath)) {
7030
6921
  if (requireAuthForMutations && !auth.userId) {
7031
- await request.body?.cancel().catch(() => void 0);
6922
+ await request2.body?.cancel().catch(() => void 0);
7032
6923
  return fail2(401, "Authentication required", "unauthorized");
7033
6924
  }
7034
- return subpath === "upload" ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
6925
+ return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
7035
6926
  }
7036
- if (!subpath && request.method === "GET") {
6927
+ if (!subpath && request2.method === "GET") {
7037
6928
  return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
7038
6929
  }
7039
- if (subpath && request.method === "GET") {
6930
+ if (subpath && request2.method === "GET") {
7040
6931
  if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
7041
6932
  return Response.json(
7042
6933
  { error: "Access denied: key outside scope" },
7043
6934
  { status: 403, headers: CORS_HEADERS4 }
7044
6935
  );
7045
6936
  }
7046
- return handleDownload(request, bucket, subpath, scope);
6937
+ return handleDownload(request2, bucket, subpath, scope);
7047
6938
  }
7048
- if (subpath && request.method === "DELETE") {
6939
+ if (subpath && request2.method === "DELETE") {
7049
6940
  if (requireAuthForMutations && !auth.userId) {
7050
6941
  return Response.json(
7051
6942
  { error: "Authentication required" },
@@ -7071,18 +6962,18 @@ function createScopedR2Handler(config) {
7071
6962
  return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
7072
6963
  };
7073
6964
  }
7074
- async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
6965
+ async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7075
6966
  try {
7076
- const declaredBytes = declaredLength(request, "File uploads");
6967
+ const declaredBytes = declaredLength(request2, "File uploads");
7077
6968
  if (declaredBytes instanceof Response) return declaredBytes;
7078
6969
  const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
7079
6970
  if (tooLargeRequest) return tooLargeRequest;
7080
- const contentType = request.headers.get("content-type") || "";
6971
+ const contentType = request2.headers.get("content-type") || "";
7081
6972
  let fileData;
7082
6973
  let fileName;
7083
6974
  let rawMimeType;
7084
6975
  if (contentType.includes("multipart/form-data")) {
7085
- const formData = await request.formData();
6976
+ const formData = await request2.formData();
7086
6977
  const file = formData.get("file");
7087
6978
  if (!file) return fail2(400, "No file provided", "bad_request");
7088
6979
  const tooBig = overRequestBound(file.size);
@@ -7091,7 +6982,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
7091
6982
  fileName = formData.get("name")?.toString() || file.name;
7092
6983
  rawMimeType = file.type || "application/octet-stream";
7093
6984
  } else {
7094
- const body = await request.json();
6985
+ const body = await request2.json();
7095
6986
  if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
7096
6987
  const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
7097
6988
  const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
@@ -7172,15 +7063,15 @@ function storedFileJson(key, prefix, name, url, scope) {
7172
7063
  );
7173
7064
  }
7174
7065
  var MAX_CONTROL_BODY_BYTES = 64 * 1024;
7175
- async function readControlJson(request) {
7176
- const declared = declaredLength(request, "This request");
7066
+ async function readControlJson(request2) {
7067
+ const declared = declaredLength(request2, "This request");
7177
7068
  if (declared instanceof Response) return declared;
7178
7069
  if (declared > MAX_CONTROL_BODY_BYTES) {
7179
7070
  return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
7180
7071
  }
7181
7072
  let parsed;
7182
7073
  try {
7183
- parsed = await request.json();
7074
+ parsed = await request2.json();
7184
7075
  } catch {
7185
7076
  return fail2(400, "Body must be JSON", "bad_request");
7186
7077
  }
@@ -7232,7 +7123,7 @@ function multipartLayoutError(declaredBytes) {
7232
7123
  { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
7233
7124
  );
7234
7125
  }
7235
- async function streamPart(upload, partNumber, body, expectedBytes) {
7126
+ async function streamPart(upload2, partNumber, body, expectedBytes) {
7236
7127
  let observedBytes = 0;
7237
7128
  const sized = new FixedLengthStream(expectedBytes);
7238
7129
  const pumped = body.pipeThrough(
@@ -7244,7 +7135,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7244
7135
  })
7245
7136
  ).pipeTo(sized.writable);
7246
7137
  const [uploaded, drained] = await Promise.allSettled([
7247
- upload.uploadPart(partNumber, sized.readable),
7138
+ upload2.uploadPart(partNumber, sized.readable),
7248
7139
  pumped
7249
7140
  ]);
7250
7141
  if (uploaded.status === "rejected" || drained.status === "rejected") {
@@ -7253,20 +7144,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7253
7144
  }
7254
7145
  return { ok: true, part: uploaded.value };
7255
7146
  }
7256
- async function handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
7257
- if (subpath === "multipart" && request.method === "POST") {
7258
- return multipartInit(request, url, bucket, prefix, excludedPrefixes, auth);
7147
+ async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
7148
+ if (subpath === "multipart" && request2.method === "POST") {
7149
+ return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
7259
7150
  }
7260
7151
  if (subpath === "multipart/part") {
7261
- return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth);
7152
+ return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
7262
7153
  }
7263
7154
  if (subpath === "multipart/complete") {
7264
- return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope);
7155
+ return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
7265
7156
  }
7266
7157
  return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
7267
7158
  }
7268
- async function multipartInit(request, url, bucket, prefix, excludedPrefixes, auth) {
7269
- const body = await readControlJson(request);
7159
+ async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
7160
+ const body = await readControlJson(request2);
7270
7161
  if (body instanceof Response) return body;
7271
7162
  const size = body.size;
7272
7163
  if (!Number.isSafeInteger(size) || size <= 0) {
@@ -7307,9 +7198,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7307
7198
  "request_id_conflict"
7308
7199
  );
7309
7200
  }
7310
- let upload;
7201
+ let upload2;
7311
7202
  try {
7312
- upload = await bucket.createMultipartUpload(located.key, {
7203
+ upload2 = await bucket.createMultipartUpload(located.key, {
7313
7204
  httpMetadata: { contentType: mimeType },
7314
7205
  customMetadata: fileMetadata(name, auth.userId, requestId, size)
7315
7206
  });
@@ -7321,7 +7212,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7321
7212
  body: JSON.stringify({
7322
7213
  version: 1,
7323
7214
  reservationId: requestId,
7324
- uploadId: upload.uploadId,
7215
+ uploadId: upload2.uploadId,
7325
7216
  objectKey: located.key,
7326
7217
  uploadKey: located.subkey,
7327
7218
  admitted: false,
@@ -7335,7 +7226,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7335
7226
  const reservedBytes = Math.max(size, 5 * 1024 * 1024);
7336
7227
  const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
7337
7228
  if (admission instanceof Response) {
7338
- await upload.abort().catch(() => void 0);
7229
+ await upload2.abort().catch(() => void 0);
7339
7230
  if (admission.status === 409 && auth.storage) {
7340
7231
  const winner = await readMultipartReservation(bucket, auth.storage, requestId);
7341
7232
  if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
@@ -7345,11 +7236,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7345
7236
  return admission;
7346
7237
  }
7347
7238
  if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
7348
- await upload.abort().catch(() => void 0);
7239
+ await upload2.abort().catch(() => void 0);
7349
7240
  await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
7350
7241
  return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
7351
7242
  }
7352
- return multipartSessionJson(upload.uploadId, located.subkey, requestId);
7243
+ return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
7353
7244
  }
7354
7245
  async function abandonMultipart(bucket, session, auth, reservation) {
7355
7246
  let result = "aborted";
@@ -7367,12 +7258,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
7367
7258
  }
7368
7259
  return result;
7369
7260
  }
7370
- async function multipartPart(request, url, bucket, prefix, excludedPrefixes, auth) {
7261
+ async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
7371
7262
  const refuse = async (response) => {
7372
- await request.body?.cancel().catch(() => void 0);
7263
+ await request2.body?.cancel().catch(() => void 0);
7373
7264
  return response;
7374
7265
  };
7375
- const declaredBytes = declaredLength(request, "Part uploads");
7266
+ const declaredBytes = declaredLength(request2, "Part uploads");
7376
7267
  if (declaredBytes instanceof Response) return refuse(declaredBytes);
7377
7268
  const tooBig = overPartBound(declaredBytes);
7378
7269
  if (tooBig) return refuse(tooBig);
@@ -7384,7 +7275,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7384
7275
  }
7385
7276
  const session = resolveSession(url, prefix, excludedPrefixes);
7386
7277
  if (session instanceof Response) return refuse(session);
7387
- if (!request.body) return fail2(400, "A part body is required", "bad_request");
7278
+ if (!request2.body) return fail2(400, "A part body is required", "bad_request");
7388
7279
  const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
7389
7280
  if (reservation === "invalid") {
7390
7281
  return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
@@ -7426,8 +7317,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7426
7317
  if (refreshed === "busy") {
7427
7318
  return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
7428
7319
  }
7429
- const upload = bucket.resumeMultipartUpload(session.key, session.uploadId);
7430
- const stored = await streamPart(upload, partNumber, request.body, declaredBytes);
7320
+ const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
7321
+ const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
7431
7322
  if (!stored.ok) {
7432
7323
  if (r2ErrorCode(stored.error) === 10024 && reservation) {
7433
7324
  await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
@@ -7483,8 +7374,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
7483
7374
  const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
7484
7375
  return storedFileJson(session.key, prefix, name, url, scope);
7485
7376
  }
7486
- async function multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
7487
- const body = await readControlJson(request);
7377
+ async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7378
+ const body = await readControlJson(request2);
7488
7379
  if (body instanceof Response) return body;
7489
7380
  const session = resolveSession(url, prefix, excludedPrefixes);
7490
7381
  if (session instanceof Response) return session;
@@ -7643,7 +7534,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
7643
7534
  { headers: CORS_HEADERS4 }
7644
7535
  );
7645
7536
  }
7646
- async function handleDownload(request, bucket, key, scope) {
7537
+ async function handleDownload(request2, bucket, key, scope) {
7647
7538
  const object = await bucket.get(key);
7648
7539
  if (!object) {
7649
7540
  return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
@@ -7665,7 +7556,7 @@ async function handleDownload(request, bucket, key, scope) {
7665
7556
  contentDisposition(originalName, mustDownload ? "attachment" : "inline")
7666
7557
  );
7667
7558
  if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
7668
- if (scope === "app" && request.headers.get("if-none-match") === object.httpEtag) {
7559
+ if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
7669
7560
  return new Response(null, { status: 304, headers });
7670
7561
  }
7671
7562
  return new Response(object.body, { headers });
@@ -8118,8 +8009,8 @@ async function composioTools(env, options) {
8118
8009
  if (!list.ok || !list.success) {
8119
8010
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
8120
8011
  }
8121
- const listItems = list.data?.items;
8122
- const items = Array.isArray(listItems) ? listItems : [];
8012
+ const listItems2 = list.data?.items;
8013
+ const items = Array.isArray(listItems2) ? listItems2 : [];
8123
8014
  const tools = {};
8124
8015
  for (const item of items) {
8125
8016
  if (!item?.slug) continue;
@@ -8704,18 +8595,272 @@ var COST_RATES = {
8704
8595
  queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
8705
8596
  /** USD per stored dimension per month. */
8706
8597
  storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
8598
+ },
8599
+ aiSearch: {
8600
+ ingestPerToken: 0.75 / 1e6,
8601
+ ingestImagePerToken: 0.5 / 1e6,
8602
+ storagePerByteMonth: 2 / 1e9,
8603
+ hybridOrSemanticPerQuery: 0.75 / 1e3,
8604
+ fulltextPerQuery: 0.1 / 1e3
8707
8605
  }
8708
8606
  };
8709
8607
  function priceBindingUsageEvent(kind, op, units) {
8608
+ if (!Number.isFinite(units) || units < 0) return 0;
8710
8609
  if (kind === "ai" && op === "input") {
8711
8610
  return units * COST_RATES.ai.embedInputPerChar;
8712
8611
  }
8713
8612
  if (kind === "vectorize" && op === "query") {
8714
8613
  return units * COST_RATES.vectorize.queriedPerDim;
8715
8614
  }
8615
+ if (kind === "ai_search") {
8616
+ if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
8617
+ if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
8618
+ if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
8619
+ if (op === "search-hybrid" || op === "search-semantic")
8620
+ return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
8621
+ if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
8622
+ }
8716
8623
  return 0;
8717
8624
  }
8718
8625
 
8626
+ // src/server/utils/knowledge-base.ts
8627
+ var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
8628
+ var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
8629
+ var MAX_QUERY_CHARS = 4096;
8630
+ function hasControlChar(value) {
8631
+ for (let i = 0; i < value.length; i++) {
8632
+ const code = value.charCodeAt(i);
8633
+ if (code < 32 || code === 127) return true;
8634
+ }
8635
+ return false;
8636
+ }
8637
+ var KnowledgeError = class extends Error {
8638
+ constructor(status, code, message, uploadedItems) {
8639
+ super(message);
8640
+ this.status = status;
8641
+ this.code = code;
8642
+ this.uploadedItems = uploadedItems;
8643
+ this.name = "KnowledgeError";
8644
+ }
8645
+ };
8646
+ function normalizeKnowledgeFolder(folder) {
8647
+ if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
8648
+ throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
8649
+ const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
8650
+ if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
8651
+ throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
8652
+ const parts = withoutTrailingSlash.split("/");
8653
+ if (parts.some((part) => !part || part === "." || part === ".."))
8654
+ throw new KnowledgeError(
8655
+ 400,
8656
+ "invalid_folder",
8657
+ "Folder cannot contain empty, dot, or parent segments"
8658
+ );
8659
+ const normalized = `${parts.join("/")}/`;
8660
+ if (normalized.length > 128)
8661
+ throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
8662
+ return normalized;
8663
+ }
8664
+ function validateKnowledgeFilename(name) {
8665
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
8666
+ throw new KnowledgeError(
8667
+ 400,
8668
+ "invalid_filename",
8669
+ "File name must be a non-empty base name without path separators"
8670
+ );
8671
+ }
8672
+ }
8673
+ function folderFilter(folder) {
8674
+ const value = normalizeKnowledgeFolder(folder);
8675
+ return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
8676
+ }
8677
+ function knowledge(env) {
8678
+ const add = (file, options = {}) => addFile(env, file, options);
8679
+ const list = (options = {}) => listItems(env, options);
8680
+ const search = (query, options = {}) => searchItems(env, query, options);
8681
+ return {
8682
+ add,
8683
+ list,
8684
+ search,
8685
+ async remove(itemId) {
8686
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
8687
+ throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
8688
+ await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
8689
+ },
8690
+ scoped({ folder }) {
8691
+ const fixed = normalizeKnowledgeFolder(folder);
8692
+ return {
8693
+ add: (file) => add(file, { folder: fixed }),
8694
+ list: (options = {}) => list({ ...options, folder: fixed }),
8695
+ search: (query, options = {}) => search(query, { ...options, folder: fixed })
8696
+ };
8697
+ }
8698
+ };
8699
+ }
8700
+ async function addFile(env, file, options) {
8701
+ validateKnowledgeFilename(file.name);
8702
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8703
+ if ((folder?.length ?? 0) + file.name.length > 128)
8704
+ throw new KnowledgeError(
8705
+ 400,
8706
+ "invalid_key",
8707
+ "Folder and filename together exceed the 128-character AI Search key limit"
8708
+ );
8709
+ if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
8710
+ if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
8711
+ throw new KnowledgeError(
8712
+ 413,
8713
+ "file_too_large",
8714
+ "This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
8715
+ );
8716
+ const bytes = new Uint8Array(await file.arrayBuffer());
8717
+ const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
8718
+ const uploaded = [];
8719
+ try {
8720
+ for (let i = 0; i < parts.length; i++) {
8721
+ const bytes2 = parts[i];
8722
+ const contents = bytes2.buffer.slice(
8723
+ bytes2.byteOffset,
8724
+ bytes2.byteOffset + bytes2.byteLength
8725
+ );
8726
+ const part = new File([contents], partName(file.name, i + 1), {
8727
+ type: file.type || "text/plain"
8728
+ });
8729
+ if ((folder?.length ?? 0) + part.name.length > 128)
8730
+ throw new KnowledgeError(
8731
+ 400,
8732
+ "invalid_key",
8733
+ "Folder and split filename together exceed the 128-character AI Search key limit"
8734
+ );
8735
+ uploaded.push(...(await upload(env, part, folder)).items);
8736
+ }
8737
+ } catch (error) {
8738
+ if (error instanceof KnowledgeError)
8739
+ throw new KnowledgeError(error.status, error.code, error.message, uploaded);
8740
+ throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
8741
+ }
8742
+ return { items: uploaded };
8743
+ }
8744
+ function splitUtf8(bytes, max) {
8745
+ const out = [];
8746
+ for (let start = 0; start < bytes.length; ) {
8747
+ let end = Math.min(start + max, bytes.length);
8748
+ if (end < bytes.length) {
8749
+ let cut = end;
8750
+ while (cut > start && (bytes[cut] & 192) === 128) cut--;
8751
+ for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
8752
+ if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
8753
+ cut = i + 1;
8754
+ break;
8755
+ }
8756
+ end = cut > start ? cut : end;
8757
+ }
8758
+ out.push(bytes.slice(start, end));
8759
+ start = end;
8760
+ }
8761
+ return out;
8762
+ }
8763
+ function partName(name, n) {
8764
+ const dot = name.lastIndexOf(".");
8765
+ return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
8766
+ }
8767
+ async function upload(env, file, folder) {
8768
+ const body = new FormData();
8769
+ body.append("file", file);
8770
+ if (folder) body.append("folder", folder);
8771
+ return await request(env, "/api/knowledge/items", {
8772
+ method: "POST",
8773
+ body
8774
+ });
8775
+ }
8776
+ async function listItems(env, options) {
8777
+ if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
8778
+ throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
8779
+ if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
8780
+ throw new KnowledgeError(
8781
+ 400,
8782
+ "invalid_per_page",
8783
+ "List perPage must be an integer from 1 to 50"
8784
+ );
8785
+ if (options.search !== void 0 && options.search.length > 256)
8786
+ throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
8787
+ if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
8788
+ throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
8789
+ const q = new URLSearchParams();
8790
+ if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
8791
+ if (options.page !== void 0) q.set("page", String(options.page));
8792
+ if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
8793
+ if (options.status) q.set("status", options.status);
8794
+ if (options.search) q.set("search", options.search);
8795
+ return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
8796
+ }
8797
+ async function searchItems(env, query, options) {
8798
+ const normalizedQuery = query.trim();
8799
+ if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
8800
+ if (normalizedQuery.length > MAX_QUERY_CHARS)
8801
+ throw new KnowledgeError(
8802
+ 400,
8803
+ "invalid_query",
8804
+ `Search query cannot exceed ${MAX_QUERY_CHARS} characters`
8805
+ );
8806
+ if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
8807
+ throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
8808
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
8809
+ throw new KnowledgeError(
8810
+ 400,
8811
+ "invalid_limit",
8812
+ "Search limit must be an integer from 1 through 50"
8813
+ );
8814
+ if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
8815
+ throw new KnowledgeError(
8816
+ 400,
8817
+ "invalid_match_threshold",
8818
+ "Match threshold must be between 0 and 1"
8819
+ );
8820
+ if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
8821
+ throw new KnowledgeError(
8822
+ 400,
8823
+ "invalid_query_rewrite",
8824
+ "queryRewrite must be a boolean when provided"
8825
+ );
8826
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8827
+ return await request(env, "/api/knowledge/search", {
8828
+ method: "POST",
8829
+ headers: { "content-type": "application/json" },
8830
+ body: JSON.stringify({
8831
+ query: normalizedQuery,
8832
+ mode: options.mode ?? "hybrid",
8833
+ limit: options.limit ?? 10,
8834
+ queryRewrite: options.queryRewrite ?? false,
8835
+ ...options.matchThreshold !== void 0 && {
8836
+ matchThreshold: options.matchThreshold
8837
+ },
8838
+ ...folder && { folder }
8839
+ })
8840
+ });
8841
+ }
8842
+ async function request(env, path, init = {}) {
8843
+ const headers = new Headers(init.headers);
8844
+ appendAppIdentity(headers, env);
8845
+ const response = await apiWorkerFetch(env, path, { ...init, headers });
8846
+ const text = await response.text();
8847
+ let data;
8848
+ try {
8849
+ data = text ? JSON.parse(text) : void 0;
8850
+ } catch {
8851
+ data = void 0;
8852
+ }
8853
+ if (!response.ok) {
8854
+ const body = data && typeof data === "object" ? data : {};
8855
+ throw new KnowledgeError(
8856
+ response.status,
8857
+ typeof body.code === "string" ? body.code : "knowledge_request_failed",
8858
+ typeof body.error === "string" ? body.error : "Knowledge request failed"
8859
+ );
8860
+ }
8861
+ return data;
8862
+ }
8863
+
8719
8864
  // src/server/utils/d1-migrations.ts
8720
8865
  var META_TABLE = "_dpc_migrations";
8721
8866
  var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
@@ -9226,16 +9371,16 @@ var RequestBodyTooLargeError = class extends Error {
9226
9371
  this.name = "RequestBodyTooLargeError";
9227
9372
  }
9228
9373
  };
9229
- async function readBoundedRequestText(request, maxBytes) {
9374
+ async function readBoundedRequestText(request2, maxBytes) {
9230
9375
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
9231
9376
  throw new TypeError("maxBytes must be a positive safe integer");
9232
9377
  }
9233
- const declared = request.headers.get("content-length");
9378
+ const declared = request2.headers.get("content-length");
9234
9379
  if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
9235
9380
  throw new RequestBodyTooLargeError(maxBytes);
9236
9381
  }
9237
- if (!request.body) return "";
9238
- const reader = request.body.getReader();
9382
+ if (!request2.body) return "";
9383
+ const reader = request2.body.getReader();
9239
9384
  const decoder = new TextDecoder();
9240
9385
  let bytes = 0;
9241
9386
  let text = "";
@@ -9449,10 +9594,10 @@ function documentationRequestPath(pathname, publicBasePath) {
9449
9594
  const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
9450
9595
  return canonicalNativeDocumentationRequestPath(clean);
9451
9596
  }
9452
- async function fetchDocumentationAsset(env, request, initialUrl, assetBasePath) {
9597
+ async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
9453
9598
  let url = initialUrl;
9454
9599
  for (let redirects = 0; redirects < 3; redirects++) {
9455
- const response = await env.ASSETS.fetch(new Request(url, request));
9600
+ const response = await env.ASSETS.fetch(new Request(url, request2));
9456
9601
  const location = response.headers.get("Location");
9457
9602
  if (response.status < 300 || response.status >= 400 || !location) return response;
9458
9603
  const next = new URL(location, url);
@@ -9492,11 +9637,11 @@ function secureDocumentationResponse(response, status, pathname) {
9492
9637
  var MCP_SERVER_VERSION = "1.0.0";
9493
9638
  var CURRENT_PROTOCOL_VERSION = "2025-11-25";
9494
9639
  var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
9495
- function initializeResponse(request) {
9496
- const params = objectValue(request.params);
9640
+ function initializeResponse(request2) {
9641
+ const params = objectValue(request2.params);
9497
9642
  const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
9498
9643
  const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
9499
- return mcpResult(request.id, {
9644
+ return mcpResult(request2.id, {
9500
9645
  protocolVersion,
9501
9646
  capabilities: {
9502
9647
  tools: { listChanged: false },
@@ -9510,11 +9655,11 @@ function initializeResponse(request) {
9510
9655
  instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
9511
9656
  });
9512
9657
  }
9513
- function hasValidOrigin(request) {
9514
- const origin = request.headers.get("Origin");
9658
+ function hasValidOrigin(request2) {
9659
+ const origin = request2.headers.get("Origin");
9515
9660
  if (!origin) return true;
9516
9661
  try {
9517
- return new URL(origin).origin === new URL(request.url).origin;
9662
+ return new URL(origin).origin === new URL(request2.url).origin;
9518
9663
  } catch {
9519
9664
  return false;
9520
9665
  }
@@ -9640,8 +9785,8 @@ var DOCUMENTATION_TOOLS = [
9640
9785
  }
9641
9786
  }
9642
9787
  ];
9643
- async function callDocumentationTool(rawRequest, request, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9644
- const params = objectValue(request.params);
9788
+ async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9789
+ const params = objectValue(request2.params);
9645
9790
  const name = typeof params?.name === "string" ? params.name : "";
9646
9791
  const args = objectValue(params?.arguments) ?? {};
9647
9792
  const origin = new URL(rawRequest.url).origin;
@@ -9649,13 +9794,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9649
9794
  const query = typeof args.query === "string" ? args.query.trim() : "";
9650
9795
  const limit = args.limit === void 0 ? 6 : args.limit;
9651
9796
  if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
9652
- return mcpError(request.id, -32602, "Invalid documentation_search arguments");
9797
+ return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
9653
9798
  }
9654
9799
  const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
9655
9800
  ...result,
9656
9801
  url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
9657
9802
  }));
9658
- return toolResult(request.id, {
9803
+ return toolResult(request2.id, {
9659
9804
  ...results[0]?.termCoverage === 1 ? {} : {
9660
9805
  notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
9661
9806
  },
@@ -9665,7 +9810,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9665
9810
  if (name === "documentation_read") {
9666
9811
  const route = typeof args.route === "string" ? args.route.trim() : "";
9667
9812
  if (!route || route.length > 500) {
9668
- return mcpError(request.id, -32602, "Invalid documentation_read arguments");
9813
+ return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
9669
9814
  }
9670
9815
  const hash = route.indexOf("#");
9671
9816
  const fragment = hash === -1 ? "" : route.slice(hash + 1);
@@ -9677,9 +9822,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9677
9822
  rawRequest.url,
9678
9823
  assetBasePath
9679
9824
  );
9680
- if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
9825
+ if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
9681
9826
  const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
9682
- return toolResult(request.id, {
9827
+ return toolResult(request2.id, {
9683
9828
  ...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
9684
9829
  ...page,
9685
9830
  ...section === null ? {} : { markdown: section },
@@ -9687,10 +9832,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9687
9832
  });
9688
9833
  } catch (error) {
9689
9834
  console.error("[documentation-mcp] page unavailable:", error);
9690
- return toolError(request.id, "The documentation page could not be read");
9835
+ return toolError(request2.id, "The documentation page could not be read");
9691
9836
  }
9692
9837
  }
9693
- return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9838
+ return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9694
9839
  }
9695
9840
 
9696
9841
  // src/documentation/worker/mcp-routes.ts
@@ -9727,27 +9872,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9727
9872
  }
9728
9873
  const parsed = parseRequest(raw);
9729
9874
  if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
9730
- const request = parsed.request;
9731
- if (request.method !== "initialize") {
9875
+ const request2 = parsed.request;
9876
+ if (request2.method !== "initialize") {
9732
9877
  const protocolVersion = c.req.header("MCP-Protocol-Version");
9733
9878
  if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
9734
- return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9879
+ return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9735
9880
  }
9736
9881
  }
9737
- const requestId = request.id;
9882
+ const requestId = request2.id;
9738
9883
  if (requestId === void 0) return new Response(null, { status: 202 });
9739
- const requestWithId = { ...request, id: requestId };
9740
- if (request.method === "initialize") return initializeResponse(requestWithId);
9741
- if (request.method === "ping") return mcpResult(requestId, {});
9742
- if (request.method === "tools/list") {
9884
+ const requestWithId = { ...request2, id: requestId };
9885
+ if (request2.method === "initialize") return initializeResponse(requestWithId);
9886
+ if (request2.method === "ping") return mcpResult(requestId, {});
9887
+ if (request2.method === "tools/list") {
9743
9888
  return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
9744
9889
  }
9745
- if (request.method === "resources/list") {
9890
+ if (request2.method === "resources/list") {
9746
9891
  return mcpResult(requestId, {
9747
9892
  resources: [skillResource(c.req.raw, manifest, publicBasePath)]
9748
9893
  });
9749
9894
  }
9750
- if (request.method === "resources/read") {
9895
+ if (request2.method === "resources/read") {
9751
9896
  return readSkillResource(
9752
9897
  c.req.raw,
9753
9898
  requestWithId,
@@ -9757,8 +9902,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9757
9902
  assetBasePath
9758
9903
  );
9759
9904
  }
9760
- if (request.method !== "tools/call") {
9761
- return mcpError(requestId, -32601, `Method not found: ${request.method}`);
9905
+ if (request2.method !== "tools/call") {
9906
+ return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
9762
9907
  }
9763
9908
  const clientKey = await documentationPublicClientKey(
9764
9909
  c.req.header("CF-Connecting-IP") ?? "unknown"
@@ -9815,44 +9960,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9815
9960
  }
9816
9961
  );
9817
9962
  }
9818
- async function readSkillResource(rawRequest, request, manifest, env, publicBasePath, assetBasePath) {
9819
- const params = objectValue(request.params);
9963
+ async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
9964
+ const params = objectValue(request2.params);
9820
9965
  const requestedUri = typeof params?.uri === "string" ? params.uri : "";
9821
9966
  const resource = skillResource(rawRequest, manifest, publicBasePath);
9822
9967
  if (requestedUri !== resource.uri) {
9823
- return mcpError(request.id, -32602, "Unknown documentation resource");
9968
+ return mcpError(request2.id, -32602, "Unknown documentation resource");
9824
9969
  }
9825
9970
  const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
9826
9971
  const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
9827
- if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
9972
+ if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
9828
9973
  const text = await response.text();
9829
9974
  if (text.length > MAX_SKILL_CHARS) {
9830
- return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
9975
+ return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
9831
9976
  }
9832
- return mcpResult(request.id, {
9977
+ return mcpResult(request2.id, {
9833
9978
  contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
9834
9979
  });
9835
9980
  }
9836
- function skillResource(request, manifest, publicBasePath) {
9981
+ function skillResource(request2, manifest, publicBasePath) {
9837
9982
  const subject = documentationSubject(manifest.name);
9838
9983
  return {
9839
- uri: new URL(`${publicBasePath}/skill.md`, request.url).toString(),
9984
+ uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
9840
9985
  name: `${subject} skill`,
9841
9986
  title: `Use ${manifest.name}`,
9842
9987
  description: `Agent instructions grounded in the published ${subject}.`,
9843
9988
  mimeType: "text/markdown"
9844
9989
  };
9845
9990
  }
9846
- async function publicManifest(request, env, assetBasePath) {
9847
- const manifest = await loadDocumentationPublishedManifest(env, request.url, assetBasePath);
9991
+ async function publicManifest(request2, env, assetBasePath) {
9992
+ const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
9848
9993
  return manifest?.mcp.access === "public" ? manifest : null;
9849
9994
  }
9850
- function discoveryResponse(request, manifest, publicBasePath) {
9851
- return jsonResponse(serverCard(request, manifest, publicBasePath), 200, {
9995
+ function discoveryResponse(request2, manifest, publicBasePath) {
9996
+ return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
9852
9997
  "Cache-Control": "public, max-age=300"
9853
9998
  });
9854
9999
  }
9855
- function serverCard(request, manifest, publicBasePath) {
10000
+ function serverCard(request2, manifest, publicBasePath) {
9856
10001
  return {
9857
10002
  name: manifest.name,
9858
10003
  description: `Search and read the published ${documentationSubject(manifest.name)}.`,
@@ -9865,7 +10010,7 @@ function serverCard(request, manifest, publicBasePath) {
9865
10010
  protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
9866
10011
  transport: {
9867
10012
  type: "streamable-http",
9868
- url: new URL(`${publicBasePath}/mcp`, request.url).toString()
10013
+ url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
9869
10014
  },
9870
10015
  authentication: { required: false },
9871
10016
  capabilities: {
@@ -9874,7 +10019,7 @@ function serverCard(request, manifest, publicBasePath) {
9874
10019
  title,
9875
10020
  description
9876
10021
  })),
9877
- resources: [skillResource(request, manifest, publicBasePath)]
10022
+ resources: [skillResource(request2, manifest, publicBasePath)]
9878
10023
  }
9879
10024
  };
9880
10025
  }
@@ -10297,21 +10442,21 @@ function jsonResponse2(body, status) {
10297
10442
  headers: { "Content-Type": "application/json" }
10298
10443
  });
10299
10444
  }
10300
- async function handleClientErrorReport(request) {
10301
- const site = request.headers.get("sec-fetch-site");
10445
+ async function handleClientErrorReport(request2) {
10446
+ const site = request2.headers.get("sec-fetch-site");
10302
10447
  if (site && site !== "same-origin" && site !== "none") {
10303
10448
  return new Response(null, { status: 403 });
10304
10449
  }
10305
10450
  if (shouldThrottleClientError()) {
10306
10451
  return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
10307
10452
  }
10308
- const declared = Number(request.headers.get("content-length"));
10453
+ const declared = Number(request2.headers.get("content-length"));
10309
10454
  if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
10310
10455
  return jsonResponse2({ error: "payload_too_large" }, 413);
10311
10456
  }
10312
10457
  let body;
10313
10458
  try {
10314
- body = await request.json();
10459
+ body = await request2.json();
10315
10460
  } catch {
10316
10461
  return jsonResponse2({ error: "invalid_json" }, 400);
10317
10462
  }
@@ -10339,6 +10484,8 @@ var APP_LOG_EVENT_KEYS = [
10339
10484
  ];
10340
10485
  export {
10341
10486
  ACCOUNT_STORAGE_LIMIT_BYTES,
10487
+ ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
10488
+ ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
10342
10489
  AI_CHATS_SCHEMA,
10343
10490
  AI_MESSAGES_SCHEMA,
10344
10491
  ALLOWED_BINDING_TYPES,
@@ -10352,6 +10499,7 @@ export {
10352
10499
  Awareness,
10353
10500
  BASE_USERS_SCHEMA,
10354
10501
  BROWSER_PROXY_ROUTES,
10502
+ BUILD_INJECTED_APP_ID_MIGRATION_ID,
10355
10503
  BUILT_IN_TOOLS,
10356
10504
  BaseRoom,
10357
10505
  CHANNELS_SCHEMA,
@@ -10377,6 +10525,7 @@ export {
10377
10525
  GLOBAL_DO_TYPES,
10378
10526
  GLOBAL_DO_TYPE_NAMES,
10379
10527
  JobRoom,
10528
+ KnowledgeError,
10380
10529
  LOG_LEVELS,
10381
10530
  MAX_APP_FILE_BYTES,
10382
10531
  MAX_DELETE_WHERE_LIMIT,
@@ -10423,13 +10572,13 @@ export {
10423
10572
  USERS_COLUMNS,
10424
10573
  VOTING_SCHEMAS,
10425
10574
  WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
10426
- WORKSPACE_SCHEMAS,
10427
10575
  YjsRoom,
10428
10576
  analyzePermissions,
10429
10577
  apiWorkerFetch,
10430
10578
  appendMessage,
10431
10579
  applyAiToolDefaults,
10432
10580
  applySlidingWindow,
10581
+ armCronRoom,
10433
10582
  authWorkerFetch,
10434
10583
  authenticatedRoomRequest,
10435
10584
  bindingManifestFromOutputConfig,
@@ -10469,6 +10618,8 @@ export {
10469
10618
  encodeSyncStep2,
10470
10619
  encodeUpdate,
10471
10620
  enqueueJob,
10621
+ epochSeconds,
10622
+ folderFilter,
10472
10623
  formatBytes,
10473
10624
  getChat,
10474
10625
  getDeepSpaceAIModel,
@@ -10489,6 +10640,7 @@ export {
10489
10640
  isTestAccountClaims,
10490
10641
  isTestAccountEmail,
10491
10642
  isTestAccountTier,
10643
+ knowledge,
10492
10644
  lintSchema,
10493
10645
  lintSchemaSet,
10494
10646
  lintSchemas,
@@ -10502,6 +10654,7 @@ export {
10502
10654
  mintAppId,
10503
10655
  mintUlid,
10504
10656
  noopPermissionContext,
10657
+ normalizeKnowledgeFolder,
10505
10658
  parseQuotaLimit,
10506
10659
  platformCorsPatterns,
10507
10660
  platformWorkerFetch,
@@ -10537,17 +10690,6 @@ export {
10537
10690
  validateBindingManifest,
10538
10691
  validateDoManifest,
10539
10692
  verifyJwt,
10540
- workspaceAccountsSchema,
10541
- workspaceContentSharesSchema,
10542
- workspaceEmailHandlesSchema,
10543
- workspaceFormResponsesSchema,
10544
- workspacePeopleSchema,
10545
- workspaceProjectsSchema,
10546
- workspaceTagsSchema,
10547
- workspaceTasksSchema,
10548
- workspaceTeamMembersSchema,
10549
- workspaceTeamsSchema,
10550
- workspaceTransactionsSchema,
10551
10693
  writeVarUint,
10552
10694
  writeVarUint8Array
10553
10695
  };