deepspace 0.23.2 → 0.24.1

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
@@ -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,
@@ -3837,8 +4103,21 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
3837
4103
  return { success: true, data: { deleted: true } };
3838
4104
  }
3839
4105
  function refuseUnknownWhere(collection, schema, where) {
3840
- const columns = (schema.columns ?? []).map(resolveColumn);
4106
+ const columns = (schema?.columns ?? []).map(resolveColumn);
3841
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;
4110
+ return {
4111
+ success: false,
4112
+ error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
4113
+ };
4114
+ }
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
+ };
4120
+ }
3842
4121
  const unknown = Object.keys(where).filter(
3843
4122
  (key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
3844
4123
  );
@@ -3867,11 +4146,14 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
3867
4146
  error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
3868
4147
  };
3869
4148
  }
3870
- if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
3871
- return { success: false, error: "Missing required param: where (must match at least one field)" };
3872
- }
3873
4149
  const whereRefusal = refuseUnknownWhere(collection, schema, where);
3874
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
+ }
3875
4157
  if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
3876
4158
  return { success: false, error: "limit must be a finite number" };
3877
4159
  }
@@ -3879,32 +4161,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
3879
4161
  Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
3880
4162
  MAX_DELETE_WHERE_LIMIT
3881
4163
  );
3882
- const scan = { capped: false };
3883
4164
  const matched = executeQuery(
3884
4165
  ctx,
3885
4166
  { collection, where, limit: page },
3886
4167
  userId,
3887
4168
  userRole,
3888
- skipUserRbac,
3889
- scan
4169
+ true
3890
4170
  );
3891
- if (scan.capped && matched.length < page) {
3892
- return {
3893
- success: false,
3894
- 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`
3895
- };
3896
- }
3897
- if (!skipUserRbac && schema) {
4171
+ if (!skipUserRbac) {
3898
4172
  const permCtx = ctx.getPermissionContext();
3899
4173
  for (const record of matched) {
3900
4174
  if (!canDelete(schema, userRole, record, userId, permCtx)) {
3901
- return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` };
4175
+ return {
4176
+ success: false,
4177
+ error: `DELETE DENIED: role=${userRole}, collection=${collection}`
4178
+ };
3902
4179
  }
3903
4180
  }
3904
4181
  }
3905
4182
  let deleted = 0;
3906
4183
  for (const record of matched) {
3907
- if (deleteRecord(ctx, collection, record.recordId, userId, userRole, skipUserRbac).success) {
4184
+ if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
3908
4185
  deleted++;
3909
4186
  }
3910
4187
  }
@@ -4603,17 +4880,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
4603
4880
  );
4604
4881
  return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
4605
4882
  }
4606
- function rosterFor(ctx, schema, records, publicRoster, attachment) {
4607
- if (isAnonymousUserId(attachment.userId)) return [];
4608
- if (attachment.role === "admin") {
4883
+ function rosterFor(ctx, schema, records, publicRoster, userId, role) {
4884
+ if (!userId || isAnonymousUserId(userId)) return [];
4885
+ if (role === "admin") {
4609
4886
  const permissionContext = ctx.getPermissionContext();
4610
- 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 }));
4611
4888
  }
4612
- if (!getRolePermissions(schema, attachment.role).read) return [];
4889
+ if (!getRolePermissions(schema, role).read) return [];
4613
4890
  if (schema.roster === "read-policy") {
4614
4891
  const permissionContext = ctx.getPermissionContext();
4615
4892
  const readable = new Set(
4616
- 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)
4617
4894
  );
4618
4895
  return publicRoster.filter((user) => readable.has(user.id));
4619
4896
  }
@@ -4624,17 +4901,18 @@ function publicIdentities(records) {
4624
4901
  id: recordId,
4625
4902
  name: data.name,
4626
4903
  ...data.imageUrl ? { imageUrl: data.imageUrl } : {},
4627
- role: data.role
4904
+ role: data.role,
4905
+ lastSeenAt: data.lastSeenAt
4628
4906
  }));
4629
4907
  }
4630
- function handleUserList(ctx, ws, attachment) {
4908
+ function rosterForCaller(ctx, userId, role) {
4631
4909
  const schema = ctx.schemaRegistry.get("users");
4632
- if (!schema) {
4633
- ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
4634
- return;
4635
- }
4910
+ if (!schema) return [];
4636
4911
  const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
4637
- 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);
4638
4916
  ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
4639
4917
  }
4640
4918
  function broadcastUserList(ctx, except) {
@@ -4649,7 +4927,9 @@ function broadcastUserList(ctx, except) {
4649
4927
  if (!attachment) continue;
4650
4928
  ctx.send(other, {
4651
4929
  type: MSG.USER_LIST,
4652
- payload: { users: rosterFor(ctx, schema, records, publicRoster, attachment) }
4930
+ payload: {
4931
+ users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
4932
+ }
4653
4933
  });
4654
4934
  }
4655
4935
  }
@@ -4702,8 +4982,8 @@ var CORS_HEADERS3 = {
4702
4982
  "Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
4703
4983
  "Content-Type": "application/json"
4704
4984
  };
4705
- async function handleToolsRequest(ctx, request, path) {
4706
- if (request.method === "OPTIONS") {
4985
+ async function handleToolsRequest(ctx, request2, path) {
4986
+ if (request2.method === "OPTIONS") {
4707
4987
  return new Response(null, { headers: CORS_HEADERS3 });
4708
4988
  }
4709
4989
  if (path === "tools/list") {
@@ -4720,15 +5000,15 @@ async function handleToolsRequest(ctx, request, path) {
4720
5000
  }
4721
5001
  return Response.json({ tool: tool2 }, { headers: CORS_HEADERS3 });
4722
5002
  }
4723
- if (path.startsWith("tools/execute") && request.method === "POST") {
4724
- return handleToolExecute(ctx, request);
5003
+ if (path.startsWith("tools/execute") && request2.method === "POST") {
5004
+ return handleToolExecute(ctx, request2);
4725
5005
  }
4726
5006
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS3 });
4727
5007
  }
4728
- async function handleToolExecute(ctx, request) {
5008
+ async function handleToolExecute(ctx, request2) {
4729
5009
  let body;
4730
5010
  try {
4731
- body = await request.json();
5011
+ body = await request2.json();
4732
5012
  } catch {
4733
5013
  return Response.json(
4734
5014
  { success: false, error: "Invalid JSON body" },
@@ -4742,8 +5022,8 @@ async function handleToolExecute(ctx, request) {
4742
5022
  { status: 400, headers: CORS_HEADERS3 }
4743
5023
  );
4744
5024
  }
4745
- const userId = request.headers.get("x-user-id") ?? "";
4746
- 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";
4747
5027
  let userRole = "viewer";
4748
5028
  if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
4749
5029
  userRole = "admin";
@@ -4778,17 +5058,11 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
4778
5058
  if (!collection) return { success: false, error: "Missing required param: collection" };
4779
5059
  const resolved = resolveCollection(ctx, collection);
4780
5060
  if (!resolved.ok) return { success: false, error: resolved.error };
4781
- const where = params.where;
4782
- if (where && typeof where === "object" && Object.keys(where).length > 0) {
4783
- if (!resolved.schema) {
4784
- return {
4785
- success: false,
4786
- error: `records.query: collection "${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
4787
- };
4788
- }
4789
- const refusal = refuseUnknownWhere(collection, resolved.schema, where);
5061
+ if (params.where !== void 0) {
5062
+ const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
4790
5063
  if (refusal) return refusal;
4791
5064
  }
5065
+ const where = params.where;
4792
5066
  const query = {
4793
5067
  collection,
4794
5068
  where,
@@ -4897,8 +5171,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
4897
5171
  if (!user) return { success: false, error: "User not found" };
4898
5172
  return { success: true, data: { user } };
4899
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.
4900
5182
  case "user.list": {
4901
- const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
5183
+ const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
4902
5184
  return { success: true, data: { users } };
4903
5185
  }
4904
5186
  default:
@@ -5033,25 +5315,25 @@ var CORS_HEADERS4 = {
5033
5315
  "Access-Control-Allow-Headers": "Content-Type",
5034
5316
  "Content-Type": "application/json"
5035
5317
  };
5036
- async function handleApiRequest(ctx, request, url) {
5037
- if (request.method === "OPTIONS") {
5318
+ async function handleApiRequest(ctx, request2, url) {
5319
+ if (request2.method === "OPTIONS") {
5038
5320
  return new Response(null, { headers: CORS_HEADERS4 });
5039
5321
  }
5040
5322
  const path = url.pathname.replace("/api/", "");
5041
5323
  if (path.startsWith("tools/")) {
5042
- return handleToolsRequest(ctx, request, path);
5324
+ return handleToolsRequest(ctx, request2, path);
5043
5325
  }
5044
5326
  if (path === "debug/status") {
5045
5327
  return handleDebugStatus(ctx);
5046
5328
  }
5047
5329
  if (path === "debug/sql") {
5048
- return handleDebugSql(ctx, request, url);
5330
+ return handleDebugSql(ctx, request2, url);
5049
5331
  }
5050
5332
  if (path === "debug/user-role") {
5051
5333
  return handleDebugUserRole(ctx, url);
5052
5334
  }
5053
- if (path === "debug/set-role" && request.method === "POST") {
5054
- return handleDebugSetRole(ctx, request);
5335
+ if (path === "debug/set-role" && request2.method === "POST") {
5336
+ return handleDebugSetRole(ctx, request2);
5055
5337
  }
5056
5338
  if (path.startsWith("debug/records/")) {
5057
5339
  return handleDebugRecords(ctx, path);
@@ -5082,8 +5364,8 @@ function handleDebugUserRole(ctx, url) {
5082
5364
  role: user.role
5083
5365
  }, { headers: CORS_HEADERS4 });
5084
5366
  }
5085
- async function handleDebugSetRole(ctx, request) {
5086
- const body = await request.json();
5367
+ async function handleDebugSetRole(ctx, request2) {
5368
+ const body = await request2.json();
5087
5369
  if (!body.role) {
5088
5370
  return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS4 });
5089
5371
  }
@@ -5191,11 +5473,11 @@ function handleDebugQuery(ctx, url) {
5191
5473
  records
5192
5474
  }, { headers: CORS_HEADERS4 });
5193
5475
  }
5194
- async function handleDebugSql(ctx, request, url) {
5476
+ async function handleDebugSql(ctx, request2, url) {
5195
5477
  let sql;
5196
5478
  let params = [];
5197
- if (request.method === "POST") {
5198
- const body = await request.json();
5479
+ if (request2.method === "POST") {
5480
+ const body = await request2.json();
5199
5481
  sql = body.sql;
5200
5482
  params = body.params ?? [];
5201
5483
  } else {
@@ -5315,10 +5597,10 @@ var RecordRoom = class extends BaseRoom {
5315
5597
  // ============================================================================
5316
5598
  // HTTP Entry Point (overrides BaseRoom.fetch)
5317
5599
  // ============================================================================
5318
- async fetch(request) {
5319
- const url = new URL(request.url);
5600
+ async fetch(request2) {
5601
+ const url = new URL(request2.url);
5320
5602
  if (url.pathname.startsWith("/internal/")) {
5321
- const internal = await this.handleInternalRequest(request, url);
5603
+ const internal = await this.handleInternalRequest(request2, url);
5322
5604
  if (internal) return internal;
5323
5605
  }
5324
5606
  const fetchStart = Date.now();
@@ -5331,11 +5613,11 @@ var RecordRoom = class extends BaseRoom {
5331
5613
  if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
5332
5614
  return new Response("Not Found", { status: 404 });
5333
5615
  }
5334
- return handleApiRequest(this.createHandlerContext(), request, url);
5616
+ return handleApiRequest(this.createHandlerContext(), request2, url);
5335
5617
  }
5336
- if (request.headers.get("Upgrade") === "websocket") {
5618
+ if (request2.headers.get("Upgrade") === "websocket") {
5337
5619
  this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
5338
- return super.fetch(request);
5620
+ return super.fetch(request2);
5339
5621
  }
5340
5622
  return new Response("Not Found", { status: 404 });
5341
5623
  }
@@ -5917,9 +6199,9 @@ var CanvasRoom = class extends BaseRoom {
5917
6199
  // --------------------------------------------------------------------------
5918
6200
  // BaseRoom Lifecycle
5919
6201
  // --------------------------------------------------------------------------
5920
- async fetch(request) {
6202
+ async fetch(request2) {
5921
6203
  this.ensureInitialized();
5922
- return super.fetch(request);
6204
+ return super.fetch(request2);
5923
6205
  }
5924
6206
  onConnect(ws, user) {
5925
6207
  this.ensureInitialized();
@@ -6398,6 +6680,7 @@ function cronMatches(parsed, timezone, date) {
6398
6680
  }
6399
6681
 
6400
6682
  // src/server/rooms/cron-room.ts
6683
+ var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
6401
6684
  var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
6402
6685
  MSG.CRON_TRIGGER,
6403
6686
  MSG.CRON_PAUSE,
@@ -6472,9 +6755,15 @@ var CronRoom = class extends BaseRoom {
6472
6755
  // ==========================================================================
6473
6756
  // BaseRoom Lifecycle
6474
6757
  // ==========================================================================
6475
- async fetch(request) {
6758
+ async fetch(request2) {
6476
6759
  this.ensureInitialized();
6477
- 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);
6478
6767
  }
6479
6768
  onConnect(ws, user) {
6480
6769
  this.ensureInitialized();
@@ -6622,15 +6911,22 @@ var CronRoom = class extends BaseRoom {
6622
6911
  const start = Date.now();
6623
6912
  let success = true;
6624
6913
  let error;
6914
+ let thrown;
6625
6915
  try {
6626
6916
  await this.onTask(taskName);
6627
6917
  } catch (e) {
6628
6918
  success = false;
6919
+ thrown = e;
6629
6920
  error = e instanceof Error ? e.message : String(e);
6630
- console.error(`[CronRoom] Task "${taskName}" failed:`, e);
6631
6921
  }
6632
6922
  const durationMs = Date.now() - start;
6633
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
+ }
6634
6930
  this.sql.exec(
6635
6931
  `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
6636
6932
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -6739,6 +7035,19 @@ var CronRoom = class extends BaseRoom {
6739
7035
  );
6740
7036
  }
6741
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
+ }
6742
7051
  function computeNextRunAt(task, from) {
6743
7052
  if (task.interval_minutes) {
6744
7053
  return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
@@ -6811,9 +7120,9 @@ var JobRoom = class extends BaseRoom {
6811
7120
  // ==========================================================================
6812
7121
  // BaseRoom Lifecycle
6813
7122
  // ==========================================================================
6814
- async fetch(request) {
7123
+ async fetch(request2) {
6815
7124
  this.ensureInitialized();
6816
- return super.fetch(request);
7125
+ return super.fetch(request2);
6817
7126
  }
6818
7127
  async onConnect(ws, user) {
6819
7128
  this.ensureInitialized();
@@ -6900,13 +7209,13 @@ var JobRoom = class extends BaseRoom {
6900
7209
  * Subclasses overriding `onRequest` should call `super.onRequest(req)`
6901
7210
  * for paths they don't handle.
6902
7211
  */
6903
- async onRequest(request) {
7212
+ async onRequest(request2) {
6904
7213
  this.ensureInitialized();
6905
- const url = new URL(request.url);
6906
- if (request.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
7214
+ const url = new URL(request2.url);
7215
+ if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
6907
7216
  return new Response("Not Found", { status: 404 });
6908
7217
  }
6909
- const body = await request.json().catch(() => null);
7218
+ const body = await request2.json().catch(() => null);
6910
7219
  if (!body || typeof body.type !== "string" || !body.type) {
6911
7220
  return new Response(JSON.stringify({ error: "type required" }), {
6912
7221
  status: 400,
@@ -7072,7 +7381,8 @@ var JobRoom = class extends BaseRoom {
7072
7381
  } catch (e) {
7073
7382
  outcome = "failed";
7074
7383
  errorMessage = e instanceof Error ? e.message : String(e);
7075
- 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] : []);
7076
7386
  } finally {
7077
7387
  this.inFlight.delete(row.id);
7078
7388
  }
@@ -7380,13 +7690,15 @@ function validateDoManifest(manifest) {
7380
7690
  }
7381
7691
 
7382
7692
  // src/server/rooms/binding-manifest.ts
7693
+ var MAX_ENV_VAR_BYTES = 5 * 1024;
7383
7694
  var AUTO_PROVISION_SENTINEL = "auto";
7384
7695
  var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
7385
7696
  "d1",
7386
7697
  "kv_namespace",
7387
7698
  "vectorize",
7388
7699
  "r2_bucket",
7389
- "queue"
7700
+ "queue",
7701
+ "ai_search"
7390
7702
  ]);
7391
7703
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
7392
7704
  function isAutoProvision(b) {
@@ -7401,11 +7713,15 @@ function isAutoProvision(b) {
7401
7713
  return b.bucket_name === AUTO_PROVISION_SENTINEL;
7402
7714
  case "queue":
7403
7715
  return b.queue_name === AUTO_PROVISION_SENTINEL;
7716
+ case "ai_search":
7717
+ return b.instance_name === AUTO_PROVISION_SENTINEL;
7404
7718
  default:
7405
7719
  return false;
7406
7720
  }
7407
7721
  }
7408
7722
  var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
7723
+ "plain_text",
7724
+ "json",
7409
7725
  "vectorize",
7410
7726
  "ai",
7411
7727
  "r2_bucket",
@@ -7415,7 +7731,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
7415
7731
  "browser_rendering",
7416
7732
  "analytics_engine",
7417
7733
  "hyperdrive",
7418
- "ratelimit"
7734
+ "ratelimit",
7735
+ "ai_search"
7419
7736
  ]);
7420
7737
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
7421
7738
  "ASSETS",
@@ -7439,6 +7756,7 @@ function validateBindingManifest(manifest) {
7439
7756
  }
7440
7757
  const errors = [];
7441
7758
  const seenNames = /* @__PURE__ */ new Set();
7759
+ let aiSearchCount = 0;
7442
7760
  for (const entry of manifest) {
7443
7761
  if (!entry || typeof entry !== "object") {
7444
7762
  errors.push({
@@ -7469,14 +7787,34 @@ function validateBindingManifest(manifest) {
7469
7787
  }
7470
7788
  seenNames.add(name);
7471
7789
  const binding = entry;
7790
+ if (binding.type === "ai_search") aiSearchCount++;
7472
7791
  const fieldErr = requiredFieldError(binding);
7473
7792
  if (fieldErr) errors.push({ binding, reason: fieldErr });
7474
7793
  }
7794
+ if (aiSearchCount > 1) {
7795
+ errors.push({ reason: "Only one ai_search binding may be declared per app" });
7796
+ }
7475
7797
  if (errors.length) return { valid: false, errors };
7476
7798
  return { valid: true, bindings: manifest };
7477
7799
  }
7478
7800
  function requiredFieldError(b) {
7479
7801
  switch (b.type) {
7802
+ case "plain_text":
7803
+ if (typeof b.text !== "string") {
7804
+ return `plain_text binding "${b.name}" missing text (string)`;
7805
+ }
7806
+ return envVarSizeError(b.name, b.text);
7807
+ case "json": {
7808
+ if (!isBindingJsonValue(b.json)) {
7809
+ return `json binding "${b.name}" must contain a JSON-serializable value`;
7810
+ }
7811
+ return envVarSizeError(b.name, JSON.stringify(b.json));
7812
+ }
7813
+ case "ai_search":
7814
+ if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
7815
+ return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
7816
+ }
7817
+ return null;
7480
7818
  case "vectorize": {
7481
7819
  if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
7482
7820
  if (b.index_name === AUTO_PROVISION_SENTINEL) {
@@ -7538,7 +7876,26 @@ function requiredFieldError(b) {
7538
7876
  }
7539
7877
  }
7540
7878
  function bindingManifestFromOutputConfig(outputConfig) {
7879
+ const aiSearchNamespaces = outputConfig.ai_search_namespaces;
7880
+ const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
7881
+ if (declaresAiSearchNamespace) {
7882
+ throw new Error(
7883
+ 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
7884
+ );
7885
+ }
7541
7886
  const out = [];
7887
+ const vars = isObject(outputConfig.vars) ? outputConfig.vars : null;
7888
+ for (const [name, value] of Object.entries(vars ?? {})) {
7889
+ if (name === "DEEPSPACE_APP_ID" || name === "APP_NAME") continue;
7890
+ if (typeof value === "string") {
7891
+ out.push({ type: "plain_text", name, text: value });
7892
+ continue;
7893
+ }
7894
+ if (!isBindingJsonValue(value)) {
7895
+ throw new Error(`Wrangler var "${name}" is not a JSON-serializable value`);
7896
+ }
7897
+ out.push({ type: "json", name, json: value });
7898
+ }
7542
7899
  const aiBinding = pluckString(outputConfig.ai, "binding");
7543
7900
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
7544
7901
  for (const v of asObjectArray(outputConfig.vectorize)) {
@@ -7555,6 +7912,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
7555
7912
  ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
7556
7913
  });
7557
7914
  }
7915
+ for (const a of asObjectArray(outputConfig.ai_search)) {
7916
+ const name = pluckString(a, "binding");
7917
+ const instance_name = pluckString(a, "instance_name");
7918
+ if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
7919
+ }
7558
7920
  for (const r of asObjectArray(outputConfig.r2_buckets)) {
7559
7921
  const name = pluckString(r, "binding");
7560
7922
  const bucket_name = pluckString(r, "bucket_name");
@@ -7616,6 +7978,25 @@ function bindingManifestFromOutputConfig(outputConfig) {
7616
7978
  function isObject(v) {
7617
7979
  return v !== null && typeof v === "object" && !Array.isArray(v);
7618
7980
  }
7981
+ function isBindingJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
7982
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
7983
+ return true;
7984
+ }
7985
+ if (typeof value !== "object") return false;
7986
+ if (ancestors.has(value)) return false;
7987
+ if (!Array.isArray(value)) {
7988
+ const prototype = Object.getPrototypeOf(value);
7989
+ if (prototype !== Object.prototype && prototype !== null) return false;
7990
+ }
7991
+ ancestors.add(value);
7992
+ const valid = Array.isArray(value) ? value.every((entry) => isBindingJsonValue(entry, ancestors)) : Object.values(value).every((entry) => isBindingJsonValue(entry, ancestors));
7993
+ ancestors.delete(value);
7994
+ return valid;
7995
+ }
7996
+ function envVarSizeError(name, serializedValue) {
7997
+ const size = new TextEncoder().encode(serializedValue).byteLength;
7998
+ return size <= MAX_ENV_VAR_BYTES ? null : `Environment variable binding "${name}" is ${size} bytes; maximum is ${MAX_ENV_VAR_BYTES}`;
7999
+ }
7619
8000
  function pluckString(v, key) {
7620
8001
  if (!isObject(v)) return null;
7621
8002
  const val = v[key];
@@ -8090,218 +8471,8 @@ var DIRECTORY_SCHEMAS = [
8090
8471
  postsSchema
8091
8472
  ];
8092
8473
 
8093
- // src/server/schemas/workspace.ts
8094
- var workspaceTeamsSchema = {
8095
- name: "teams",
8096
- columns: [
8097
- { name: "Name", storage: "text", interpretation: "plain" },
8098
- { name: "CreatedBy", storage: "text", interpretation: "plain" },
8099
- { name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
8100
- ],
8101
- teamField: "_rowId",
8102
- permissions: {
8103
- admin: { read: true, create: true, update: true, delete: true },
8104
- member: { read: "team", create: true, update: "team", delete: "own" }
8105
- }
8106
- };
8107
- var workspaceTeamMembersSchema = {
8108
- name: "team_members",
8109
- columns: [
8110
- { name: "TeamId", storage: "text", interpretation: "plain" },
8111
- { name: "UserId", storage: "text", interpretation: "plain" },
8112
- { name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
8113
- { name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
8114
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
8115
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
8116
- ],
8117
- teamField: "TeamId",
8118
- permissions: {
8119
- admin: { read: true, create: true, update: true, delete: true },
8120
- member: { read: "team", create: true, update: "team", delete: "team" }
8121
- }
8122
- };
8123
- var workspaceTasksSchema = {
8124
- name: "tasks",
8125
- columns: [
8126
- { name: "TeamId", storage: "text", interpretation: "plain" },
8127
- { name: "Title", storage: "text", interpretation: "plain" },
8128
- { name: "Notes", storage: "text", interpretation: "plain" },
8129
- { name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
8130
- { name: "CompletedAt", storage: "number", interpretation: "plain" },
8131
- { name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
8132
- { name: "DeletedAt", storage: "number", interpretation: "plain" },
8133
- { name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
8134
- { name: "DueDate", storage: "text", interpretation: "plain" },
8135
- { name: "ProjectId", storage: "text", interpretation: "plain" },
8136
- { name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
8137
- { name: "Order", storage: "number", interpretation: "plain" },
8138
- { name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
8139
- { name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
8140
- { name: "TagIds", storage: "text", interpretation: { kind: "json" } },
8141
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8142
- ],
8143
- teamField: "TeamId",
8144
- permissions: {
8145
- admin: { read: true, create: true, update: true, delete: true },
8146
- member: { read: "team", create: true, update: "team", delete: "team" },
8147
- viewer: { read: false, create: false, update: false, delete: false }
8148
- }
8149
- };
8150
- var workspaceProjectsSchema = {
8151
- name: "projects",
8152
- columns: [
8153
- { name: "TeamId", storage: "text", interpretation: "plain" },
8154
- { name: "Title", storage: "text", interpretation: "plain" },
8155
- { name: "Notes", storage: "text", interpretation: "plain" },
8156
- { name: "Color", storage: "text", interpretation: "plain" },
8157
- { name: "ParentId", storage: "text", interpretation: "plain" },
8158
- { name: "Order", storage: "number", interpretation: "plain" },
8159
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8160
- ],
8161
- teamField: "TeamId",
8162
- permissions: {
8163
- admin: { read: true, create: true, update: true, delete: true },
8164
- member: { read: "team", create: true, update: "team", delete: "team" },
8165
- viewer: { read: false, create: false, update: false, delete: false }
8166
- }
8167
- };
8168
- var workspaceTagsSchema = {
8169
- name: "tags",
8170
- columns: [
8171
- { name: "TeamId", storage: "text", interpretation: "plain" },
8172
- { name: "Name", storage: "text", interpretation: "plain" },
8173
- { name: "Color", storage: "text", interpretation: "plain" },
8174
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
8175
- ],
8176
- teamField: "TeamId",
8177
- permissions: {
8178
- admin: { read: true, create: true, update: true, delete: true },
8179
- member: { read: "team", create: true, update: "team", delete: "team" },
8180
- viewer: { read: false, create: false, update: false, delete: false }
8181
- }
8182
- };
8183
- var workspacePeopleSchema = {
8184
- name: "people",
8185
- columns: [
8186
- { name: "Name", storage: "text", interpretation: "plain" },
8187
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
8188
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
8189
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
8190
- { name: "CompanyId", storage: "text", interpretation: "plain" },
8191
- { name: "Department", storage: "text", interpretation: "plain" },
8192
- { name: "Title", storage: "text", interpretation: "plain" },
8193
- { name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
8194
- { name: "Metadata", storage: "text", interpretation: { kind: "json" } }
8195
- ],
8196
- permissions: {
8197
- "*": { read: true, create: true, update: true, delete: false },
8198
- admin: { read: true, create: true, update: true, delete: true }
8199
- }
8200
- };
8201
- var workspaceTransactionsSchema = {
8202
- name: "transactions",
8203
- columns: [
8204
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
8205
- { name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
8206
- { name: "Currency", storage: "text", interpretation: "plain" },
8207
- { name: "Description", storage: "text", interpretation: "plain" },
8208
- { name: "Category", storage: "text", interpretation: "plain" },
8209
- { name: "CounterpartyRef", storage: "text", interpretation: "plain" },
8210
- { name: "OrderRef", storage: "text", interpretation: "plain" },
8211
- { name: "Account", storage: "text", interpretation: "plain" }
8212
- ],
8213
- permissions: {
8214
- "*": { read: true, create: true, update: true, delete: false },
8215
- admin: { read: true, create: true, update: true, delete: true }
8216
- }
8217
- };
8218
- var workspaceAccountsSchema = {
8219
- name: "accounts",
8220
- columns: [
8221
- { name: "Name", storage: "text", interpretation: "plain" },
8222
- { name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
8223
- { name: "Currency", storage: "text", interpretation: "plain" }
8224
- ],
8225
- permissions: {
8226
- "*": { read: true, create: true, update: true, delete: false },
8227
- admin: { read: true, create: true, update: true, delete: true }
8228
- }
8229
- };
8230
- var workspaceContentSharesSchema = {
8231
- name: "content_shares",
8232
- ownerField: "OwnerId",
8233
- columns: [
8234
- { name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
8235
- { name: "ContentId", storage: "text", interpretation: "plain" },
8236
- { name: "OwnerId", storage: "text", interpretation: "plain" },
8237
- { name: "OwnerName", storage: "text", interpretation: "plain" },
8238
- { name: "Title", storage: "text", interpretation: "plain" },
8239
- { name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
8240
- { name: "ShareTarget", storage: "text", interpretation: "plain" },
8241
- { name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
8242
- { name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
8243
- { name: "SharedBy", storage: "text", interpretation: "plain" },
8244
- { name: "SourceApp", storage: "text", interpretation: "plain" },
8245
- { name: "WordCount", storage: "number", interpretation: "plain" },
8246
- { name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
8247
- ],
8248
- permissions: {
8249
- "*": { read: true, create: true, update: true, delete: "own" },
8250
- admin: { read: true, create: true, update: true, delete: true }
8251
- }
8252
- };
8253
- var workspaceFormResponsesSchema = {
8254
- name: "form_responses",
8255
- columns: [
8256
- { name: "FormId", storage: "text", interpretation: "plain" },
8257
- { name: "FormTitle", storage: "text", interpretation: "plain" },
8258
- { name: "RespondentId", storage: "text", interpretation: "plain" },
8259
- { name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
8260
- { name: "RespondentName", storage: "text", interpretation: "plain" },
8261
- { name: "Data", storage: "text", interpretation: { kind: "json" } },
8262
- { name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
8263
- ],
8264
- permissions: {
8265
- "*": { read: true, create: true, update: false, delete: false },
8266
- admin: { read: true, create: true, update: true, delete: true }
8267
- }
8268
- };
8269
- var workspaceEmailHandlesSchema = {
8270
- name: "email_handles",
8271
- columns: [
8272
- { name: "UserId", storage: "text", interpretation: "plain" },
8273
- { name: "Handle", storage: "text", interpretation: "plain" },
8274
- { name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
8275
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
8276
- ],
8277
- uniqueOn: ["Handle"],
8278
- ownerField: "UserId",
8279
- permissions: {
8280
- "*": { read: true, create: true, update: "own", delete: false },
8281
- admin: { read: true, create: true, update: true, delete: true }
8282
- }
8283
- };
8284
- var WORKSPACE_SCHEMAS = [
8285
- workspaceTeamsSchema,
8286
- workspaceTeamMembersSchema,
8287
- workspaceTasksSchema,
8288
- workspaceProjectsSchema,
8289
- workspaceTagsSchema,
8290
- workspacePeopleSchema,
8291
- workspaceTransactionsSchema,
8292
- workspaceAccountsSchema,
8293
- workspaceContentSharesSchema,
8294
- workspaceFormResponsesSchema,
8295
- workspaceEmailHandlesSchema
8296
- ];
8297
-
8298
8474
  // src/server/schemas/shared-do.ts
8299
8475
  var GLOBAL_DO_TYPES = [
8300
- {
8301
- name: "workspace",
8302
- schemas: WORKSPACE_SCHEMAS,
8303
- description: "Shared workspace data (teams, tasks, projects, people, ledger)"
8304
- },
8305
8476
  {
8306
8477
  name: "conv",
8307
8478
  schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
@@ -8811,9 +8982,11 @@ export {
8811
8982
  GLOBAL_DO_TYPES,
8812
8983
  GLOBAL_DO_TYPE_NAMES,
8813
8984
  JobRoom,
8985
+ KnowledgeError,
8814
8986
  MAX_APP_FILE_BYTES,
8815
8987
  MAX_DELETE_WHERE_LIMIT,
8816
8988
  MAX_DEPLOY_ASSET_FILE_BYTES,
8989
+ MAX_ENV_VAR_BYTES,
8817
8990
  MAX_UPLOAD_PARTS,
8818
8991
  MAX_UPLOAD_REQUEST_BYTES,
8819
8992
  MESSAGES_SCHEMA,
@@ -8839,13 +9012,13 @@ export {
8839
9012
  UPLOAD_PART_BYTES,
8840
9013
  USERS_COLUMNS,
8841
9014
  VOTING_SCHEMAS,
8842
- WORKSPACE_SCHEMAS,
8843
9015
  YjsRoom,
8844
9016
  analyzePermissions,
8845
9017
  apiWorkerFetch,
8846
9018
  appendMessage,
8847
9019
  applyAiToolDefaults,
8848
9020
  applySlidingWindow,
9021
+ armCronRoom,
8849
9022
  authWorkerFetch,
8850
9023
  bindingManifestFromOutputConfig,
8851
9024
  broadcastChange,
@@ -8880,7 +9053,9 @@ export {
8880
9053
  deleteChatCascade,
8881
9054
  deleteRecord,
8882
9055
  enqueueJob,
9056
+ epochSeconds,
8883
9057
  executeQuery,
9058
+ folderFilter,
8884
9059
  formatBytes,
8885
9060
  getAllUsers,
8886
9061
  getChat,
@@ -8910,6 +9085,7 @@ export {
8910
9085
  isTestAccountClaims,
8911
9086
  isTestAccountEmail,
8912
9087
  isTestAccountTier,
9088
+ knowledge,
8913
9089
  lintSchema,
8914
9090
  lintSchemaSet,
8915
9091
  lintSchemas,
@@ -8922,6 +9098,7 @@ export {
8922
9098
  mintAppId,
8923
9099
  mintUlid,
8924
9100
  noopPermissionContext,
9101
+ normalizeKnowledgeFolder,
8925
9102
  parseQuotaLimit,
8926
9103
  platformCorsPatterns,
8927
9104
  platformWorkerFetch,
@@ -8954,17 +9131,6 @@ export {
8954
9131
  validateAppName,
8955
9132
  validateBindingManifest,
8956
9133
  validateDoManifest,
8957
- verifyJwt,
8958
- workspaceAccountsSchema,
8959
- workspaceContentSharesSchema,
8960
- workspaceEmailHandlesSchema,
8961
- workspaceFormResponsesSchema,
8962
- workspacePeopleSchema,
8963
- workspaceProjectsSchema,
8964
- workspaceTagsSchema,
8965
- workspaceTasksSchema,
8966
- workspaceTeamMembersSchema,
8967
- workspaceTeamsSchema,
8968
- workspaceTransactionsSchema
9134
+ verifyJwt
8969
9135
  };
8970
9136
  //# sourceMappingURL=server.js.map