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/server.js CHANGED
@@ -111,7 +111,7 @@ var BUILT_IN_TOOLS = [
111
111
  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.`,
112
112
  params: {
113
113
  collection: { type: "string", description: "Collection name to query", required: true },
114
- where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
114
+ 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 },
115
115
  orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
116
116
  orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
117
117
  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 }
@@ -181,7 +181,7 @@ var BUILT_IN_TOOLS = [
181
181
  },
182
182
  {
183
183
  name: "user.list",
184
- description: "List all users in this room",
184
+ 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.",
185
185
  params: {}
186
186
  },
187
187
  // Yjs tools (collaborative document access)
@@ -762,8 +762,8 @@ function overPartBound(bytes) {
762
762
  if (bytes <= UPLOAD_PART_BYTES) return null;
763
763
  return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
764
764
  }
765
- function declaredLength(request, what) {
766
- const raw = request.headers.get("content-length");
765
+ function declaredLength(request2, what) {
766
+ const raw = request2.headers.get("content-length");
767
767
  const declared = raw === null ? NaN : Number(raw);
768
768
  if (!Number.isSafeInteger(declared) || declared < 0) {
769
769
  return fail2(
@@ -841,7 +841,7 @@ function multipartFailure(error) {
841
841
  }
842
842
  function createScopedR2Handler(config) {
843
843
  const requireAuthForMutations = config.requireAuthForMutations ?? true;
844
- return async (request, url, bucket, auth) => {
844
+ return async (request2, url, bucket, auth) => {
845
845
  let subpathRaw;
846
846
  try {
847
847
  subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
@@ -870,26 +870,26 @@ function createScopedR2Handler(config) {
870
870
  }
871
871
  const prefix = result.prefix;
872
872
  const excludedPrefixes = result.excludedPrefixes ?? [];
873
- if (isFilesVerbPath(request.method, subpath)) {
873
+ if (isFilesVerbPath(request2.method, subpath)) {
874
874
  if (requireAuthForMutations && !auth.userId) {
875
- await request.body?.cancel().catch(() => void 0);
875
+ await request2.body?.cancel().catch(() => void 0);
876
876
  return fail2(401, "Authentication required", "unauthorized");
877
877
  }
878
- return subpath === "upload" ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
878
+ return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
879
879
  }
880
- if (!subpath && request.method === "GET") {
880
+ if (!subpath && request2.method === "GET") {
881
881
  return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
882
882
  }
883
- if (subpath && request.method === "GET") {
883
+ if (subpath && request2.method === "GET") {
884
884
  if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
885
885
  return Response.json(
886
886
  { error: "Access denied: key outside scope" },
887
887
  { status: 403, headers: CORS_HEADERS2 }
888
888
  );
889
889
  }
890
- return handleDownload(request, bucket, subpath, scope);
890
+ return handleDownload(request2, bucket, subpath, scope);
891
891
  }
892
- if (subpath && request.method === "DELETE") {
892
+ if (subpath && request2.method === "DELETE") {
893
893
  if (requireAuthForMutations && !auth.userId) {
894
894
  return Response.json(
895
895
  { error: "Authentication required" },
@@ -915,18 +915,18 @@ function createScopedR2Handler(config) {
915
915
  return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS2 });
916
916
  };
917
917
  }
918
- async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
918
+ async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
919
919
  try {
920
- const declaredBytes = declaredLength(request, "File uploads");
920
+ const declaredBytes = declaredLength(request2, "File uploads");
921
921
  if (declaredBytes instanceof Response) return declaredBytes;
922
922
  const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
923
923
  if (tooLargeRequest) return tooLargeRequest;
924
- const contentType = request.headers.get("content-type") || "";
924
+ const contentType = request2.headers.get("content-type") || "";
925
925
  let fileData;
926
926
  let fileName;
927
927
  let rawMimeType;
928
928
  if (contentType.includes("multipart/form-data")) {
929
- const formData = await request.formData();
929
+ const formData = await request2.formData();
930
930
  const file = formData.get("file");
931
931
  if (!file) return fail2(400, "No file provided", "bad_request");
932
932
  const tooBig = overRequestBound(file.size);
@@ -935,7 +935,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
935
935
  fileName = formData.get("name")?.toString() || file.name;
936
936
  rawMimeType = file.type || "application/octet-stream";
937
937
  } else {
938
- const body = await request.json();
938
+ const body = await request2.json();
939
939
  if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
940
940
  const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
941
941
  const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
@@ -1016,15 +1016,15 @@ function storedFileJson(key, prefix, name, url, scope) {
1016
1016
  );
1017
1017
  }
1018
1018
  var MAX_CONTROL_BODY_BYTES = 64 * 1024;
1019
- async function readControlJson(request) {
1020
- const declared = declaredLength(request, "This request");
1019
+ async function readControlJson(request2) {
1020
+ const declared = declaredLength(request2, "This request");
1021
1021
  if (declared instanceof Response) return declared;
1022
1022
  if (declared > MAX_CONTROL_BODY_BYTES) {
1023
1023
  return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
1024
1024
  }
1025
1025
  let parsed;
1026
1026
  try {
1027
- parsed = await request.json();
1027
+ parsed = await request2.json();
1028
1028
  } catch {
1029
1029
  return fail2(400, "Body must be JSON", "bad_request");
1030
1030
  }
@@ -1076,7 +1076,7 @@ function multipartLayoutError(declaredBytes) {
1076
1076
  { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
1077
1077
  );
1078
1078
  }
1079
- async function streamPart(upload, partNumber, body, expectedBytes) {
1079
+ async function streamPart(upload2, partNumber, body, expectedBytes) {
1080
1080
  let observedBytes = 0;
1081
1081
  const sized = new FixedLengthStream(expectedBytes);
1082
1082
  const pumped = body.pipeThrough(
@@ -1088,7 +1088,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
1088
1088
  })
1089
1089
  ).pipeTo(sized.writable);
1090
1090
  const [uploaded, drained] = await Promise.allSettled([
1091
- upload.uploadPart(partNumber, sized.readable),
1091
+ upload2.uploadPart(partNumber, sized.readable),
1092
1092
  pumped
1093
1093
  ]);
1094
1094
  if (uploaded.status === "rejected" || drained.status === "rejected") {
@@ -1097,20 +1097,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
1097
1097
  }
1098
1098
  return { ok: true, part: uploaded.value };
1099
1099
  }
1100
- async function handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
1101
- if (subpath === "multipart" && request.method === "POST") {
1102
- return multipartInit(request, url, bucket, prefix, excludedPrefixes, auth);
1100
+ async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
1101
+ if (subpath === "multipart" && request2.method === "POST") {
1102
+ return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
1103
1103
  }
1104
1104
  if (subpath === "multipart/part") {
1105
- return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth);
1105
+ return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
1106
1106
  }
1107
1107
  if (subpath === "multipart/complete") {
1108
- return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope);
1108
+ return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
1109
1109
  }
1110
1110
  return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
1111
1111
  }
1112
- async function multipartInit(request, url, bucket, prefix, excludedPrefixes, auth) {
1113
- const body = await readControlJson(request);
1112
+ async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
1113
+ const body = await readControlJson(request2);
1114
1114
  if (body instanceof Response) return body;
1115
1115
  const size = body.size;
1116
1116
  if (!Number.isSafeInteger(size) || size <= 0) {
@@ -1151,9 +1151,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
1151
1151
  "request_id_conflict"
1152
1152
  );
1153
1153
  }
1154
- let upload;
1154
+ let upload2;
1155
1155
  try {
1156
- upload = await bucket.createMultipartUpload(located.key, {
1156
+ upload2 = await bucket.createMultipartUpload(located.key, {
1157
1157
  httpMetadata: { contentType: mimeType },
1158
1158
  customMetadata: fileMetadata(name, auth.userId, requestId, size)
1159
1159
  });
@@ -1165,7 +1165,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
1165
1165
  body: JSON.stringify({
1166
1166
  version: 1,
1167
1167
  reservationId: requestId,
1168
- uploadId: upload.uploadId,
1168
+ uploadId: upload2.uploadId,
1169
1169
  objectKey: located.key,
1170
1170
  uploadKey: located.subkey,
1171
1171
  admitted: false,
@@ -1179,7 +1179,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
1179
1179
  const reservedBytes = Math.max(size, 5 * 1024 * 1024);
1180
1180
  const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
1181
1181
  if (admission instanceof Response) {
1182
- await upload.abort().catch(() => void 0);
1182
+ await upload2.abort().catch(() => void 0);
1183
1183
  if (admission.status === 409 && auth.storage) {
1184
1184
  const winner = await readMultipartReservation(bucket, auth.storage, requestId);
1185
1185
  if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
@@ -1189,11 +1189,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
1189
1189
  return admission;
1190
1190
  }
1191
1191
  if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
1192
- await upload.abort().catch(() => void 0);
1192
+ await upload2.abort().catch(() => void 0);
1193
1193
  await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
1194
1194
  return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
1195
1195
  }
1196
- return multipartSessionJson(upload.uploadId, located.subkey, requestId);
1196
+ return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
1197
1197
  }
1198
1198
  async function abandonMultipart(bucket, session, auth, reservation) {
1199
1199
  let result = "aborted";
@@ -1211,12 +1211,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
1211
1211
  }
1212
1212
  return result;
1213
1213
  }
1214
- async function multipartPart(request, url, bucket, prefix, excludedPrefixes, auth) {
1214
+ async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
1215
1215
  const refuse = async (response) => {
1216
- await request.body?.cancel().catch(() => void 0);
1216
+ await request2.body?.cancel().catch(() => void 0);
1217
1217
  return response;
1218
1218
  };
1219
- const declaredBytes = declaredLength(request, "Part uploads");
1219
+ const declaredBytes = declaredLength(request2, "Part uploads");
1220
1220
  if (declaredBytes instanceof Response) return refuse(declaredBytes);
1221
1221
  const tooBig = overPartBound(declaredBytes);
1222
1222
  if (tooBig) return refuse(tooBig);
@@ -1228,7 +1228,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
1228
1228
  }
1229
1229
  const session = resolveSession(url, prefix, excludedPrefixes);
1230
1230
  if (session instanceof Response) return refuse(session);
1231
- if (!request.body) return fail2(400, "A part body is required", "bad_request");
1231
+ if (!request2.body) return fail2(400, "A part body is required", "bad_request");
1232
1232
  const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
1233
1233
  if (reservation === "invalid") {
1234
1234
  return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
@@ -1270,8 +1270,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
1270
1270
  if (refreshed === "busy") {
1271
1271
  return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
1272
1272
  }
1273
- const upload = bucket.resumeMultipartUpload(session.key, session.uploadId);
1274
- const stored = await streamPart(upload, partNumber, request.body, declaredBytes);
1273
+ const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
1274
+ const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
1275
1275
  if (!stored.ok) {
1276
1276
  if (r2ErrorCode(stored.error) === 10024 && reservation) {
1277
1277
  await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
@@ -1327,8 +1327,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
1327
1327
  const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
1328
1328
  return storedFileJson(session.key, prefix, name, url, scope);
1329
1329
  }
1330
- async function multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
1331
- const body = await readControlJson(request);
1330
+ async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
1331
+ const body = await readControlJson(request2);
1332
1332
  if (body instanceof Response) return body;
1333
1333
  const session = resolveSession(url, prefix, excludedPrefixes);
1334
1334
  if (session instanceof Response) return session;
@@ -1487,7 +1487,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
1487
1487
  { headers: CORS_HEADERS2 }
1488
1488
  );
1489
1489
  }
1490
- async function handleDownload(request, bucket, key, scope) {
1490
+ async function handleDownload(request2, bucket, key, scope) {
1491
1491
  const object = await bucket.get(key);
1492
1492
  if (!object) {
1493
1493
  return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS2 });
@@ -1509,7 +1509,7 @@ async function handleDownload(request, bucket, key, scope) {
1509
1509
  contentDisposition(originalName, mustDownload ? "attachment" : "inline")
1510
1510
  );
1511
1511
  if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
1512
- if (scope === "app" && request.headers.get("if-none-match") === object.httpEtag) {
1512
+ if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
1513
1513
  return new Response(null, { status: 304, headers });
1514
1514
  }
1515
1515
  return new Response(object.body, { headers });
@@ -2075,8 +2075,8 @@ async function composioTools(env, options) {
2075
2075
  if (!list.ok || !list.success) {
2076
2076
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
2077
2077
  }
2078
- const listItems = list.data?.items;
2079
- const items = Array.isArray(listItems) ? listItems : [];
2078
+ const listItems2 = list.data?.items;
2079
+ const items = Array.isArray(listItems2) ? listItems2 : [];
2080
2080
  const tools = {};
2081
2081
  for (const item of items) {
2082
2082
  if (!item?.slug) continue;
@@ -2739,18 +2739,272 @@ var COST_RATES = {
2739
2739
  queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
2740
2740
  /** USD per stored dimension per month. */
2741
2741
  storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
2742
+ },
2743
+ aiSearch: {
2744
+ ingestPerToken: 0.75 / 1e6,
2745
+ ingestImagePerToken: 0.5 / 1e6,
2746
+ storagePerByteMonth: 2 / 1e9,
2747
+ hybridOrSemanticPerQuery: 0.75 / 1e3,
2748
+ fulltextPerQuery: 0.1 / 1e3
2742
2749
  }
2743
2750
  };
2744
2751
  function priceBindingUsageEvent(kind, op, units) {
2752
+ if (!Number.isFinite(units) || units < 0) return 0;
2745
2753
  if (kind === "ai" && op === "input") {
2746
2754
  return units * COST_RATES.ai.embedInputPerChar;
2747
2755
  }
2748
2756
  if (kind === "vectorize" && op === "query") {
2749
2757
  return units * COST_RATES.vectorize.queriedPerDim;
2750
2758
  }
2759
+ if (kind === "ai_search") {
2760
+ if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
2761
+ if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
2762
+ if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
2763
+ if (op === "search-hybrid" || op === "search-semantic")
2764
+ return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
2765
+ if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
2766
+ }
2751
2767
  return 0;
2752
2768
  }
2753
2769
 
2770
+ // src/server/utils/knowledge-base.ts
2771
+ var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
2772
+ var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
2773
+ var MAX_QUERY_CHARS = 4096;
2774
+ function hasControlChar(value) {
2775
+ for (let i = 0; i < value.length; i++) {
2776
+ const code = value.charCodeAt(i);
2777
+ if (code < 32 || code === 127) return true;
2778
+ }
2779
+ return false;
2780
+ }
2781
+ var KnowledgeError = class extends Error {
2782
+ constructor(status, code, message, uploadedItems) {
2783
+ super(message);
2784
+ this.status = status;
2785
+ this.code = code;
2786
+ this.uploadedItems = uploadedItems;
2787
+ this.name = "KnowledgeError";
2788
+ }
2789
+ };
2790
+ function normalizeKnowledgeFolder(folder) {
2791
+ if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
2792
+ throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
2793
+ const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
2794
+ if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
2795
+ throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
2796
+ const parts = withoutTrailingSlash.split("/");
2797
+ if (parts.some((part) => !part || part === "." || part === ".."))
2798
+ throw new KnowledgeError(
2799
+ 400,
2800
+ "invalid_folder",
2801
+ "Folder cannot contain empty, dot, or parent segments"
2802
+ );
2803
+ const normalized = `${parts.join("/")}/`;
2804
+ if (normalized.length > 128)
2805
+ throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
2806
+ return normalized;
2807
+ }
2808
+ function validateKnowledgeFilename(name) {
2809
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
2810
+ throw new KnowledgeError(
2811
+ 400,
2812
+ "invalid_filename",
2813
+ "File name must be a non-empty base name without path separators"
2814
+ );
2815
+ }
2816
+ }
2817
+ function folderFilter(folder) {
2818
+ const value = normalizeKnowledgeFolder(folder);
2819
+ return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
2820
+ }
2821
+ function knowledge(env) {
2822
+ const add = (file, options = {}) => addFile(env, file, options);
2823
+ const list = (options = {}) => listItems(env, options);
2824
+ const search = (query, options = {}) => searchItems(env, query, options);
2825
+ return {
2826
+ add,
2827
+ list,
2828
+ search,
2829
+ async remove(itemId) {
2830
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
2831
+ throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
2832
+ await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
2833
+ },
2834
+ scoped({ folder }) {
2835
+ const fixed = normalizeKnowledgeFolder(folder);
2836
+ return {
2837
+ add: (file) => add(file, { folder: fixed }),
2838
+ list: (options = {}) => list({ ...options, folder: fixed }),
2839
+ search: (query, options = {}) => search(query, { ...options, folder: fixed })
2840
+ };
2841
+ }
2842
+ };
2843
+ }
2844
+ async function addFile(env, file, options) {
2845
+ validateKnowledgeFilename(file.name);
2846
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
2847
+ if ((folder?.length ?? 0) + file.name.length > 128)
2848
+ throw new KnowledgeError(
2849
+ 400,
2850
+ "invalid_key",
2851
+ "Folder and filename together exceed the 128-character AI Search key limit"
2852
+ );
2853
+ if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
2854
+ if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
2855
+ throw new KnowledgeError(
2856
+ 413,
2857
+ "file_too_large",
2858
+ "This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
2859
+ );
2860
+ const bytes = new Uint8Array(await file.arrayBuffer());
2861
+ const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
2862
+ const uploaded = [];
2863
+ try {
2864
+ for (let i = 0; i < parts.length; i++) {
2865
+ const bytes2 = parts[i];
2866
+ const contents = bytes2.buffer.slice(
2867
+ bytes2.byteOffset,
2868
+ bytes2.byteOffset + bytes2.byteLength
2869
+ );
2870
+ const part = new File([contents], partName(file.name, i + 1), {
2871
+ type: file.type || "text/plain"
2872
+ });
2873
+ if ((folder?.length ?? 0) + part.name.length > 128)
2874
+ throw new KnowledgeError(
2875
+ 400,
2876
+ "invalid_key",
2877
+ "Folder and split filename together exceed the 128-character AI Search key limit"
2878
+ );
2879
+ uploaded.push(...(await upload(env, part, folder)).items);
2880
+ }
2881
+ } catch (error) {
2882
+ if (error instanceof KnowledgeError)
2883
+ throw new KnowledgeError(error.status, error.code, error.message, uploaded);
2884
+ throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
2885
+ }
2886
+ return { items: uploaded };
2887
+ }
2888
+ function splitUtf8(bytes, max) {
2889
+ const out = [];
2890
+ for (let start = 0; start < bytes.length; ) {
2891
+ let end = Math.min(start + max, bytes.length);
2892
+ if (end < bytes.length) {
2893
+ let cut = end;
2894
+ while (cut > start && (bytes[cut] & 192) === 128) cut--;
2895
+ for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
2896
+ if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
2897
+ cut = i + 1;
2898
+ break;
2899
+ }
2900
+ end = cut > start ? cut : end;
2901
+ }
2902
+ out.push(bytes.slice(start, end));
2903
+ start = end;
2904
+ }
2905
+ return out;
2906
+ }
2907
+ function partName(name, n) {
2908
+ const dot = name.lastIndexOf(".");
2909
+ return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
2910
+ }
2911
+ async function upload(env, file, folder) {
2912
+ const body = new FormData();
2913
+ body.append("file", file);
2914
+ if (folder) body.append("folder", folder);
2915
+ return await request(env, "/api/knowledge/items", {
2916
+ method: "POST",
2917
+ body
2918
+ });
2919
+ }
2920
+ async function listItems(env, options) {
2921
+ if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
2922
+ throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
2923
+ if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
2924
+ throw new KnowledgeError(
2925
+ 400,
2926
+ "invalid_per_page",
2927
+ "List perPage must be an integer from 1 to 50"
2928
+ );
2929
+ if (options.search !== void 0 && options.search.length > 256)
2930
+ throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
2931
+ if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
2932
+ throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
2933
+ const q = new URLSearchParams();
2934
+ if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
2935
+ if (options.page !== void 0) q.set("page", String(options.page));
2936
+ if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
2937
+ if (options.status) q.set("status", options.status);
2938
+ if (options.search) q.set("search", options.search);
2939
+ return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
2940
+ }
2941
+ async function searchItems(env, query, options) {
2942
+ const normalizedQuery = query.trim();
2943
+ if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
2944
+ if (normalizedQuery.length > MAX_QUERY_CHARS)
2945
+ throw new KnowledgeError(
2946
+ 400,
2947
+ "invalid_query",
2948
+ `Search query cannot exceed ${MAX_QUERY_CHARS} characters`
2949
+ );
2950
+ if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
2951
+ throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
2952
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
2953
+ throw new KnowledgeError(
2954
+ 400,
2955
+ "invalid_limit",
2956
+ "Search limit must be an integer from 1 through 50"
2957
+ );
2958
+ if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
2959
+ throw new KnowledgeError(
2960
+ 400,
2961
+ "invalid_match_threshold",
2962
+ "Match threshold must be between 0 and 1"
2963
+ );
2964
+ if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
2965
+ throw new KnowledgeError(
2966
+ 400,
2967
+ "invalid_query_rewrite",
2968
+ "queryRewrite must be a boolean when provided"
2969
+ );
2970
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
2971
+ return await request(env, "/api/knowledge/search", {
2972
+ method: "POST",
2973
+ headers: { "content-type": "application/json" },
2974
+ body: JSON.stringify({
2975
+ query: normalizedQuery,
2976
+ mode: options.mode ?? "hybrid",
2977
+ limit: options.limit ?? 10,
2978
+ queryRewrite: options.queryRewrite ?? false,
2979
+ ...options.matchThreshold !== void 0 && {
2980
+ matchThreshold: options.matchThreshold
2981
+ },
2982
+ ...folder && { folder }
2983
+ })
2984
+ });
2985
+ }
2986
+ async function request(env, path, init = {}) {
2987
+ const headers = new Headers(init.headers);
2988
+ appendAppIdentity(headers, env);
2989
+ const response = await apiWorkerFetch(env, path, { ...init, headers });
2990
+ const text = await response.text();
2991
+ let data;
2992
+ try {
2993
+ data = text ? JSON.parse(text) : void 0;
2994
+ } catch {
2995
+ data = void 0;
2996
+ }
2997
+ if (!response.ok) {
2998
+ const body = data && typeof data === "object" ? data : {};
2999
+ throw new KnowledgeError(
3000
+ response.status,
3001
+ typeof body.code === "string" ? body.code : "knowledge_request_failed",
3002
+ typeof body.error === "string" ? body.error : "Knowledge request failed"
3003
+ );
3004
+ }
3005
+ return data;
3006
+ }
3007
+
2754
3008
  // src/server/utils/d1-migrations.ts
2755
3009
  var META_TABLE = "_dpc_migrations";
2756
3010
  var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
@@ -2796,13 +3050,18 @@ function decodeRoomIdentityHeader(value) {
2796
3050
  }
2797
3051
 
2798
3052
  // src/server/rooms/base-room.ts
2799
- function connectionAttachmentFromRequest(request) {
3053
+ function connectionAttachmentFromRequest(request2) {
3054
+ if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
3055
+ console.warn(
3056
+ "[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."
3057
+ );
3058
+ }
2800
3059
  return {
2801
- userId: decodeRoomIdentityHeader(request.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
2802
- userName: decodeRoomIdentityHeader(request.headers.get("x-user-name")) || "Anonymous",
2803
- userEmail: decodeRoomIdentityHeader(request.headers.get("x-user-email")) || "",
2804
- userImageUrl: decodeRoomIdentityHeader(request.headers.get("x-user-image-url")),
2805
- role: decodeRoomIdentityHeader(request.headers.get("x-user-role"))
3060
+ userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
3061
+ userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
3062
+ userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
3063
+ userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
3064
+ role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
2806
3065
  };
2807
3066
  }
2808
3067
  var BaseRoom = class {
@@ -2818,15 +3077,15 @@ var BaseRoom = class {
2818
3077
  // ==========================================================================
2819
3078
  // HTTP Entry Point
2820
3079
  // ==========================================================================
2821
- async fetch(request) {
2822
- const url = new URL(request.url);
2823
- const internal = await this.handleInternalRequest(request, url);
3080
+ async fetch(request2) {
3081
+ const url = new URL(request2.url);
3082
+ const internal = await this.handleInternalRequest(request2, url);
2824
3083
  if (internal) return internal;
2825
- if (request.headers.get("Upgrade") === "websocket") {
2826
- return this.handleWebSocketUpgrade(request, url);
3084
+ if (request2.headers.get("Upgrade") === "websocket") {
3085
+ return this.handleWebSocketUpgrade(request2, url);
2827
3086
  }
2828
3087
  if (this.onRequest) {
2829
- return this.onRequest(request);
3088
+ return this.onRequest(request2);
2830
3089
  }
2831
3090
  return new Response("Not Found", { status: 404 });
2832
3091
  }
@@ -2847,11 +3106,11 @@ var BaseRoom = class {
2847
3106
  * See the security note on `fetch()`: this path is only reachable via DO
2848
3107
  * stub fetch from the app worker, never from the public internet.
2849
3108
  */
2850
- async handleInternalRequest(request, url) {
2851
- if (request.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
3109
+ async handleInternalRequest(request2, url) {
3110
+ if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
2852
3111
  let options;
2853
3112
  try {
2854
- const text = await request.text();
3113
+ const text = await request2.text();
2855
3114
  if (text) options = JSON.parse(text);
2856
3115
  } catch {
2857
3116
  }
@@ -2863,11 +3122,11 @@ var BaseRoom = class {
2863
3122
  // ==========================================================================
2864
3123
  // WebSocket Connection
2865
3124
  // ==========================================================================
2866
- async handleWebSocketUpgrade(request, _url) {
3125
+ async handleWebSocketUpgrade(request2, _url) {
2867
3126
  const pair = new WebSocketPair();
2868
3127
  const [client, server] = Object.values(pair);
2869
3128
  this.state.acceptWebSocket(server);
2870
- const attachment = connectionAttachmentFromRequest(request);
3129
+ const attachment = connectionAttachmentFromRequest(request2);
2871
3130
  const augmented = await this.onConnect(server, attachment) ?? attachment;
2872
3131
  server.serializeAttachment(augmented);
2873
3132
  return new Response(null, { status: 101, webSocket: client });
@@ -3103,7 +3362,8 @@ function dataToColumnValues(data, columns) {
3103
3362
  return values;
3104
3363
  }
3105
3364
  function coerceValue(value, storage, interpretation) {
3106
- if (value === null || value === void 0 || value === "") return null;
3365
+ if (value === null || value === void 0) return null;
3366
+ if (value === "") return storage === "text" ? "" : null;
3107
3367
  if (storage === "text") {
3108
3368
  if (interpretation.kind === "json") {
3109
3369
  if (typeof value === "string") return value;
@@ -3137,7 +3397,7 @@ function coerceValue(value, storage, interpretation) {
3137
3397
  }
3138
3398
  if (interpretation.kind === "date" || interpretation.kind === "datetime") {
3139
3399
  const d = new Date(s);
3140
- if (!isNaN(d.getTime())) return Math.floor(d.getTime() / 1e3);
3400
+ if (!isNaN(d.getTime())) return epochSeconds(d);
3141
3401
  return s;
3142
3402
  }
3143
3403
  const cleaned = s.replace(/[$€£¥,\s]/g, "");
@@ -3145,6 +3405,9 @@ function coerceValue(value, storage, interpretation) {
3145
3405
  if (!isNaN(n) && isFinite(n)) return n;
3146
3406
  return s;
3147
3407
  }
3408
+ function epochSeconds(date = /* @__PURE__ */ new Date()) {
3409
+ return Math.floor(date.getTime() / 1e3);
3410
+ }
3148
3411
  function buildTableSelect(collectionName, columns) {
3149
3412
  const tbl = collectionTableName(collectionName);
3150
3413
  const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
@@ -3579,6 +3842,9 @@ var serverBuild = {
3579
3842
  function getResolvedColumns(schema) {
3580
3843
  return (schema.columns ?? []).map(resolveColumn);
3581
3844
  }
3845
+ function triggerTimestamp(col) {
3846
+ return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
3847
+ }
3582
3848
  function systemManagedColumnError(column, isUpdate) {
3583
3849
  const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
3584
3850
  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.`;
@@ -3697,7 +3963,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
3697
3963
  if (!isUpdate) {
3698
3964
  if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
3699
3965
  if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
3700
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
3966
+ mergedData[col.name] = triggerTimestamp(col);
3701
3967
  }
3702
3968
  }
3703
3969
  } else {
@@ -3705,7 +3971,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
3705
3971
  const newVal = mergedData[triggerField];
3706
3972
  if (newVal !== oldVal) {
3707
3973
  if (triggerValue === void 0 || newVal === triggerValue) {
3708
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
3974
+ mergedData[col.name] = triggerTimestamp(col);
3709
3975
  }
3710
3976
  }
3711
3977
  }
@@ -3750,27 +4016,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
3750
4016
  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)) {
3751
4017
  return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
3752
4018
  }
3753
- if (current !== void 0) finalData[col.name] = current;
4019
+ if (current !== void 0)
4020
+ finalData[col.name] = current;
3754
4021
  else delete finalData[col.name];
3755
4022
  }
3756
4023
  }
3757
4024
  const colValues = dataToColumnValues(finalData, columns);
3758
4025
  const now = (/* @__PURE__ */ new Date()).toISOString();
3759
4026
  const tbl = collectionTableName(collection);
3760
- if (!isUpdate && schema.uniqueOn && schema.uniqueOn.length > 0) {
3761
- const uniqueWhere = schema.uniqueOn.map((fieldName) => {
3762
- const colSqlId = columnId(fieldName);
3763
- return `"${colSqlId}" = ?`;
3764
- });
3765
- const uniqueParams = schema.uniqueOn.map((fieldName) => {
3766
- const val = finalData[fieldName];
3767
- return val !== void 0 ? val : null;
4027
+ if (schema.uniqueOn && schema.uniqueOn.length > 0) {
4028
+ const uniqueColumns = schema.uniqueOn.map((fieldName) => {
4029
+ const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
4030
+ return { id, value: colValues[id] ?? null };
3768
4031
  });
3769
- const existing2 = ctx.sql.exec(
3770
- `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")} LIMIT 1`,
4032
+ const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
4033
+ const uniqueParams = uniqueColumns.map(({ value }) => value);
4034
+ if (isUpdate) uniqueParams.push(recordId);
4035
+ const conflict = ctx.sql.exec(
4036
+ `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
3771
4037
  ...uniqueParams
3772
4038
  );
3773
- if (existing2.toArray().length > 0) {
4039
+ if (conflict.toArray().length > 0) {
3774
4040
  const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
3775
4041
  return {
3776
4042
  success: false,
@@ -3836,21 +4102,22 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
3836
4102
  notifyTeamMembershipChange(ctx, collection, record);
3837
4103
  return { success: true, data: { deleted: true } };
3838
4104
  }
3839
- function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
3840
- const resolved = resolveCollection(ctx, collection);
3841
- if (!resolved.ok) return { success: false, error: resolved.error };
3842
- const schema = resolved.schema;
3843
- if (!schema) {
4105
+ function refuseUnknownWhere(collection, schema, where) {
4106
+ const columns = (schema?.columns ?? []).map(resolveColumn);
4107
+ const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
4108
+ if (typeof where !== "object" || where === null || Array.isArray(where)) {
4109
+ const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
3844
4110
  return {
3845
4111
  success: false,
3846
- error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
4112
+ error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
3847
4113
  };
3848
4114
  }
3849
- if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
3850
- return { success: false, error: "Missing required param: where (must match at least one field)" };
4115
+ if (!schema && Object.keys(where).length > 0) {
4116
+ return {
4117
+ success: false,
4118
+ error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
4119
+ };
3851
4120
  }
3852
- const columns = (schema.columns ?? []).map(resolveColumn);
3853
- const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
3854
4121
  const unknown = Object.keys(where).filter(
3855
4122
  (key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
3856
4123
  );
@@ -3867,6 +4134,26 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
3867
4134
  error: `where values must be primitives (equality only): ${nonPrimitive.map(([k]) => k).join(", ")}`
3868
4135
  };
3869
4136
  }
4137
+ return null;
4138
+ }
4139
+ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRbac = false) {
4140
+ const resolved = resolveCollection(ctx, collection);
4141
+ if (!resolved.ok) return { success: false, error: resolved.error };
4142
+ const schema = resolved.schema;
4143
+ if (!schema) {
4144
+ return {
4145
+ success: false,
4146
+ error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
4147
+ };
4148
+ }
4149
+ const whereRefusal = refuseUnknownWhere(collection, schema, where);
4150
+ if (whereRefusal) return whereRefusal;
4151
+ if (Object.keys(where).length === 0) {
4152
+ return {
4153
+ success: false,
4154
+ error: "Missing required param: where (must match at least one field)"
4155
+ };
4156
+ }
3870
4157
  if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
3871
4158
  return { success: false, error: "limit must be a finite number" };
3872
4159
  }
@@ -3874,32 +4161,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
3874
4161
  Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
3875
4162
  MAX_DELETE_WHERE_LIMIT
3876
4163
  );
3877
- const scan = { capped: false };
3878
4164
  const matched = executeQuery(
3879
4165
  ctx,
3880
4166
  { collection, where, limit: page },
3881
4167
  userId,
3882
4168
  userRole,
3883
- skipUserRbac,
3884
- scan
4169
+ true
3885
4170
  );
3886
- if (scan.capped && matched.length < page) {
3887
- return {
3888
- success: false,
3889
- 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`
3890
- };
3891
- }
3892
- if (!skipUserRbac && schema) {
4171
+ if (!skipUserRbac) {
3893
4172
  const permCtx = ctx.getPermissionContext();
3894
4173
  for (const record of matched) {
3895
4174
  if (!canDelete(schema, userRole, record, userId, permCtx)) {
3896
- return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` };
4175
+ return {
4176
+ success: false,
4177
+ error: `DELETE DENIED: role=${userRole}, collection=${collection}`
4178
+ };
3897
4179
  }
3898
4180
  }
3899
4181
  }
3900
4182
  let deleted = 0;
3901
4183
  for (const record of matched) {
3902
- if (deleteRecord(ctx, collection, record.recordId, userId, userRole, skipUserRbac).success) {
4184
+ if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
3903
4185
  deleted++;
3904
4186
  }
3905
4187
  }
@@ -4598,17 +4880,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
4598
4880
  );
4599
4881
  return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
4600
4882
  }
4601
- function rosterFor(ctx, schema, records, publicRoster, attachment) {
4602
- if (isAnonymousUserId(attachment.userId)) return [];
4603
- if (attachment.role === "admin") {
4883
+ function rosterFor(ctx, schema, records, publicRoster, userId, role) {
4884
+ if (!userId || isAnonymousUserId(userId)) return [];
4885
+ if (role === "admin") {
4604
4886
  const permissionContext = ctx.getPermissionContext();
4605
- return records.filter((record) => canRead(schema, "admin", record, attachment.userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
4887
+ return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
4606
4888
  }
4607
- if (!getRolePermissions(schema, attachment.role).read) return [];
4889
+ if (!getRolePermissions(schema, role).read) return [];
4608
4890
  if (schema.roster === "read-policy") {
4609
4891
  const permissionContext = ctx.getPermissionContext();
4610
4892
  const readable = new Set(
4611
- records.filter((record) => canRead(schema, attachment.role, record, attachment.userId, permissionContext)).map((record) => record.recordId)
4893
+ records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
4612
4894
  );
4613
4895
  return publicRoster.filter((user) => readable.has(user.id));
4614
4896
  }
@@ -4619,17 +4901,18 @@ function publicIdentities(records) {
4619
4901
  id: recordId,
4620
4902
  name: data.name,
4621
4903
  ...data.imageUrl ? { imageUrl: data.imageUrl } : {},
4622
- role: data.role
4904
+ role: data.role,
4905
+ lastSeenAt: data.lastSeenAt
4623
4906
  }));
4624
4907
  }
4625
- function handleUserList(ctx, ws, attachment) {
4908
+ function rosterForCaller(ctx, userId, role) {
4626
4909
  const schema = ctx.schemaRegistry.get("users");
4627
- if (!schema) {
4628
- ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
4629
- return;
4630
- }
4910
+ if (!schema) return [];
4631
4911
  const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
4632
- const users = rosterFor(ctx, schema, records, publicIdentities(records), attachment);
4912
+ return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
4913
+ }
4914
+ function handleUserList(ctx, ws, attachment) {
4915
+ const users = rosterForCaller(ctx, attachment.userId, attachment.role);
4633
4916
  ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
4634
4917
  }
4635
4918
  function broadcastUserList(ctx, except) {
@@ -4644,7 +4927,9 @@ function broadcastUserList(ctx, except) {
4644
4927
  if (!attachment) continue;
4645
4928
  ctx.send(other, {
4646
4929
  type: MSG.USER_LIST,
4647
- payload: { users: rosterFor(ctx, schema, records, publicRoster, attachment) }
4930
+ payload: {
4931
+ users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
4932
+ }
4648
4933
  });
4649
4934
  }
4650
4935
  }
@@ -4697,8 +4982,8 @@ var CORS_HEADERS3 = {
4697
4982
  "Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
4698
4983
  "Content-Type": "application/json"
4699
4984
  };
4700
- async function handleToolsRequest(ctx, request, path) {
4701
- if (request.method === "OPTIONS") {
4985
+ async function handleToolsRequest(ctx, request2, path) {
4986
+ if (request2.method === "OPTIONS") {
4702
4987
  return new Response(null, { headers: CORS_HEADERS3 });
4703
4988
  }
4704
4989
  if (path === "tools/list") {
@@ -4715,15 +5000,15 @@ async function handleToolsRequest(ctx, request, path) {
4715
5000
  }
4716
5001
  return Response.json({ tool: tool2 }, { headers: CORS_HEADERS3 });
4717
5002
  }
4718
- if (path.startsWith("tools/execute") && request.method === "POST") {
4719
- return handleToolExecute(ctx, request);
5003
+ if (path.startsWith("tools/execute") && request2.method === "POST") {
5004
+ return handleToolExecute(ctx, request2);
4720
5005
  }
4721
5006
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS3 });
4722
5007
  }
4723
- async function handleToolExecute(ctx, request) {
5008
+ async function handleToolExecute(ctx, request2) {
4724
5009
  let body;
4725
5010
  try {
4726
- body = await request.json();
5011
+ body = await request2.json();
4727
5012
  } catch {
4728
5013
  return Response.json(
4729
5014
  { success: false, error: "Invalid JSON body" },
@@ -4737,8 +5022,8 @@ async function handleToolExecute(ctx, request) {
4737
5022
  { status: 400, headers: CORS_HEADERS3 }
4738
5023
  );
4739
5024
  }
4740
- const userId = request.headers.get("x-user-id") ?? "";
4741
- const isAppAction = request.headers.get("x-app-action")?.toLowerCase() === "true";
5025
+ const userId = request2.headers.get("x-user-id") ?? "";
5026
+ const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
4742
5027
  let userRole = "viewer";
4743
5028
  if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
4744
5029
  userRole = "admin";
@@ -4773,9 +5058,14 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
4773
5058
  if (!collection) return { success: false, error: "Missing required param: collection" };
4774
5059
  const resolved = resolveCollection(ctx, collection);
4775
5060
  if (!resolved.ok) return { success: false, error: resolved.error };
5061
+ if (params.where !== void 0) {
5062
+ const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
5063
+ if (refusal) return refusal;
5064
+ }
5065
+ const where = params.where;
4776
5066
  const query = {
4777
5067
  collection,
4778
- where: params.where,
5068
+ where,
4779
5069
  orderBy: params.orderBy,
4780
5070
  orderDir: params.orderDir,
4781
5071
  limit: params.limit
@@ -4881,8 +5171,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
4881
5171
  if (!user) return { success: false, error: "User not found" };
4882
5172
  return { success: true, data: { user } };
4883
5173
  }
5174
+ // The same roster the socket's `user.list` returns, for the same caller.
5175
+ // Reading the users table directly here handed any member every peer's
5176
+ // email regardless of the room's users policy, while their own socket
5177
+ // roster and `useQuery('users')` obeyed it.
5178
+ //
5179
+ // An app action (`X-App-Action`) is the app's own server-side code, which
5180
+ // is already the trust boundary and reads every other collection RBAC-off;
5181
+ // it keeps the full rows.
4884
5182
  case "user.list": {
4885
- const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
5183
+ const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
4886
5184
  return { success: true, data: { users } };
4887
5185
  }
4888
5186
  default:
@@ -5017,25 +5315,25 @@ var CORS_HEADERS4 = {
5017
5315
  "Access-Control-Allow-Headers": "Content-Type",
5018
5316
  "Content-Type": "application/json"
5019
5317
  };
5020
- async function handleApiRequest(ctx, request, url) {
5021
- if (request.method === "OPTIONS") {
5318
+ async function handleApiRequest(ctx, request2, url) {
5319
+ if (request2.method === "OPTIONS") {
5022
5320
  return new Response(null, { headers: CORS_HEADERS4 });
5023
5321
  }
5024
5322
  const path = url.pathname.replace("/api/", "");
5025
5323
  if (path.startsWith("tools/")) {
5026
- return handleToolsRequest(ctx, request, path);
5324
+ return handleToolsRequest(ctx, request2, path);
5027
5325
  }
5028
5326
  if (path === "debug/status") {
5029
5327
  return handleDebugStatus(ctx);
5030
5328
  }
5031
5329
  if (path === "debug/sql") {
5032
- return handleDebugSql(ctx, request, url);
5330
+ return handleDebugSql(ctx, request2, url);
5033
5331
  }
5034
5332
  if (path === "debug/user-role") {
5035
5333
  return handleDebugUserRole(ctx, url);
5036
5334
  }
5037
- if (path === "debug/set-role" && request.method === "POST") {
5038
- return handleDebugSetRole(ctx, request);
5335
+ if (path === "debug/set-role" && request2.method === "POST") {
5336
+ return handleDebugSetRole(ctx, request2);
5039
5337
  }
5040
5338
  if (path.startsWith("debug/records/")) {
5041
5339
  return handleDebugRecords(ctx, path);
@@ -5066,8 +5364,8 @@ function handleDebugUserRole(ctx, url) {
5066
5364
  role: user.role
5067
5365
  }, { headers: CORS_HEADERS4 });
5068
5366
  }
5069
- async function handleDebugSetRole(ctx, request) {
5070
- const body = await request.json();
5367
+ async function handleDebugSetRole(ctx, request2) {
5368
+ const body = await request2.json();
5071
5369
  if (!body.role) {
5072
5370
  return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS4 });
5073
5371
  }
@@ -5175,11 +5473,11 @@ function handleDebugQuery(ctx, url) {
5175
5473
  records
5176
5474
  }, { headers: CORS_HEADERS4 });
5177
5475
  }
5178
- async function handleDebugSql(ctx, request, url) {
5476
+ async function handleDebugSql(ctx, request2, url) {
5179
5477
  let sql;
5180
5478
  let params = [];
5181
- if (request.method === "POST") {
5182
- const body = await request.json();
5479
+ if (request2.method === "POST") {
5480
+ const body = await request2.json();
5183
5481
  sql = body.sql;
5184
5482
  params = body.params ?? [];
5185
5483
  } else {
@@ -5299,10 +5597,10 @@ var RecordRoom = class extends BaseRoom {
5299
5597
  // ============================================================================
5300
5598
  // HTTP Entry Point (overrides BaseRoom.fetch)
5301
5599
  // ============================================================================
5302
- async fetch(request) {
5303
- const url = new URL(request.url);
5600
+ async fetch(request2) {
5601
+ const url = new URL(request2.url);
5304
5602
  if (url.pathname.startsWith("/internal/")) {
5305
- const internal = await this.handleInternalRequest(request, url);
5603
+ const internal = await this.handleInternalRequest(request2, url);
5306
5604
  if (internal) return internal;
5307
5605
  }
5308
5606
  const fetchStart = Date.now();
@@ -5315,11 +5613,11 @@ var RecordRoom = class extends BaseRoom {
5315
5613
  if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
5316
5614
  return new Response("Not Found", { status: 404 });
5317
5615
  }
5318
- return handleApiRequest(this.createHandlerContext(), request, url);
5616
+ return handleApiRequest(this.createHandlerContext(), request2, url);
5319
5617
  }
5320
- if (request.headers.get("Upgrade") === "websocket") {
5618
+ if (request2.headers.get("Upgrade") === "websocket") {
5321
5619
  this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
5322
- return super.fetch(request);
5620
+ return super.fetch(request2);
5323
5621
  }
5324
5622
  return new Response("Not Found", { status: 404 });
5325
5623
  }
@@ -5901,9 +6199,9 @@ var CanvasRoom = class extends BaseRoom {
5901
6199
  // --------------------------------------------------------------------------
5902
6200
  // BaseRoom Lifecycle
5903
6201
  // --------------------------------------------------------------------------
5904
- async fetch(request) {
6202
+ async fetch(request2) {
5905
6203
  this.ensureInitialized();
5906
- return super.fetch(request);
6204
+ return super.fetch(request2);
5907
6205
  }
5908
6206
  onConnect(ws, user) {
5909
6207
  this.ensureInitialized();
@@ -6382,6 +6680,7 @@ function cronMatches(parsed, timezone, date) {
6382
6680
  }
6383
6681
 
6384
6682
  // src/server/rooms/cron-room.ts
6683
+ var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
6385
6684
  var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
6386
6685
  MSG.CRON_TRIGGER,
6387
6686
  MSG.CRON_PAUSE,
@@ -6456,9 +6755,15 @@ var CronRoom = class extends BaseRoom {
6456
6755
  // ==========================================================================
6457
6756
  // BaseRoom Lifecycle
6458
6757
  // ==========================================================================
6459
- async fetch(request) {
6758
+ async fetch(request2) {
6460
6759
  this.ensureInitialized();
6461
- return super.fetch(request);
6760
+ return super.fetch(request2);
6761
+ }
6762
+ async handleInternalRequest(request2, url) {
6763
+ if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
6764
+ return Response.json({ tasks: this.getTaskStates() });
6765
+ }
6766
+ return super.handleInternalRequest(request2, url);
6462
6767
  }
6463
6768
  onConnect(ws, user) {
6464
6769
  this.ensureInitialized();
@@ -6606,15 +6911,22 @@ var CronRoom = class extends BaseRoom {
6606
6911
  const start = Date.now();
6607
6912
  let success = true;
6608
6913
  let error;
6914
+ let thrown;
6609
6915
  try {
6610
6916
  await this.onTask(taskName);
6611
6917
  } catch (e) {
6612
6918
  success = false;
6919
+ thrown = e;
6613
6920
  error = e instanceof Error ? e.message : String(e);
6614
- console.error(`[CronRoom] Task "${taskName}" failed:`, e);
6615
6921
  }
6616
6922
  const durationMs = Date.now() - start;
6617
6923
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
6924
+ if (success) {
6925
+ console.log(`[cron] ${taskName} ok ${durationMs}ms`);
6926
+ } else {
6927
+ const stack = thrown instanceof Error ? thrown.stack : void 0;
6928
+ console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
6929
+ }
6618
6930
  this.sql.exec(
6619
6931
  `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
6620
6932
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -6723,6 +7035,19 @@ var CronRoom = class extends BaseRoom {
6723
7035
  );
6724
7036
  }
6725
7037
  };
7038
+ var armedCronRooms = /* @__PURE__ */ new Set();
7039
+ function armCronRoom(ctx, namespace, roomId, tasks) {
7040
+ if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
7041
+ armedCronRooms.add(roomId);
7042
+ ctx.waitUntil(
7043
+ namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
7044
+ if (!response.ok) armedCronRooms.delete(roomId);
7045
+ return response;
7046
+ }).catch(() => {
7047
+ armedCronRooms.delete(roomId);
7048
+ })
7049
+ );
7050
+ }
6726
7051
  function computeNextRunAt(task, from) {
6727
7052
  if (task.interval_minutes) {
6728
7053
  return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
@@ -6795,9 +7120,9 @@ var JobRoom = class extends BaseRoom {
6795
7120
  // ==========================================================================
6796
7121
  // BaseRoom Lifecycle
6797
7122
  // ==========================================================================
6798
- async fetch(request) {
7123
+ async fetch(request2) {
6799
7124
  this.ensureInitialized();
6800
- return super.fetch(request);
7125
+ return super.fetch(request2);
6801
7126
  }
6802
7127
  async onConnect(ws, user) {
6803
7128
  this.ensureInitialized();
@@ -6884,13 +7209,13 @@ var JobRoom = class extends BaseRoom {
6884
7209
  * Subclasses overriding `onRequest` should call `super.onRequest(req)`
6885
7210
  * for paths they don't handle.
6886
7211
  */
6887
- async onRequest(request) {
7212
+ async onRequest(request2) {
6888
7213
  this.ensureInitialized();
6889
- const url = new URL(request.url);
6890
- if (request.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
7214
+ const url = new URL(request2.url);
7215
+ if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
6891
7216
  return new Response("Not Found", { status: 404 });
6892
7217
  }
6893
- const body = await request.json().catch(() => null);
7218
+ const body = await request2.json().catch(() => null);
6894
7219
  if (!body || typeof body.type !== "string" || !body.type) {
6895
7220
  return new Response(JSON.stringify({ error: "type required" }), {
6896
7221
  status: 400,
@@ -7056,7 +7381,8 @@ var JobRoom = class extends BaseRoom {
7056
7381
  } catch (e) {
7057
7382
  outcome = "failed";
7058
7383
  errorMessage = e instanceof Error ? e.message : String(e);
7059
- console.error(`[JobRoom] Job "${row.type}" (${row.id}) failed:`, e);
7384
+ const stack = e instanceof Error ? e.stack : void 0;
7385
+ console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
7060
7386
  } finally {
7061
7387
  this.inFlight.delete(row.id);
7062
7388
  }
@@ -7370,7 +7696,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
7370
7696
  "kv_namespace",
7371
7697
  "vectorize",
7372
7698
  "r2_bucket",
7373
- "queue"
7699
+ "queue",
7700
+ "ai_search"
7374
7701
  ]);
7375
7702
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
7376
7703
  function isAutoProvision(b) {
@@ -7385,6 +7712,8 @@ function isAutoProvision(b) {
7385
7712
  return b.bucket_name === AUTO_PROVISION_SENTINEL;
7386
7713
  case "queue":
7387
7714
  return b.queue_name === AUTO_PROVISION_SENTINEL;
7715
+ case "ai_search":
7716
+ return b.instance_name === AUTO_PROVISION_SENTINEL;
7388
7717
  default:
7389
7718
  return false;
7390
7719
  }
@@ -7399,7 +7728,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
7399
7728
  "browser_rendering",
7400
7729
  "analytics_engine",
7401
7730
  "hyperdrive",
7402
- "ratelimit"
7731
+ "ratelimit",
7732
+ "ai_search"
7403
7733
  ]);
7404
7734
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
7405
7735
  "ASSETS",
@@ -7423,6 +7753,7 @@ function validateBindingManifest(manifest) {
7423
7753
  }
7424
7754
  const errors = [];
7425
7755
  const seenNames = /* @__PURE__ */ new Set();
7756
+ let aiSearchCount = 0;
7426
7757
  for (const entry of manifest) {
7427
7758
  if (!entry || typeof entry !== "object") {
7428
7759
  errors.push({
@@ -7453,14 +7784,23 @@ function validateBindingManifest(manifest) {
7453
7784
  }
7454
7785
  seenNames.add(name);
7455
7786
  const binding = entry;
7787
+ if (binding.type === "ai_search") aiSearchCount++;
7456
7788
  const fieldErr = requiredFieldError(binding);
7457
7789
  if (fieldErr) errors.push({ binding, reason: fieldErr });
7458
7790
  }
7791
+ if (aiSearchCount > 1) {
7792
+ errors.push({ reason: "Only one ai_search binding may be declared per app" });
7793
+ }
7459
7794
  if (errors.length) return { valid: false, errors };
7460
7795
  return { valid: true, bindings: manifest };
7461
7796
  }
7462
7797
  function requiredFieldError(b) {
7463
7798
  switch (b.type) {
7799
+ case "ai_search":
7800
+ if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
7801
+ return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
7802
+ }
7803
+ return null;
7464
7804
  case "vectorize": {
7465
7805
  if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
7466
7806
  if (b.index_name === AUTO_PROVISION_SENTINEL) {
@@ -7522,6 +7862,13 @@ function requiredFieldError(b) {
7522
7862
  }
7523
7863
  }
7524
7864
  function bindingManifestFromOutputConfig(outputConfig) {
7865
+ const aiSearchNamespaces = outputConfig.ai_search_namespaces;
7866
+ const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
7867
+ if (declaresAiSearchNamespace) {
7868
+ throw new Error(
7869
+ 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
7870
+ );
7871
+ }
7525
7872
  const out = [];
7526
7873
  const aiBinding = pluckString(outputConfig.ai, "binding");
7527
7874
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
@@ -7539,6 +7886,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
7539
7886
  ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
7540
7887
  });
7541
7888
  }
7889
+ for (const a of asObjectArray(outputConfig.ai_search)) {
7890
+ const name = pluckString(a, "binding");
7891
+ const instance_name = pluckString(a, "instance_name");
7892
+ if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
7893
+ }
7542
7894
  for (const r of asObjectArray(outputConfig.r2_buckets)) {
7543
7895
  const name = pluckString(r, "binding");
7544
7896
  const bucket_name = pluckString(r, "bucket_name");
@@ -8074,218 +8426,8 @@ var DIRECTORY_SCHEMAS = [
8074
8426
  postsSchema
8075
8427
  ];
8076
8428
 
8077
- // src/server/schemas/workspace.ts
8078
- var workspaceTeamsSchema = {
8079
- name: "teams",
8080
- columns: [
8081
- { name: "Name", storage: "text", interpretation: "plain" },
8082
- { name: "CreatedBy", storage: "text", interpretation: "plain" },
8083
- { name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
8084
- ],
8085
- teamField: "_rowId",
8086
- permissions: {
8087
- admin: { read: true, create: true, update: true, delete: true },
8088
- member: { read: "team", create: true, update: "team", delete: "own" }
8089
- }
8090
- };
8091
- var workspaceTeamMembersSchema = {
8092
- name: "team_members",
8093
- columns: [
8094
- { name: "TeamId", storage: "text", interpretation: "plain" },
8095
- { name: "UserId", storage: "text", interpretation: "plain" },
8096
- { name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
8097
- { name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
8098
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
8099
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
8100
- ],
8101
- teamField: "TeamId",
8102
- permissions: {
8103
- admin: { read: true, create: true, update: true, delete: true },
8104
- member: { read: "team", create: true, update: "team", delete: "team" }
8105
- }
8106
- };
8107
- var workspaceTasksSchema = {
8108
- name: "tasks",
8109
- columns: [
8110
- { name: "TeamId", storage: "text", interpretation: "plain" },
8111
- { name: "Title", storage: "text", interpretation: "plain" },
8112
- { name: "Notes", storage: "text", interpretation: "plain" },
8113
- { name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
8114
- { name: "CompletedAt", storage: "number", interpretation: "plain" },
8115
- { name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
8116
- { name: "DeletedAt", storage: "number", interpretation: "plain" },
8117
- { name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
8118
- { name: "DueDate", storage: "text", interpretation: "plain" },
8119
- { name: "ProjectId", storage: "text", interpretation: "plain" },
8120
- { name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
8121
- { name: "Order", storage: "number", interpretation: "plain" },
8122
- { name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
8123
- { name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
8124
- { name: "TagIds", storage: "text", interpretation: { kind: "json" } },
8125
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8126
- ],
8127
- teamField: "TeamId",
8128
- permissions: {
8129
- admin: { read: true, create: true, update: true, delete: true },
8130
- member: { read: "team", create: true, update: "team", delete: "team" },
8131
- viewer: { read: false, create: false, update: false, delete: false }
8132
- }
8133
- };
8134
- var workspaceProjectsSchema = {
8135
- name: "projects",
8136
- columns: [
8137
- { name: "TeamId", storage: "text", interpretation: "plain" },
8138
- { name: "Title", storage: "text", interpretation: "plain" },
8139
- { name: "Notes", storage: "text", interpretation: "plain" },
8140
- { name: "Color", storage: "text", interpretation: "plain" },
8141
- { name: "ParentId", storage: "text", interpretation: "plain" },
8142
- { name: "Order", storage: "number", interpretation: "plain" },
8143
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8144
- ],
8145
- teamField: "TeamId",
8146
- permissions: {
8147
- admin: { read: true, create: true, update: true, delete: true },
8148
- member: { read: "team", create: true, update: "team", delete: "team" },
8149
- viewer: { read: false, create: false, update: false, delete: false }
8150
- }
8151
- };
8152
- var workspaceTagsSchema = {
8153
- name: "tags",
8154
- columns: [
8155
- { name: "TeamId", storage: "text", interpretation: "plain" },
8156
- { name: "Name", storage: "text", interpretation: "plain" },
8157
- { name: "Color", storage: "text", interpretation: "plain" },
8158
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8159
- ],
8160
- teamField: "TeamId",
8161
- permissions: {
8162
- admin: { read: true, create: true, update: true, delete: true },
8163
- member: { read: "team", create: true, update: "team", delete: "team" },
8164
- viewer: { read: false, create: false, update: false, delete: false }
8165
- }
8166
- };
8167
- var workspacePeopleSchema = {
8168
- name: "people",
8169
- columns: [
8170
- { name: "Name", storage: "text", interpretation: "plain" },
8171
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
8172
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
8173
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
8174
- { name: "CompanyId", storage: "text", interpretation: "plain" },
8175
- { name: "Department", storage: "text", interpretation: "plain" },
8176
- { name: "Title", storage: "text", interpretation: "plain" },
8177
- { name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
8178
- { name: "Metadata", storage: "text", interpretation: { kind: "json" } }
8179
- ],
8180
- permissions: {
8181
- "*": { read: true, create: true, update: true, delete: false },
8182
- admin: { read: true, create: true, update: true, delete: true }
8183
- }
8184
- };
8185
- var workspaceTransactionsSchema = {
8186
- name: "transactions",
8187
- columns: [
8188
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
8189
- { name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
8190
- { name: "Currency", storage: "text", interpretation: "plain" },
8191
- { name: "Description", storage: "text", interpretation: "plain" },
8192
- { name: "Category", storage: "text", interpretation: "plain" },
8193
- { name: "CounterpartyRef", storage: "text", interpretation: "plain" },
8194
- { name: "OrderRef", storage: "text", interpretation: "plain" },
8195
- { name: "Account", storage: "text", interpretation: "plain" }
8196
- ],
8197
- permissions: {
8198
- "*": { read: true, create: true, update: true, delete: false },
8199
- admin: { read: true, create: true, update: true, delete: true }
8200
- }
8201
- };
8202
- var workspaceAccountsSchema = {
8203
- name: "accounts",
8204
- columns: [
8205
- { name: "Name", storage: "text", interpretation: "plain" },
8206
- { name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
8207
- { name: "Currency", storage: "text", interpretation: "plain" }
8208
- ],
8209
- permissions: {
8210
- "*": { read: true, create: true, update: true, delete: false },
8211
- admin: { read: true, create: true, update: true, delete: true }
8212
- }
8213
- };
8214
- var workspaceContentSharesSchema = {
8215
- name: "content_shares",
8216
- ownerField: "OwnerId",
8217
- columns: [
8218
- { name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
8219
- { name: "ContentId", storage: "text", interpretation: "plain" },
8220
- { name: "OwnerId", storage: "text", interpretation: "plain" },
8221
- { name: "OwnerName", storage: "text", interpretation: "plain" },
8222
- { name: "Title", storage: "text", interpretation: "plain" },
8223
- { name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
8224
- { name: "ShareTarget", storage: "text", interpretation: "plain" },
8225
- { name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
8226
- { name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
8227
- { name: "SharedBy", storage: "text", interpretation: "plain" },
8228
- { name: "SourceApp", storage: "text", interpretation: "plain" },
8229
- { name: "WordCount", storage: "number", interpretation: "plain" },
8230
- { name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
8231
- ],
8232
- permissions: {
8233
- "*": { read: true, create: true, update: true, delete: "own" },
8234
- admin: { read: true, create: true, update: true, delete: true }
8235
- }
8236
- };
8237
- var workspaceFormResponsesSchema = {
8238
- name: "form_responses",
8239
- columns: [
8240
- { name: "FormId", storage: "text", interpretation: "plain" },
8241
- { name: "FormTitle", storage: "text", interpretation: "plain" },
8242
- { name: "RespondentId", storage: "text", interpretation: "plain" },
8243
- { name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
8244
- { name: "RespondentName", storage: "text", interpretation: "plain" },
8245
- { name: "Data", storage: "text", interpretation: { kind: "json" } },
8246
- { name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
8247
- ],
8248
- permissions: {
8249
- "*": { read: true, create: true, update: false, delete: false },
8250
- admin: { read: true, create: true, update: true, delete: true }
8251
- }
8252
- };
8253
- var workspaceEmailHandlesSchema = {
8254
- name: "email_handles",
8255
- columns: [
8256
- { name: "UserId", storage: "text", interpretation: "plain" },
8257
- { name: "Handle", storage: "text", interpretation: "plain" },
8258
- { name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
8259
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
8260
- ],
8261
- uniqueOn: ["Handle"],
8262
- ownerField: "UserId",
8263
- permissions: {
8264
- "*": { read: true, create: true, update: "own", delete: false },
8265
- admin: { read: true, create: true, update: true, delete: true }
8266
- }
8267
- };
8268
- var WORKSPACE_SCHEMAS = [
8269
- workspaceTeamsSchema,
8270
- workspaceTeamMembersSchema,
8271
- workspaceTasksSchema,
8272
- workspaceProjectsSchema,
8273
- workspaceTagsSchema,
8274
- workspacePeopleSchema,
8275
- workspaceTransactionsSchema,
8276
- workspaceAccountsSchema,
8277
- workspaceContentSharesSchema,
8278
- workspaceFormResponsesSchema,
8279
- workspaceEmailHandlesSchema
8280
- ];
8281
-
8282
8429
  // src/server/schemas/shared-do.ts
8283
8430
  var GLOBAL_DO_TYPES = [
8284
- {
8285
- name: "workspace",
8286
- schemas: WORKSPACE_SCHEMAS,
8287
- description: "Shared workspace data (teams, tasks, projects, people, ledger)"
8288
- },
8289
8431
  {
8290
8432
  name: "conv",
8291
8433
  schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
@@ -8795,6 +8937,7 @@ export {
8795
8937
  GLOBAL_DO_TYPES,
8796
8938
  GLOBAL_DO_TYPE_NAMES,
8797
8939
  JobRoom,
8940
+ KnowledgeError,
8798
8941
  MAX_APP_FILE_BYTES,
8799
8942
  MAX_DELETE_WHERE_LIMIT,
8800
8943
  MAX_DEPLOY_ASSET_FILE_BYTES,
@@ -8823,13 +8966,13 @@ export {
8823
8966
  UPLOAD_PART_BYTES,
8824
8967
  USERS_COLUMNS,
8825
8968
  VOTING_SCHEMAS,
8826
- WORKSPACE_SCHEMAS,
8827
8969
  YjsRoom,
8828
8970
  analyzePermissions,
8829
8971
  apiWorkerFetch,
8830
8972
  appendMessage,
8831
8973
  applyAiToolDefaults,
8832
8974
  applySlidingWindow,
8975
+ armCronRoom,
8833
8976
  authWorkerFetch,
8834
8977
  bindingManifestFromOutputConfig,
8835
8978
  broadcastChange,
@@ -8864,7 +9007,9 @@ export {
8864
9007
  deleteChatCascade,
8865
9008
  deleteRecord,
8866
9009
  enqueueJob,
9010
+ epochSeconds,
8867
9011
  executeQuery,
9012
+ folderFilter,
8868
9013
  formatBytes,
8869
9014
  getAllUsers,
8870
9015
  getChat,
@@ -8894,6 +9039,7 @@ export {
8894
9039
  isTestAccountClaims,
8895
9040
  isTestAccountEmail,
8896
9041
  isTestAccountTier,
9042
+ knowledge,
8897
9043
  lintSchema,
8898
9044
  lintSchemaSet,
8899
9045
  lintSchemas,
@@ -8906,6 +9052,7 @@ export {
8906
9052
  mintAppId,
8907
9053
  mintUlid,
8908
9054
  noopPermissionContext,
9055
+ normalizeKnowledgeFolder,
8909
9056
  parseQuotaLimit,
8910
9057
  platformCorsPatterns,
8911
9058
  platformWorkerFetch,
@@ -8938,17 +9085,6 @@ export {
8938
9085
  validateAppName,
8939
9086
  validateBindingManifest,
8940
9087
  validateDoManifest,
8941
- verifyJwt,
8942
- workspaceAccountsSchema,
8943
- workspaceContentSharesSchema,
8944
- workspaceEmailHandlesSchema,
8945
- workspaceFormResponsesSchema,
8946
- workspacePeopleSchema,
8947
- workspaceProjectsSchema,
8948
- workspaceTagsSchema,
8949
- workspaceTasksSchema,
8950
- workspaceTeamMembersSchema,
8951
- workspaceTeamsSchema,
8952
- workspaceTransactionsSchema
9088
+ verifyJwt
8953
9089
  };
8954
9090
  //# sourceMappingURL=server.js.map