deepspace 0.33.1 → 0.34.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -1
  3. package/dist/build.js.map +1 -1
  4. package/dist/chunk-KELQ7OD5.js +1957 -0
  5. package/dist/chunk-KELQ7OD5.js.map +1 -0
  6. package/dist/{chunk-PZDVKSLI.js → chunk-SINBB5BQ.js} +1 -1
  7. package/dist/{chunk-PZDVKSLI.js.map → chunk-SINBB5BQ.js.map} +1 -1
  8. package/dist/cli.js +74 -15
  9. package/dist/cli.js.map +1 -1
  10. package/dist/documentation-client-core.js +1 -1
  11. package/dist/documentation-default-renderer.cjs +1 -1
  12. package/dist/documentation-react.js +1 -1
  13. package/dist/documentation-runtime.js +10 -10
  14. package/dist/documentation-server-core.js +1 -1
  15. package/dist/documentation.js +47 -22
  16. package/dist/documentation.js.map +1 -1
  17. package/dist/expo.d.ts +73 -0
  18. package/dist/expo.js +233 -0
  19. package/dist/expo.js.map +1 -0
  20. package/dist/index.d.ts +105 -22
  21. package/dist/index.js +50 -23
  22. package/dist/index.js.map +1 -1
  23. package/dist/server.d.ts +205 -37
  24. package/dist/server.js +290 -97
  25. package/dist/server.js.map +1 -1
  26. package/dist/worker.d.ts +312 -48
  27. package/dist/worker.js +499 -110
  28. package/dist/worker.js.map +1 -1
  29. package/features/documents/feature.json +15 -0
  30. package/features/documents/src/DocumentsListPage.tsx +22 -8
  31. package/features/documents/src/InviteDialog.tsx +63 -42
  32. package/features/documents/src/__tests__/documents-find-invitee-action.test.ts +131 -0
  33. package/features/documents/src/__tests__/documents-invitee-lookup.test.ts +67 -0
  34. package/features/documents/src/documents-find-invitee-action.ts +56 -0
  35. package/features/documents/src/documents-invitee-lookup.ts +38 -0
  36. package/features/documents/src/documents-ui.css +10 -0
  37. package/package.json +28 -8
package/dist/server.js CHANGED
@@ -1860,36 +1860,60 @@ async function resolveAppRole(env, userId) {
1860
1860
  import { createAnthropic } from "@ai-sdk/anthropic";
1861
1861
  import { createOpenAI } from "@ai-sdk/openai";
1862
1862
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
1863
- var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096;
1864
- function withDefaultAnthropicMaxTokens(body) {
1863
+
1864
+ // src/shared/sandbox.ts
1865
+ var SANDBOX_SCOPE_HEADER = "x-deepspace-sandbox-scope";
1866
+
1867
+ // src/server/utils/app-identity.ts
1868
+ function appendAppIdentity(headers, env) {
1869
+ headers.delete("x-app-identity-token");
1870
+ headers.delete("x-app-id");
1871
+ if (!env.APP_IDENTITY_TOKEN) return;
1872
+ headers.set("x-app-identity-token", env.APP_IDENTITY_TOKEN);
1873
+ headers.set("x-app-id", env.DEEPSPACE_APP_ID);
1874
+ }
1875
+
1876
+ // src/server/utils/ai.ts
1877
+ import { defaultSettingsMiddleware, wrapLanguageModel } from "ai";
1878
+ function resolveProxyAuthToken(env, options) {
1879
+ const authToken = options.authToken ?? env.APP_OWNER_JWT;
1880
+ if (!authToken) {
1881
+ throw new Error(
1882
+ "DeepSpace AI proxy: no auth token available. Either pass `options.authToken` explicitly (for user-initiated calls), or ensure `env.APP_OWNER_JWT` is set (injected at deploy time or by `deepspace dev start`)."
1883
+ );
1884
+ }
1885
+ return authToken;
1886
+ }
1887
+ function setProxyHeaders(headers, env, authToken, scope = "user") {
1888
+ headers.set("X-Auth-Token", authToken);
1889
+ appendAppIdentity(headers, {
1890
+ DEEPSPACE_APP_ID: env.DEEPSPACE_APP_ID ?? "",
1891
+ APP_IDENTITY_TOKEN: env.APP_IDENTITY_TOKEN
1892
+ });
1893
+ headers.set(SANDBOX_SCOPE_HEADER, scope);
1894
+ }
1895
+ var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 64e3;
1896
+ var OPENAI_TOOLS_NEED_NO_REASONING = /^gpt-(5\.6|6)-(sol|terra|luna)(-|$)/;
1897
+ function withOpenAIToolReasoningEffort(body) {
1865
1898
  if (typeof body !== "string") return body;
1866
1899
  try {
1867
1900
  const parsed = JSON.parse(body);
1868
- const hasExplicitLimit = typeof parsed.max_tokens === "number" || typeof parsed.max_completion_tokens === "number" || typeof parsed.max_output_tokens === "number";
1869
- if (hasExplicitLimit) return body;
1870
- return JSON.stringify({
1871
- ...parsed,
1872
- max_tokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS
1873
- });
1901
+ const needsEffort = typeof parsed.model === "string" && OPENAI_TOOLS_NEED_NO_REASONING.test(parsed.model) && Array.isArray(parsed.tools) && parsed.tools.length > 0 && parsed.reasoning_effort === void 0;
1902
+ return needsEffort ? JSON.stringify({ ...parsed, reasoning_effort: "none" }) : body;
1874
1903
  } catch {
1875
1904
  return body;
1876
1905
  }
1877
1906
  }
1878
1907
  function createDeepSpaceAI(env, provider, options = {}) {
1879
1908
  const transport = resolveApiTransport(env);
1880
- const authToken = options.authToken ?? env.APP_OWNER_JWT;
1881
- if (!authToken) {
1882
- throw new Error(
1883
- "createDeepSpaceAI: no auth token available. Either pass `options.authToken` explicitly (for user-initiated calls), or ensure `env.APP_OWNER_JWT` is set (injected at deploy time or by `deepspace dev start`)."
1884
- );
1885
- }
1909
+ const authToken = resolveProxyAuthToken(env, options);
1886
1910
  const proxyFetch = (input, init) => {
1887
1911
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1888
1912
  const headers = new Headers(init?.headers);
1889
1913
  headers.delete("authorization");
1890
1914
  headers.delete("x-api-key");
1891
- headers.set("X-Auth-Token", authToken);
1892
- const body = provider === "anthropic" ? withDefaultAnthropicMaxTokens(init?.body) : init?.body;
1915
+ setProxyHeaders(headers, env, authToken, options.sandboxScope);
1916
+ const body = provider === "openai" ? withOpenAIToolReasoningEffort(init?.body) : init?.body;
1893
1917
  if (body !== init?.body) {
1894
1918
  headers.delete("content-length");
1895
1919
  }
@@ -1903,8 +1927,13 @@ function createDeepSpaceAI(env, provider, options = {}) {
1903
1927
  };
1904
1928
  const baseURL = `https://api-worker.internal/api/proxy/${provider}/v1`;
1905
1929
  switch (provider) {
1906
- case "anthropic":
1907
- return createAnthropic({ baseURL, apiKey: "platform-managed", fetch: proxyFetch });
1930
+ case "anthropic": {
1931
+ const anthropic2 = createAnthropic({ baseURL, apiKey: "platform-managed", fetch: proxyFetch });
1932
+ const defaults = defaultSettingsMiddleware({
1933
+ settings: { maxOutputTokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS }
1934
+ });
1935
+ return (modelId) => wrapLanguageModel({ model: anthropic2(modelId), middleware: defaults });
1936
+ }
1908
1937
  case "openai": {
1909
1938
  const op = createOpenAI({ baseURL, apiKey: "platform-managed", fetch: proxyFetch });
1910
1939
  return op.chat;
@@ -1925,10 +1954,7 @@ function createDeepSpaceAI(env, provider, options = {}) {
1925
1954
  }
1926
1955
 
1927
1956
  // src/server/utils/agent.ts
1928
- import {
1929
- stepCountIs,
1930
- streamText
1931
- } from "ai";
1957
+ import { isStepCount, streamText } from "ai";
1932
1958
 
1933
1959
  // src/shared/ai-models.ts
1934
1960
  var MULTI_STEP_AGENT_PROFILES = ["application", "documentation"];
@@ -1950,16 +1976,17 @@ var openAIModel = createMultiStepModelFactory({
1950
1976
  providerLabel: "OpenAI",
1951
1977
  transport: "chat-completions"
1952
1978
  });
1979
+ var DIRECT_GENERATION_NOTE = "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation.";
1953
1980
  var DEEPSPACE_AI_MODELS = [
1954
1981
  anthropicModel({
1955
- id: "claude-fable-5",
1956
- label: "Claude Fable 5",
1982
+ id: "claude-opus-5-5",
1983
+ label: "Claude Opus 5.5",
1957
1984
  family: "Claude 5",
1958
1985
  recommendation: "frontier"
1959
1986
  }),
1960
1987
  anthropicModel({
1961
- id: "claude-opus-5",
1962
- label: "Claude Opus 5",
1988
+ id: "claude-fable-5-1",
1989
+ label: "Claude Fable 5.1",
1963
1990
  family: "Claude 5",
1964
1991
  recommendation: "frontier"
1965
1992
  }),
@@ -1976,23 +2003,29 @@ var DEEPSPACE_AI_MODELS = [
1976
2003
  recommendation: "fast"
1977
2004
  }),
1978
2005
  openAIModel({
1979
- id: "gpt-5.6-sol",
1980
- label: "GPT-5.6 Sol",
1981
- family: "GPT-5.6",
1982
- recommendation: "frontier"
1983
- }),
1984
- openAIModel({
1985
- id: "gpt-5.6-terra",
1986
- label: "GPT-5.6 Terra",
1987
- family: "GPT-5.6",
2006
+ id: "gpt-6-sol",
2007
+ label: "GPT-6 Sol",
2008
+ family: "GPT-6",
1988
2009
  recommendation: "balanced"
1989
2010
  }),
1990
2011
  openAIModel({
1991
- id: "gpt-5.6-luna",
1992
- label: "GPT-5.6 Luna",
1993
- family: "GPT-5.6",
2012
+ id: "gpt-6-luna",
2013
+ label: "GPT-6 Luna",
2014
+ family: "GPT-6",
1994
2015
  recommendation: "fast"
1995
2016
  }),
2017
+ {
2018
+ id: "gpt-6-astra",
2019
+ label: "GPT-6 Astra",
2020
+ provider: "openai",
2021
+ providerLabel: "OpenAI",
2022
+ family: "GPT-6",
2023
+ agentSupport: "none",
2024
+ agentProfiles: [],
2025
+ transport: "chat-completions",
2026
+ recommendation: "limited",
2027
+ note: "Available for direct generation; its tool calling requires the Responses API, which the DeepSpace proxy does not serve."
2028
+ },
1996
2029
  {
1997
2030
  id: "gpt-oss-120b",
1998
2031
  label: "GPT-OSS 120B",
@@ -2003,12 +2036,30 @@ var DEEPSPACE_AI_MODELS = [
2003
2036
  agentProfiles: [],
2004
2037
  transport: "chat-completions",
2005
2038
  recommendation: "limited",
2006
- note: "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation."
2007
- }
2039
+ note: DIRECT_GENERATION_NOTE
2040
+ },
2041
+ {
2042
+ id: "qwen-3.8-27b",
2043
+ label: "Qwen 3.8 27B",
2044
+ provider: "cerebras",
2045
+ providerLabel: "Cerebras",
2046
+ family: "Qwen 3.8",
2047
+ agentSupport: "single-step",
2048
+ agentProfiles: [],
2049
+ transport: "chat-completions",
2050
+ recommendation: "limited",
2051
+ note: DIRECT_GENERATION_NOTE
2052
+ },
2053
+ // Superseded models: still resolvable for apps that saved them.
2054
+ anthropicModel({ id: "claude-opus-5", label: "Claude Opus 5", family: "Claude 5", recommendation: "frontier", legacy: true }),
2055
+ anthropicModel({ id: "claude-fable-5", label: "Claude Fable 5", family: "Claude 5", recommendation: "frontier", legacy: true }),
2056
+ openAIModel({ id: "gpt-5.6-sol", label: "GPT-5.6 Sol", family: "GPT-5.6", recommendation: "frontier", legacy: true }),
2057
+ openAIModel({ id: "gpt-5.6-terra", label: "GPT-5.6 Terra", family: "GPT-5.6", recommendation: "balanced", legacy: true }),
2058
+ openAIModel({ id: "gpt-5.6-luna", label: "GPT-5.6 Luna", family: "GPT-5.6", recommendation: "fast", legacy: true })
2008
2059
  ];
2009
2060
  var DEEPSPACE_AI_DEFAULTS = {
2010
- agent: "claude-sonnet-5",
2011
- directGeneration: "claude-sonnet-5",
2061
+ agent: "claude-opus-5-5",
2062
+ directGeneration: "claude-opus-5-5",
2012
2063
  summarization: "claude-haiku-4-5"
2013
2064
  };
2014
2065
  var DEEPSPACE_AGENT_PROFILES = {
@@ -2063,7 +2114,7 @@ var DeepSpaceAgentProfileError = class extends Error {
2063
2114
  code = "agent_profile_violation";
2064
2115
  };
2065
2116
  function streamDeepSpaceAgent(env, options) {
2066
- const { profile, modelId, authToken, ...streamOptions } = options;
2117
+ const { profile, modelId, authToken, sandboxScope, ...streamOptions } = options;
2067
2118
  const selection = resolveDeepSpaceAgentModel(modelId, profile);
2068
2119
  if (!selection) throw new DeepSpaceAgentModelError(modelId, profile);
2069
2120
  if (selection.profile.allowedTools !== "application-defined") {
@@ -2074,14 +2125,17 @@ function streamDeepSpaceAgent(env, options) {
2074
2125
  }
2075
2126
  }
2076
2127
  }
2077
- const provider = createDeepSpaceAI(env, selection.provider, authToken ? { authToken } : {});
2128
+ const provider = createDeepSpaceAI(env, selection.provider, {
2129
+ ...authToken ? { authToken } : {},
2130
+ ...sandboxScope ? { sandboxScope } : {}
2131
+ });
2078
2132
  const { prepareStep, tools, ...remainingStreamOptions } = streamOptions;
2079
2133
  const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls);
2080
2134
  const result = streamText({
2081
2135
  ...remainingStreamOptions,
2082
2136
  ...boundedTools ? { tools: boundedTools } : {},
2083
2137
  model: provider(selection.modelId),
2084
- stopWhen: stepCountIs(selection.profile.maxSteps),
2138
+ stopWhen: isStepCount(selection.profile.maxSteps),
2085
2139
  prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),
2086
2140
  ...selection.provider === "openai" ? { providerOptions: { openai: { reasoningEffort: "none" } } } : {}
2087
2141
  });
@@ -2193,6 +2247,115 @@ function safeLogText(value) {
2193
2247
  ).trim().slice(0, 300);
2194
2248
  }
2195
2249
 
2250
+ // src/server/utils/sandbox.ts
2251
+ import { anthropic, forwardAnthropicContainerIdFromLastStep } from "@ai-sdk/anthropic";
2252
+ var FILES_PATH = "/api/proxy/anthropic/v1/files";
2253
+ var CODE_EXECUTION_CALLER = "code_execution_20260120";
2254
+ function codeExecutionTool() {
2255
+ return anthropic.tools.codeExecution_20260120();
2256
+ }
2257
+ function callableFromSandbox(tool2) {
2258
+ const anthropicOptions = tool2.providerOptions?.anthropic;
2259
+ return {
2260
+ ...tool2,
2261
+ providerOptions: {
2262
+ ...tool2.providerOptions,
2263
+ anthropic: { ...anthropicOptions, allowedCallers: ["direct", CODE_EXECUTION_CALLER] }
2264
+ }
2265
+ };
2266
+ }
2267
+ var forwardSandboxContainer = forwardAnthropicContainerIdFromLastStep;
2268
+ function reuseSandbox(containerId) {
2269
+ return containerId ? { anthropic: { container: { id: containerId } } } : void 0;
2270
+ }
2271
+ function sandboxUpload(file) {
2272
+ return {
2273
+ type: "file",
2274
+ mediaType: file.mime_type,
2275
+ filename: file.filename,
2276
+ data: { type: "reference", reference: { anthropic: file.id } },
2277
+ providerOptions: { anthropic: { containerUpload: true } }
2278
+ };
2279
+ }
2280
+ function collectFileIds(value, out) {
2281
+ if (Array.isArray(value)) {
2282
+ for (const item of value) collectFileIds(item, out);
2283
+ return;
2284
+ }
2285
+ if (!value || typeof value !== "object") return;
2286
+ for (const [key, child] of Object.entries(value)) {
2287
+ if (key === "file_id" && typeof child === "string" && child) out.add(child);
2288
+ else collectFileIds(child, out);
2289
+ }
2290
+ }
2291
+ function sandboxOutputs(steps) {
2292
+ let containerId = null;
2293
+ const fileIds = /* @__PURE__ */ new Set();
2294
+ for (const step of steps) {
2295
+ const container = step.providerMetadata?.anthropic?.container;
2296
+ if (typeof container?.id === "string") containerId = container.id;
2297
+ for (const part of step.content) {
2298
+ const result = part;
2299
+ if (result.type === "tool-result" && result.providerExecuted) collectFileIds(result.output, fileIds);
2300
+ }
2301
+ }
2302
+ return { containerId, fileIds: [...fileIds] };
2303
+ }
2304
+ var SandboxFileError = class extends Error {
2305
+ constructor(status, message) {
2306
+ super(message);
2307
+ this.status = status;
2308
+ this.name = "SandboxFileError";
2309
+ }
2310
+ };
2311
+ function sandboxFiles(env, options = {}) {
2312
+ const authToken = resolveProxyAuthToken(env, options);
2313
+ async function send(path, init = {}) {
2314
+ const headers = new Headers(init.headers);
2315
+ setProxyHeaders(headers, env, authToken, options.scope);
2316
+ const response = await apiWorkerFetch(env, `${FILES_PATH}${path}`, { ...init, headers });
2317
+ if (!response.ok) {
2318
+ const text = await response.text();
2319
+ let message = text || response.statusText;
2320
+ try {
2321
+ const body = JSON.parse(text);
2322
+ if (typeof body.error === "string") message = body.error;
2323
+ else if (body.error && typeof body.error === "object") message = JSON.stringify(body.error);
2324
+ } catch {
2325
+ }
2326
+ throw new SandboxFileError(response.status, `Sandbox file request failed (${response.status}): ${message}`);
2327
+ }
2328
+ return response;
2329
+ }
2330
+ const fileUrl = (fileId) => `/${encodeURIComponent(fileId)}`;
2331
+ return {
2332
+ async upload(file, { filename, expiresInSeconds } = {}) {
2333
+ const name = filename ?? (file instanceof File ? file.name : void 0);
2334
+ if (!name) throw new SandboxFileError(400, "sandboxFiles.upload: a Blob needs options.filename");
2335
+ const form = new FormData();
2336
+ form.set("file", file, name);
2337
+ if (expiresInSeconds !== void 0) form.set("expires_in_seconds", String(expiresInSeconds));
2338
+ return (await send("", { method: "POST", body: form })).json();
2339
+ },
2340
+ async get(fileId) {
2341
+ return (await send(fileUrl(fileId))).json();
2342
+ },
2343
+ download(fileId) {
2344
+ return send(`${fileUrl(fileId)}/content`);
2345
+ },
2346
+ async list({ limit, page } = {}) {
2347
+ const query = new URLSearchParams();
2348
+ if (limit !== void 0) query.set("limit", String(limit));
2349
+ if (page !== void 0) query.set("page", page);
2350
+ const suffix = query.size > 0 ? `?${query}` : "";
2351
+ return (await send(suffix)).json();
2352
+ },
2353
+ async delete(fileId) {
2354
+ await send(fileUrl(fileId), { method: "DELETE" });
2355
+ }
2356
+ };
2357
+ }
2358
+
2196
2359
  // src/server/utils/composio-tools.ts
2197
2360
  import { tool, jsonSchema } from "ai";
2198
2361
  var EMPTY_SCHEMA = { type: "object", properties: {} };
@@ -2275,15 +2438,6 @@ async function composioTools(env, options) {
2275
2438
  return tools;
2276
2439
  }
2277
2440
 
2278
- // src/server/utils/app-identity.ts
2279
- function appendAppIdentity(headers, env) {
2280
- headers.delete("x-app-identity-token");
2281
- headers.delete("x-app-id");
2282
- if (!env.APP_IDENTITY_TOKEN) return;
2283
- headers.set("x-app-identity-token", env.APP_IDENTITY_TOKEN);
2284
- headers.set("x-app-id", env.DEEPSPACE_APP_ID);
2285
- }
2286
-
2287
2441
  // src/server/utils/screenshot.ts
2288
2442
  async function captureScreenshot(env, url) {
2289
2443
  const headers = new Headers({ "content-type": "application/json" });
@@ -2636,14 +2790,14 @@ function formatTranscript(messages) {
2636
2790
  }
2637
2791
  function makeDefaultSummarizer(env, options = {}) {
2638
2792
  return async (messages) => {
2639
- const anthropic = createDeepSpaceAI(env, "anthropic", { authToken: options.authToken });
2793
+ const anthropic2 = createDeepSpaceAI(env, "anthropic", { authToken: options.authToken });
2640
2794
  const { text } = await generateText({
2641
2795
  // Stable alias — provider bug-fix snapshots roll forward without code change.
2642
- model: anthropic("claude-haiku-4-5"),
2796
+ model: anthropic2("claude-haiku-4-5"),
2643
2797
  // 2500 gives the model room to finish a sentence after hitting the
2644
2798
  // "under 2000 tokens" soft limit in the prompt.
2645
2799
  maxOutputTokens: 2500,
2646
- system: SUMMARIZER_SYSTEM_PROMPT,
2800
+ instructions: SUMMARIZER_SYSTEM_PROMPT,
2647
2801
  messages: [{ role: "user", content: formatTranscript(messages) }]
2648
2802
  });
2649
2803
  return text;
@@ -4956,6 +5110,7 @@ function getAllUserRecords(sql, schemaRegistry) {
4956
5110
  }
4957
5111
  async function registerUser(sql, userId, name, email, imageUrl, isAdmin, defaultRole = "viewer", schemaRegistry) {
4958
5112
  const now = (/* @__PURE__ */ new Date()).toISOString();
5113
+ const normalizedEmail = email.trim().toLowerCase();
4959
5114
  const existing = getUserRecord(sql, userId, schemaRegistry);
4960
5115
  let role;
4961
5116
  if (isAdmin) {
@@ -4966,7 +5121,7 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
4966
5121
  role = defaultRole;
4967
5122
  }
4968
5123
  if (existing) {
4969
- const updatedEmail = email || existing.data.email;
5124
+ const updatedEmail = normalizedEmail || existing.data.email;
4970
5125
  const updatedName = name && name !== "Anonymous" ? name : existing.data.name;
4971
5126
  const updatedImageUrl = imageUrl ?? existing.data.imageUrl;
4972
5127
  sql.exec(
@@ -4995,14 +5150,14 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
4995
5150
  userId,
4996
5151
  now,
4997
5152
  now,
4998
- email,
5153
+ normalizedEmail,
4999
5154
  name,
5000
5155
  imageUrl ?? null,
5001
5156
  role,
5002
5157
  now,
5003
5158
  now
5004
5159
  );
5005
- return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
5160
+ return { id: userId, email: normalizedEmail, name, imageUrl, role, createdAt: now, lastSeenAt: now };
5006
5161
  }
5007
5162
  function rosterFor(ctx, schema, records, publicRoster, userId, role) {
5008
5163
  if (!userId || isAnonymousUserId(userId)) return [];
@@ -5057,16 +5212,19 @@ function broadcastUserList(ctx, except) {
5057
5212
  });
5058
5213
  }
5059
5214
  }
5060
- function handleUserUpdate(ctx, _ws, attachment, payload) {
5215
+ function handleUserUpdate(ctx, _ws, attachment) {
5061
5216
  const existing = getUserRecord(ctx.sql, attachment.userId, ctx.schemaRegistry);
5062
5217
  if (!existing) return;
5063
- const data = { lastSeenAt: (/* @__PURE__ */ new Date()).toISOString() };
5064
- if (payload.name && payload.name !== "Anonymous") data.name = payload.name;
5065
- if (payload.email) data.email = payload.email;
5066
- if (payload.imageUrl !== void 0) data.imageUrl = payload.imageUrl || void 0;
5067
- putRecord(ctx, "users", attachment.userId, data, attachment.userId, "admin", true, true);
5068
- const rosterChanged = data.name !== void 0 && data.name !== existing.data.name || data.imageUrl !== void 0 && data.imageUrl !== existing.data.imageUrl;
5069
- if (rosterChanged) broadcastUserList(ctx);
5218
+ putRecord(
5219
+ ctx,
5220
+ "users",
5221
+ attachment.userId,
5222
+ { lastSeenAt: (/* @__PURE__ */ new Date()).toISOString() },
5223
+ attachment.userId,
5224
+ "admin",
5225
+ true,
5226
+ true
5227
+ );
5070
5228
  }
5071
5229
  async function handleSetRole(ctx, ws, attachment, payload) {
5072
5230
  if (attachment.role !== "admin") {
@@ -5815,7 +5973,7 @@ var RecordRoom = class extends BaseRoom {
5815
5973
  this.schemaRegistry
5816
5974
  );
5817
5975
  const regMs = Date.now() - regStart;
5818
- if (!before || before.name !== registeredUser.name || before.imageUrl !== registeredUser.imageUrl || before.role !== registeredUser.role) {
5976
+ if (!before || before.name !== registeredUser.name || before.email !== registeredUser.email || before.imageUrl !== registeredUser.imageUrl || before.role !== registeredUser.role) {
5819
5977
  broadcastUserList(this.createUserContext(), ws);
5820
5978
  }
5821
5979
  attachment = {
@@ -5942,12 +6100,7 @@ var RecordRoom = class extends BaseRoom {
5942
6100
  handleUserList(userCtx, ws, attachment);
5943
6101
  break;
5944
6102
  case MSG.USER_UPDATE:
5945
- handleUserUpdate(
5946
- recordCtx,
5947
- ws,
5948
- attachment,
5949
- payload
5950
- );
6103
+ handleUserUpdate(recordCtx, ws, attachment);
5951
6104
  break;
5952
6105
  case MSG.SET_ROLE:
5953
6106
  await handleSetRole(userCtx, ws, attachment, payload);
@@ -7179,6 +7332,7 @@ function computeNextRunAt(task, from) {
7179
7332
 
7180
7333
  // src/server/rooms/job-room.ts
7181
7334
  var RUNNING_STALE_MS = 16 * 60 * 1e3;
7335
+ var BACKGROUND_ALARM_MS = 3e4;
7182
7336
  var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
7183
7337
  var DEFAULT_SNAPSHOT_LIMIT = 100;
7184
7338
  var JobRoom = class extends BaseRoom {
@@ -7187,6 +7341,7 @@ var JobRoom = class extends BaseRoom {
7187
7341
  retentionMs;
7188
7342
  snapshotLimit;
7189
7343
  retryBackoffMs;
7344
+ backgroundJobTypes;
7190
7345
  authorizeWrite;
7191
7346
  authorizeRead;
7192
7347
  broadcastQueue = Promise.resolve();
@@ -7200,6 +7355,7 @@ var JobRoom = class extends BaseRoom {
7200
7355
  this.retentionMs = Math.max(0, config.retentionMs ?? DEFAULT_RETENTION_MS);
7201
7356
  this.snapshotLimit = Math.max(1, config.snapshotLimit ?? DEFAULT_SNAPSHOT_LIMIT);
7202
7357
  this.retryBackoffMs = Math.max(0, config.retryBackoffMs ?? 1e3);
7358
+ this.backgroundJobTypes = new Set(config.backgroundJobTypes ?? []);
7203
7359
  this.authorizeWrite = config.authorizeWrite ?? ((user) => user.role === ROLES.MEMBER || user.role === ROLES.ADMIN);
7204
7360
  this.authorizeRead = config.authorizeRead ?? this.authorizeWrite;
7205
7361
  }
@@ -7447,11 +7603,8 @@ var JobRoom = class extends BaseRoom {
7447
7603
  // ==========================================================================
7448
7604
  // Alarm / execution
7449
7605
  // ==========================================================================
7450
- /**
7451
- * Drain due jobs in FIFO order. All due jobs share the alarm's
7452
- * 15-min wall budget (a CF property, not a per-job allocation).
7453
- */
7454
7606
  async drainDueJobs() {
7607
+ if (this.inFlight.size > 0) return;
7455
7608
  const cutoff = (/* @__PURE__ */ new Date()).toISOString();
7456
7609
  while (true) {
7457
7610
  const row = this.sql.exec(
@@ -7463,7 +7616,16 @@ var JobRoom = class extends BaseRoom {
7463
7616
  cutoff
7464
7617
  ).toArray()[0];
7465
7618
  if (!row) return;
7466
- await this.executeJob(row);
7619
+ const execution = this.executeJob(row);
7620
+ if (this.backgroundJobTypes.has(row.type)) {
7621
+ this.state.waitUntil(
7622
+ execution.catch(
7623
+ (error) => console.error(`[jobs] background execution failed: ${loggableError(error)}`)
7624
+ ).finally(() => this.scheduleNextAlarm())
7625
+ );
7626
+ return;
7627
+ }
7628
+ await execution;
7467
7629
  }
7468
7630
  }
7469
7631
  async executeJob(row) {
@@ -7500,7 +7662,7 @@ var JobRoom = class extends BaseRoom {
7500
7662
  this.inFlight.delete(row.id);
7501
7663
  }
7502
7664
  const currentStatus = this.readStatus(row.id);
7503
- if (currentStatus === "canceled") {
7665
+ if (currentStatus !== "running") {
7504
7666
  this.continueState = null;
7505
7667
  return;
7506
7668
  }
@@ -7632,6 +7794,10 @@ var JobRoom = class extends BaseRoom {
7632
7794
  // Scheduling / recovery / pruning
7633
7795
  // ==========================================================================
7634
7796
  scheduleNextAlarm() {
7797
+ if (this.inFlight.size > 0) {
7798
+ this.state.storage.setAlarm(Date.now() + BACKGROUND_ALARM_MS);
7799
+ return;
7800
+ }
7635
7801
  const queued = this.sql.exec(
7636
7802
  `SELECT next_run_at FROM jobs
7637
7803
  WHERE status = 'queued'
@@ -7665,6 +7831,7 @@ var JobRoom = class extends BaseRoom {
7665
7831
  cutoff
7666
7832
  ).toArray();
7667
7833
  for (const r of stuck) {
7834
+ if (this.inFlight.has(r.id)) continue;
7668
7835
  if (r.attempts >= r.max_attempts) {
7669
7836
  this.sql.exec(
7670
7837
  `UPDATE jobs
@@ -8619,6 +8786,7 @@ function isTestAccountTier(tier) {
8619
8786
 
8620
8787
  // src/server/auth/betterAuth.ts
8621
8788
  import { betterAuth } from "better-auth";
8789
+ import { getOAuthState } from "better-auth/api";
8622
8790
  import { organization, twoFactor } from "better-auth/plugins";
8623
8791
  import { SignJWT, importPKCS8 } from "jose";
8624
8792
  function createDeepSpaceAuth(config) {
@@ -8662,6 +8830,7 @@ function createDeepSpaceAuth(config) {
8662
8830
  emailAndPassword: {
8663
8831
  enabled: config.emailAndPassword ?? true
8664
8832
  },
8833
+ ...config.userAdditionalFields ? { user: { additionalFields: config.userAdditionalFields } } : {},
8665
8834
  socialProviders,
8666
8835
  trustedOrigins: config.trustedOrigins ?? [
8667
8836
  "https://deep.space",
@@ -8697,29 +8866,45 @@ function createDeepSpaceAuth(config) {
8697
8866
  // keys) — that case is caught by the runtime assertions in
8698
8867
  // __tests__/betterAuth-hooks.test.ts, which read the wired hook back
8699
8868
  // off auth.options. Don't rename the key without running them.
8700
- databaseHooks: buildDatabaseHooks(config.onUserCreated),
8869
+ databaseHooks: buildDatabaseHooks(config.beforeUserCreate, config.onUserCreated),
8701
8870
  plugins: [organization(), twoFactor()]
8702
8871
  });
8703
8872
  }
8704
- function buildDatabaseHooks(onUserCreated) {
8705
- if (!onUserCreated) return void 0;
8873
+ function buildDatabaseHooks(beforeUserCreate, onUserCreated) {
8874
+ if (!beforeUserCreate && !onUserCreated) return void 0;
8706
8875
  return {
8707
8876
  user: {
8708
8877
  create: {
8709
- after: async (user, ctx) => {
8710
- try {
8711
- await onUserCreated(
8712
- { id: user.id, email: user.email, name: user.name },
8713
- // Forward BOTH request and standalone headers — some better-auth
8714
- // entry points populate ctx.headers without a request.
8715
- ctx ? { request: ctx.request, headers: ctx.headers ?? void 0 } : null
8716
- );
8717
- } catch (err) {
8718
- console.error(
8719
- `[deepspace] onUserCreated hook failed (signup unaffected): ${loggableError(err)}`
8720
- );
8878
+ ...beforeUserCreate ? {
8879
+ before: async (user, ctx) => beforeUserCreate(
8880
+ user,
8881
+ ctx ? {
8882
+ request: ctx.request,
8883
+ headers: ctx.headers ?? void 0,
8884
+ path: ctx.path,
8885
+ // Read from this package's Better Auth instance. In a
8886
+ // pnpm graph the consuming worker can have another
8887
+ // physical copy whose request-state singleton differs.
8888
+ oauthState: await getOAuthState()
8889
+ } : null
8890
+ )
8891
+ } : {},
8892
+ ...onUserCreated ? {
8893
+ after: async (user, ctx) => {
8894
+ try {
8895
+ await onUserCreated(
8896
+ { id: user.id, email: user.email, name: user.name },
8897
+ // Forward BOTH request and standalone headers — some better-auth
8898
+ // entry points populate ctx.headers without a request.
8899
+ ctx ? { request: ctx.request, headers: ctx.headers ?? void 0 } : null
8900
+ );
8901
+ } catch (err) {
8902
+ console.error(
8903
+ `[deepspace] onUserCreated hook failed (signup unaffected): ${loggableError(err)}`
8904
+ );
8905
+ }
8721
8906
  }
8722
- }
8907
+ } : {}
8723
8908
  }
8724
8909
  }
8725
8910
  };
@@ -8924,6 +9109,7 @@ export {
8924
9109
  SYSTEM_COLLECTION_SCHEMAS,
8925
9110
  SYSTEM_MAINTAINED_COLUMNS,
8926
9111
  SYSTEM_MANAGED_COLUMNS,
9112
+ SandboxFileError,
8927
9113
  SchemaRegistry,
8928
9114
  SubscriptionAuthError,
8929
9115
  SubscriptionRequiredError,
@@ -8946,6 +9132,7 @@ export {
8946
9132
  buildTableSelect,
8947
9133
  buildUiParts,
8948
9134
  callRegistry,
9135
+ callableFromSandbox,
8949
9136
  canCreate,
8950
9137
  canDelete,
8951
9138
  canRead,
@@ -8955,6 +9142,7 @@ export {
8955
9142
  captureScreenshot,
8956
9143
  checkFieldPermissions,
8957
9144
  checkUnclaimedOwnerTransition,
9145
+ codeExecutionTool,
8958
9146
  coerceValue,
8959
9147
  collectionTableName,
8960
9148
  columnId,
@@ -8975,6 +9163,7 @@ export {
8975
9163
  executeQuery,
8976
9164
  folderFilter,
8977
9165
  formatBytes,
9166
+ forwardSandboxContainer,
8978
9167
  getAllUsers,
8979
9168
  getChat,
8980
9169
  getOrCreateYjsDoc,
@@ -9033,8 +9222,12 @@ export {
9033
9222
  resolveAppRole,
9034
9223
  resolveColumn,
9035
9224
  resolveSessionReadAuth,
9225
+ reuseSandbox,
9036
9226
  rowToData,
9037
9227
  runMigrations,
9228
+ sandboxFiles,
9229
+ sandboxOutputs,
9230
+ sandboxUpload,
9038
9231
  saveYjsDoc,
9039
9232
  storageLimitForTier,
9040
9233
  storageQuotaMessage,