deepspace 0.20.0 → 0.22.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 (43) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/{chunk-A7UFIOWO.js → chunk-7TDBFEB7.js} +7 -2
  3. package/dist/chunk-7TDBFEB7.js.map +1 -0
  4. package/dist/cli.js +288 -120
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +3 -2
  7. package/dist/documentation-client-core.js +1 -1
  8. package/dist/documentation-default-renderer.cjs +48 -48
  9. package/dist/documentation-react.d.ts +2 -1
  10. package/dist/documentation-react.js +1 -1
  11. package/dist/documentation-runtime.js +7 -7
  12. package/dist/documentation-server-core.d.ts +3 -2
  13. package/dist/documentation-server-core.js +1 -1
  14. package/dist/documentation.d.ts +40 -12
  15. package/dist/documentation.js +40 -7
  16. package/dist/documentation.js.map +1 -1
  17. package/dist/index.d.ts +225 -21
  18. package/dist/index.js +239 -100
  19. package/dist/index.js.map +1 -1
  20. package/dist/{public-CFAj1hL2.d.ts → public-DenQ1TsZ.d.ts} +36 -11
  21. package/dist/server.d.ts +32 -4
  22. package/dist/server.js +177 -43
  23. package/dist/server.js.map +1 -1
  24. package/dist/testing-mcp.d.ts +69 -0
  25. package/dist/testing-mcp.js +99 -0
  26. package/dist/testing-mcp.js.map +1 -0
  27. package/dist/testing.d.ts +17 -20
  28. package/dist/testing.js +47 -25
  29. package/dist/testing.js.map +1 -1
  30. package/dist/worker.d.ts +57 -5
  31. package/dist/worker.js +262 -62
  32. package/dist/worker.js.map +1 -1
  33. package/features/ai-chat/feature.json +2 -2
  34. package/features/ai-chat/src/AiChatPage.tsx +56 -21
  35. package/features/ai-chat/src/ChatPanel.tsx +33 -10
  36. package/features/ai-chat/src/__tests__/ChatPanel.lifecycle.test.tsx +156 -0
  37. package/features/cron/src/CronLogPage.tsx +107 -69
  38. package/features/file-attachments/feature.json +2 -1
  39. package/features/file-manager/feature.json +2 -1
  40. package/features/file-manager/src/FileManagerPage.tsx +227 -194
  41. package/features/integration-test/src/IntegrationTestPage.tsx +54 -34
  42. package/package.json +5 -1
  43. package/dist/chunk-A7UFIOWO.js.map +0 -1
@@ -1,4 +1,24 @@
1
1
  import { ReactNode, ReactElement, ComponentType } from 'react';
2
+ import { z } from 'zod';
3
+
4
+ declare const themeObjectSchema: z.ZodObject<{
5
+ accent: z.ZodOptional<z.ZodString>;
6
+ accentDark: z.ZodOptional<z.ZodString>;
7
+ background: z.ZodOptional<z.ZodString>;
8
+ density: z.ZodOptional<z.ZodEnum<{
9
+ comfortable: "comfortable";
10
+ compact: "compact";
11
+ }>>;
12
+ logo: z.ZodOptional<z.ZodString>;
13
+ logoDark: z.ZodOptional<z.ZodString>;
14
+ favicon: z.ZodOptional<z.ZodString>;
15
+ defaultMode: z.ZodOptional<z.ZodEnum<{
16
+ light: "light";
17
+ dark: "dark";
18
+ system: "system";
19
+ }>>;
20
+ strictMode: z.ZodOptional<z.ZodBoolean>;
21
+ }, z.core.$strip>;
2
22
 
3
23
  type DocumentationAssistantAccess = 'disabled' | 'public' | 'authenticated';
4
24
  type DocumentationMcpAccess = 'disabled' | 'public';
@@ -10,11 +30,21 @@ interface DocumentationFontConfig {
10
30
  source?: string;
11
31
  format?: string;
12
32
  }
13
- interface DocumentationThemeConfig {
14
- preset?: string;
15
- accent?: string;
16
- accentDark?: string;
17
- background?: string;
33
+ /**
34
+ * The `theme` object as authored in documentation.json — exactly the keys
35
+ * the config schema accepts under `theme.*`, derived from that schema so
36
+ * the two can never drift. Everything else (fonts, background decoration,
37
+ * styling, logo href) is configured through the Mintlify-style top-level
38
+ * keys and only exists on the resolved theme.
39
+ */
40
+ type DocumentationThemeConfig = z.infer<typeof themeObjectSchema>;
41
+ /**
42
+ * The fully resolved theme after config loading merges `theme.*` with the
43
+ * Mintlify top-level keys (`colors`, `logo`, `favicon`, `appearance`,
44
+ * `background`, `fonts`, `styling`). This is what the template and runtime
45
+ * consume; it is an output shape, never accepted as `theme.*` input.
46
+ */
47
+ interface ResolvedDocumentationTheme extends DocumentationThemeConfig {
18
48
  backgroundDark?: string;
19
49
  backgroundDecoration?: 'none' | 'gradient' | 'grid';
20
50
  bodyFont?: DocumentationFontConfig;
@@ -22,12 +52,7 @@ interface DocumentationThemeConfig {
22
52
  monoFont?: DocumentationFontConfig;
23
53
  codeBlockMode?: 'dark' | 'system';
24
54
  eyebrowStyle?: 'section' | 'breadcrumbs' | 'none';
25
- logo?: string;
26
- logoDark?: string;
27
55
  logoHref?: string;
28
- favicon?: string;
29
- defaultMode?: 'light' | 'dark' | 'system';
30
- strictMode?: boolean;
31
56
  }
32
57
  interface DocumentationLinkConfig {
33
58
  label: string;
@@ -79,7 +104,7 @@ interface DocumentationConfig {
79
104
  url?: string;
80
105
  /** Explicit custom hostnames that mount this documentation at `/`. */
81
106
  domains: string[];
82
- theme: DocumentationThemeConfig;
107
+ theme: ResolvedDocumentationTheme;
83
108
  navigation?: DocumentationNavigationItem[];
84
109
  links: DocumentationLinkConfig[];
85
110
  footer: DocumentationLinkConfig[];
package/dist/server.d.ts CHANGED
@@ -75,6 +75,13 @@ declare const APP_ID_RE: RegExp;
75
75
  declare const STRICT_APP_ID_RE: RegExp;
76
76
  /** Internal physical locators survive identity migration and are not public ids. */
77
77
  declare const RESOURCE_ID_RE: RegExp;
78
+ /** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —
79
+ * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */
80
+ declare function mintUlid(now?: number): string;
81
+ /** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the
82
+ * deploy worker's authenticated `POST /api/apps/mint`, which registers the id
83
+ * to its caller in the same step — an id with no owner never exists. */
84
+ declare function mintAppId(now?: number): string;
78
85
  declare class RegistryClientError extends Error {
79
86
  readonly code: string;
80
87
  readonly status: number;
@@ -1078,7 +1085,7 @@ declare function composioTools(env: ApiWorkerEnv, options: ComposioToolsOptions)
1078
1085
  interface ScreenshotEnv extends PlatformWorkerEnv {
1079
1086
  /** Immutable app id — the identity the platform verifies (HMAC input). */
1080
1087
  DEEPSPACE_APP_ID: string;
1081
- /** Absent until the app's first deploy registers it — see appendAppIdentity. */
1088
+ /** Absent until the app's first deploy injects it — see appendAppIdentity. */
1082
1089
  APP_IDENTITY_TOKEN?: string;
1083
1090
  }
1084
1091
  interface ScreenshotResult {
@@ -1532,6 +1539,7 @@ declare const MSG: {
1532
1539
  readonly CRON_PAUSE: "cron.pause";
1533
1540
  readonly CRON_RESUME: "cron.resume";
1534
1541
  readonly CRON_STATUS: "cron.status";
1542
+ readonly CRON_ACK: "cron.ack";
1535
1543
  readonly JOB_ENQUEUE: "job.enqueue";
1536
1544
  readonly JOB_CANCEL: "job.cancel";
1537
1545
  readonly JOB_RETRY: "job.retry";
@@ -1699,6 +1707,16 @@ type ServerMessage = BaseMessage<typeof MSG.QUERY_RESULT, {
1699
1707
  }> | BaseMessage<typeof MSG.CRON_STATUS, {
1700
1708
  tasks: unknown;
1701
1709
  recentHistory: unknown;
1710
+ }> | BaseMessage<typeof MSG.CRON_ACK, {
1711
+ requestId: string;
1712
+ taskName: string;
1713
+ ok: true;
1714
+ } | {
1715
+ requestId: string;
1716
+ taskName?: string;
1717
+ ok: false;
1718
+ reason: 'read_only' | 'unknown_task' | 'failed';
1719
+ error?: string;
1702
1720
  }> | BaseMessage<typeof MSG.JOB_UPDATE, {
1703
1721
  kind: 'snapshot' | 'enqueued' | 'progress' | 'succeeded' | 'failed' | 'canceled' | 'retried';
1704
1722
  job?: unknown;
@@ -2313,12 +2331,22 @@ declare abstract class CronRoom<E = Record<string, unknown>> extends BaseRoom<E>
2313
2331
  type: string;
2314
2332
  [key: string]: unknown;
2315
2333
  }): Promise<void>;
2334
+ private dispatchMessage;
2316
2335
  protected onAlarm(): Promise<void>;
2317
2336
  private executeTask;
2318
2337
  private scheduleNextAlarm;
2319
2338
  private getTaskStates;
2320
2339
  private getRecentHistory;
2321
2340
  private broadcastStatus;
2341
+ /**
2342
+ * Receipt for a mutation frame, addressed only to its sender. Receipts
2343
+ * are opt-in by correlation id: frames without a `requestId` keep the
2344
+ * fire-and-forget contract, so the (untyped) `requestId` from the wire
2345
+ * is the single gate here rather than a check at every call site.
2346
+ * Frames go through the shared `serverBuild` constructors so the wire
2347
+ * shape is enforced by the protocol types, not re-spelled here.
2348
+ */
2349
+ private ackMutation;
2322
2350
  /**
2323
2351
  * Execute a scheduled task by name.
2324
2352
  * Called both by the alarm scheduler and manual trigger.
@@ -4451,7 +4479,7 @@ interface SubscriptionRead {
4451
4479
  plans: PlanInfo[];
4452
4480
  }
4453
4481
  interface StarterAppEnv$1 extends ApiWorkerEnv {
4454
- /** Absent until the app's first deploy registers it — see appendAppIdentity. */
4482
+ /** Absent until the app's first deploy injects it — see appendAppIdentity. */
4455
4483
  APP_IDENTITY_TOKEN?: string;
4456
4484
  /** Immutable app id — the identity the platform verifies (HMAC input). */
4457
4485
  DEEPSPACE_APP_ID: string;
@@ -4540,7 +4568,7 @@ declare class CancelSubscriptionError extends Error {
4540
4568
  }
4541
4569
 
4542
4570
  interface StarterAppEnv extends ApiWorkerEnv {
4543
- /** Absent until the app's first deploy registers it — see appendAppIdentity. */
4571
+ /** Absent until the app's first deploy injects it — see appendAppIdentity. */
4544
4572
  APP_IDENTITY_TOKEN?: string;
4545
4573
  /** Immutable app id — the identity the platform verifies (HMAC input). */
4546
4574
  DEEPSPACE_APP_ID: string;
@@ -4565,4 +4593,4 @@ declare class RefundError extends Error {
4565
4593
  constructor(message: string, status: number);
4566
4594
  }
4567
4595
 
4568
- export { ACCOUNT_STORAGE_LIMIT_BYTES, AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type SharedConnection, type StorageAdmission, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, checkUnclaimedOwnerTransition, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, formatBytes, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppName, resolveAppRole, resolveColumn, rowToData, runMigrations, saveYjsDoc, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
4596
+ export { ACCOUNT_STORAGE_LIMIT_BYTES, AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type SharedConnection, type StorageAdmission, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, checkUnclaimedOwnerTransition, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, formatBytes, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, mintAppId, mintUlid, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppName, resolveAppRole, resolveColumn, rowToData, runMigrations, saveYjsDoc, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
package/dist/server.js CHANGED
@@ -34,6 +34,32 @@ var REGISTRY_INSTANCE = "v1:global";
34
34
  var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
35
35
  var STRICT_APP_ID_RE = APP_ID_RE;
36
36
  var RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/;
37
+ var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
38
+ function mintUlid(now = Date.now()) {
39
+ let ts = "";
40
+ let t = now;
41
+ for (let i = 0; i < 10; i++) {
42
+ ts = CROCKFORD[t % 32] + ts;
43
+ t = Math.floor(t / 32);
44
+ }
45
+ const rand = new Uint8Array(10);
46
+ crypto.getRandomValues(rand);
47
+ let rs = "";
48
+ let acc = 0;
49
+ let bits = 0;
50
+ for (const byte of rand) {
51
+ acc = acc << 8 | byte;
52
+ bits += 8;
53
+ while (bits >= 5) {
54
+ bits -= 5;
55
+ rs += CROCKFORD[acc >> bits & 31];
56
+ }
57
+ }
58
+ return `${ts}${rs}`.slice(0, 26);
59
+ }
60
+ function mintAppId(now = Date.now()) {
61
+ return `app_${mintUlid(now)}`;
62
+ }
37
63
  var RegistryClientError = class extends Error {
38
64
  constructor(message, code, status) {
39
65
  super(message);
@@ -740,6 +766,9 @@ function declaredLength(request, what) {
740
766
  function isKeyWithinScope(key, prefix, excludedPrefixes) {
741
767
  return key.startsWith(prefix) && !excludedPrefixes.some((excluded) => key.startsWith(excluded));
742
768
  }
769
+ function scopeRelativeKey(key, prefix) {
770
+ return key.slice(prefix.length);
771
+ }
743
772
  function anchorKey(raw, prefix, excludedPrefixes) {
744
773
  const safe = sanitizeSubpath(raw);
745
774
  if (safe === null) return fail2(400, "Invalid key: traversal not allowed", "invalid_key");
@@ -939,7 +968,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
939
968
  admission.markerKey,
940
969
  admission.markerKey !== null || fileData.byteLength < replacedBytes
941
970
  );
942
- return storedFileJson(located.key, fileName, url, scope);
971
+ return storedFileJson(located.key, prefix, fileName, url, scope);
943
972
  } catch (err) {
944
973
  const msg = err instanceof Error ? err.message : "Upload failed";
945
974
  console.error("[handleUpload] Error:", msg, err instanceof Error ? err.stack : "");
@@ -961,11 +990,17 @@ function fileMetadata(fileName, userId, reservationId, declaredBytes) {
961
990
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
962
991
  };
963
992
  }
964
- function storedFileJson(key, name, url, scope) {
993
+ function storedFileJson(key, prefix, name, url, scope) {
965
994
  const fileUrl = new URL(`/api/files/${encodeKeyPath(key)}`, url.origin);
966
995
  fileUrl.searchParams.set("scope", scope);
967
996
  return Response.json(
968
- { success: true, key, url: fileUrl.toString(), name },
997
+ {
998
+ success: true,
999
+ key,
1000
+ relativeKey: scopeRelativeKey(key, prefix),
1001
+ url: fileUrl.toString(),
1002
+ name
1003
+ },
969
1004
  { headers: CORS_HEADERS2 }
970
1005
  );
971
1006
  }
@@ -1245,7 +1280,7 @@ async function completedReservationObject(bucket, session) {
1245
1280
  const object = await bucket.head(session.key);
1246
1281
  return object?.customMetadata?.reservationId === session.reservationId ? object : null;
1247
1282
  }
1248
- async function finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope) {
1283
+ async function finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope, prefix) {
1249
1284
  const declaredMetadata = Number(object.customMetadata?.declaredBytes);
1250
1285
  const declaredBytes = reservation?.declaredBytes ?? (Number.isSafeInteger(declaredMetadata) && declaredMetadata >= 0 ? declaredMetadata : MAX_APP_FILE_BYTES);
1251
1286
  const refusal = overCeiling(object.size) ?? (object.size > declaredBytes ? fail2(
@@ -1279,7 +1314,7 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
1279
1314
  }
1280
1315
  }
1281
1316
  const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
1282
- return storedFileJson(session.key, name, url, scope);
1317
+ return storedFileJson(session.key, prefix, name, url, scope);
1283
1318
  }
1284
1319
  async function multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
1285
1320
  const body = await readControlJson(request);
@@ -1300,7 +1335,7 @@ async function multipartComplete(request, url, bucket, prefix, excludedPrefixes,
1300
1335
  if (auth.storage && !reservation) {
1301
1336
  const completed = await completedReservationObject(bucket, session);
1302
1337
  if (completed) {
1303
- return finishCompletedMultipart(bucket, session, null, completed, auth, url, scope);
1338
+ return finishCompletedMultipart(bucket, session, null, completed, auth, url, scope, prefix);
1304
1339
  }
1305
1340
  await abandonMultipart(bucket, session, auth, null);
1306
1341
  return quotaUnavailable("This upload reservation is missing or does not match.");
@@ -1339,14 +1374,23 @@ async function multipartComplete(request, url, bucket, prefix, excludedPrefixes,
1339
1374
  } catch (error) {
1340
1375
  const completed = await completedReservationObject(bucket, session);
1341
1376
  if (completed) {
1342
- return finishCompletedMultipart(bucket, session, reservation, completed, auth, url, scope);
1377
+ return finishCompletedMultipart(
1378
+ bucket,
1379
+ session,
1380
+ reservation,
1381
+ completed,
1382
+ auth,
1383
+ url,
1384
+ scope,
1385
+ prefix
1386
+ );
1343
1387
  }
1344
1388
  if (r2ErrorCode(error) === 10024 && reservation) {
1345
1389
  await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
1346
1390
  }
1347
1391
  return multipartFailure(error);
1348
1392
  }
1349
- return finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope);
1393
+ return finishCompletedMultipart(bucket, session, reservation, object, auth, url, scope, prefix);
1350
1394
  }
1351
1395
  async function multipartAbort(url, bucket, prefix, excludedPrefixes, auth) {
1352
1396
  const session = resolveSession(url, prefix, excludedPrefixes);
@@ -1366,37 +1410,49 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
1366
1410
  const listPrefix = `${prefix}${userPrefix}`;
1367
1411
  const requestedLimit = Number(url.searchParams.get("limit") ?? 100);
1368
1412
  const limit = Number.isSafeInteger(requestedLimit) ? Math.min(1e3, Math.max(1, requestedLimit)) : 100;
1413
+ const requestCursor = url.searchParams.get("cursor");
1369
1414
  if (excludedPrefixes.some((excluded) => listPrefix.startsWith(excluded))) {
1370
1415
  return Response.json({ files: [], truncated: false }, { headers: CORS_HEADERS2 });
1371
1416
  }
1372
- const listed = await bucket.list({ prefix: listPrefix, limit });
1417
+ const withMetadata = (options) => ({ ...options, include: ["customMetadata"] });
1418
+ const listed = await bucket.list(
1419
+ withMetadata({
1420
+ prefix: listPrefix,
1421
+ limit,
1422
+ ...requestCursor ? { cursor: requestCursor } : {}
1423
+ })
1424
+ );
1373
1425
  const visible = listed.objects.filter(
1374
1426
  (object) => isKeyWithinScope(object.key, prefix, excludedPrefixes)
1375
1427
  );
1376
1428
  const seen = new Set(visible.map((object) => object.key));
1377
- let truncated = listed.truncated;
1378
- for (const excluded of excludedPrefixes.filter((candidate) => candidate.startsWith(listPrefix))) {
1379
- if (visible.length >= limit) break;
1429
+ let frontier = listed;
1430
+ let frontierKey = listed.objects.at(-1)?.key;
1431
+ for (const excluded of excludedPrefixes.filter((candidate) => candidate.startsWith(listPrefix)).sort()) {
1432
+ if (!frontier.truncated || visible.length >= limit) break;
1380
1433
  const excludedEnd = `${excluded}\u{10FFFF}`;
1381
- const lastListedKey = listed.objects.at(-1)?.key;
1382
- const after = await bucket.list({
1383
- prefix: listPrefix,
1384
- startAfter: lastListedKey && lastListedKey > excludedEnd ? lastListedKey : excludedEnd,
1385
- limit: limit - visible.length
1386
- });
1434
+ const after = await bucket.list(
1435
+ withMetadata({
1436
+ prefix: listPrefix,
1437
+ startAfter: frontierKey && frontierKey > excludedEnd ? frontierKey : excludedEnd,
1438
+ limit: limit - visible.length
1439
+ })
1440
+ );
1387
1441
  for (const object of after.objects) {
1388
1442
  if (!seen.has(object.key) && isKeyWithinScope(object.key, prefix, excludedPrefixes)) {
1389
1443
  seen.add(object.key);
1390
1444
  visible.push(object);
1391
1445
  }
1392
1446
  }
1393
- truncated = after.truncated;
1447
+ frontierKey = after.objects.at(-1)?.key ?? frontierKey;
1448
+ frontier = after;
1394
1449
  }
1395
1450
  const files = visible.slice(0, limit).map((obj) => {
1396
1451
  const fileUrl = new URL(`/api/files/${encodeKeyPath(obj.key)}`, url.origin);
1397
1452
  fileUrl.searchParams.set("scope", scope);
1398
1453
  return {
1399
1454
  key: obj.key,
1455
+ relativeKey: scopeRelativeKey(obj.key, prefix),
1400
1456
  size: obj.size,
1401
1457
  uploaded: obj.uploaded.toISOString(),
1402
1458
  url: fileUrl.toString(),
@@ -1411,7 +1467,12 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
1411
1467
  if (storage && limitBytes !== null) storage.limitBytes = limitBytes;
1412
1468
  }
1413
1469
  return Response.json(
1414
- { files, truncated, ...storage ? { storage } : {} },
1470
+ {
1471
+ files,
1472
+ truncated: frontier.truncated,
1473
+ ...frontier.truncated ? { cursor: frontier.cursor } : {},
1474
+ ...storage ? { storage } : {}
1475
+ },
1415
1476
  { headers: CORS_HEADERS2 }
1416
1477
  );
1417
1478
  }
@@ -2952,6 +3013,9 @@ var MSG = {
2952
3013
  CRON_PAUSE: "cron.pause",
2953
3014
  CRON_RESUME: "cron.resume",
2954
3015
  CRON_STATUS: "cron.status",
3016
+ // Per-mutation receipt, sent only to the socket whose trigger/pause/
3017
+ // resume carried a `requestId` — frames without one stay fire-and-forget.
3018
+ CRON_ACK: "cron.ack",
2955
3019
  // ---- JobRoom ---------------------------------------------------------
2956
3020
  // Background-job queue with durable, alarm-driven execution. See
2957
3021
  // server/rooms/job-room.ts. Clients enqueue and subscribe to updates;
@@ -3453,6 +3517,14 @@ var serverBuild = {
3453
3517
  cronTasks: (tasks) => ({ type: MSG.CRON_TASKS, payload: { tasks } }),
3454
3518
  cronHistory: (history) => ({ type: MSG.CRON_HISTORY, payload: { history } }),
3455
3519
  cronStatus: (tasks, recentHistory) => ({ type: MSG.CRON_STATUS, payload: { tasks, recentHistory } }),
3520
+ cronAckSuccess: (requestId, taskName) => ({
3521
+ type: MSG.CRON_ACK,
3522
+ payload: { requestId, taskName, ok: true }
3523
+ }),
3524
+ cronAckFailure: (requestId, reason, error, taskName) => ({
3525
+ type: MSG.CRON_ACK,
3526
+ payload: { requestId, taskName, ok: false, reason, error }
3527
+ }),
3456
3528
  // Jobs
3457
3529
  jobSnapshot: (jobs) => ({ type: MSG.JOB_UPDATE, payload: { kind: "snapshot", jobs } }),
3458
3530
  jobEnqueued: (job, requestId) => ({
@@ -6196,40 +6268,88 @@ var CronRoom = class extends BaseRoom {
6196
6268
  }
6197
6269
  async onMessage(ws, user, message) {
6198
6270
  this.ensureInitialized();
6199
- const { type, payload } = message;
6271
+ const { type, payload = {} } = message;
6200
6272
  if (CRON_WRITE_TYPES.has(type) && !user.canWrite) {
6201
- this.sendTo(ws, {
6202
- type: MSG.ERROR,
6203
- payload: { error: "Write access denied: viewer role cannot modify cron tasks" }
6273
+ const error = "Write access denied: viewer role cannot modify cron tasks";
6274
+ this.sendTo(ws, { type: MSG.ERROR, payload: { error } });
6275
+ this.ackMutation(ws, payload.requestId, {
6276
+ ok: false,
6277
+ taskName: payload.taskName,
6278
+ reason: "read_only",
6279
+ error
6204
6280
  });
6205
6281
  return;
6206
6282
  }
6283
+ try {
6284
+ await this.dispatchMessage(ws, type, payload);
6285
+ } catch (error) {
6286
+ try {
6287
+ this.ackMutation(ws, payload.requestId, {
6288
+ ok: false,
6289
+ taskName: payload.taskName,
6290
+ reason: "failed",
6291
+ error: error instanceof Error ? error.message : String(error)
6292
+ });
6293
+ } catch {
6294
+ }
6295
+ throw error;
6296
+ }
6297
+ }
6298
+ async dispatchMessage(ws, type, payload) {
6207
6299
  switch (type) {
6208
6300
  case MSG.CRON_TRIGGER: {
6209
6301
  const taskName = payload.taskName;
6210
6302
  if (!taskName) {
6211
- this.sendTo(ws, { type: MSG.ERROR, payload: { error: "Missing taskName" } });
6303
+ const error = "Missing taskName";
6304
+ this.sendTo(ws, { type: MSG.ERROR, payload: { error } });
6305
+ this.ackMutation(ws, payload.requestId, { ok: false, reason: "failed", error });
6212
6306
  return;
6213
6307
  }
6214
- if (!await this.executeTask(taskName)) {
6215
- this.sendTo(ws, {
6216
- type: MSG.ERROR,
6217
- payload: { error: `Unknown cron task: ${taskName}` }
6308
+ const execution = await this.executeTask(taskName);
6309
+ if (!execution) {
6310
+ const error = `Unknown cron task: ${taskName}`;
6311
+ this.sendTo(ws, { type: MSG.ERROR, payload: { error } });
6312
+ this.ackMutation(ws, payload.requestId, {
6313
+ ok: false,
6314
+ taskName,
6315
+ reason: "unknown_task",
6316
+ error
6218
6317
  });
6318
+ break;
6219
6319
  }
6320
+ this.ackMutation(
6321
+ ws,
6322
+ payload.requestId,
6323
+ execution.success ? { ok: true, taskName } : { ok: false, taskName, reason: "failed", error: execution.error }
6324
+ );
6220
6325
  break;
6221
6326
  }
6222
- case MSG.CRON_PAUSE: {
6223
- const taskName = payload.taskName;
6224
- this.sql.exec(`UPDATE cron_tasks SET paused = 1 WHERE name = ?`, taskName);
6225
- this.broadcastStatus();
6226
- break;
6227
- }
6327
+ case MSG.CRON_PAUSE:
6228
6328
  case MSG.CRON_RESUME: {
6229
6329
  const taskName = payload.taskName;
6230
- this.sql.exec(`UPDATE cron_tasks SET paused = 0 WHERE name = ?`, taskName);
6231
- this.scheduleNextAlarm();
6330
+ if (!taskName) {
6331
+ const error = "Missing taskName";
6332
+ this.sendTo(ws, { type: MSG.ERROR, payload: { error } });
6333
+ this.ackMutation(ws, payload.requestId, { ok: false, reason: "failed", error });
6334
+ return;
6335
+ }
6336
+ const known = this.sql.exec(`SELECT name FROM cron_tasks WHERE name = ?`, taskName).toArray().length > 0;
6337
+ if (!known) {
6338
+ const error = `Unknown cron task: ${taskName}`;
6339
+ this.sendTo(ws, { type: MSG.ERROR, payload: { error } });
6340
+ this.ackMutation(ws, payload.requestId, {
6341
+ ok: false,
6342
+ taskName,
6343
+ reason: "unknown_task",
6344
+ error
6345
+ });
6346
+ break;
6347
+ }
6348
+ const paused = type === MSG.CRON_PAUSE;
6349
+ this.sql.exec(`UPDATE cron_tasks SET paused = ? WHERE name = ?`, paused ? 1 : 0, taskName);
6350
+ if (!paused) this.scheduleNextAlarm();
6232
6351
  this.broadcastStatus();
6352
+ this.ackMutation(ws, payload.requestId, { ok: true, taskName });
6233
6353
  break;
6234
6354
  }
6235
6355
  case MSG.CRON_TASKS: {
@@ -6272,7 +6392,7 @@ var CronRoom = class extends BaseRoom {
6272
6392
  // ==========================================================================
6273
6393
  async executeTask(taskName) {
6274
6394
  const taskRow = this.sql.exec(`SELECT interval_minutes, schedule, timezone FROM cron_tasks WHERE name = ?`, taskName).toArray()[0];
6275
- if (!taskRow) return false;
6395
+ if (!taskRow) return null;
6276
6396
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6277
6397
  const start = Date.now();
6278
6398
  let success = true;
@@ -6308,7 +6428,7 @@ var CronRoom = class extends BaseRoom {
6308
6428
  `DELETE FROM cron_history WHERE id NOT IN (SELECT id FROM cron_history ORDER BY id DESC LIMIT 500)`
6309
6429
  );
6310
6430
  this.broadcastStatus();
6311
- return true;
6431
+ return { taskName, startedAt, completedAt, success, durationMs, error };
6312
6432
  }
6313
6433
  // ==========================================================================
6314
6434
  // Scheduling
@@ -6378,6 +6498,21 @@ var CronRoom = class extends BaseRoom {
6378
6498
  }
6379
6499
  });
6380
6500
  }
6501
+ /**
6502
+ * Receipt for a mutation frame, addressed only to its sender. Receipts
6503
+ * are opt-in by correlation id: frames without a `requestId` keep the
6504
+ * fire-and-forget contract, so the (untyped) `requestId` from the wire
6505
+ * is the single gate here rather than a check at every call site.
6506
+ * Frames go through the shared `serverBuild` constructors so the wire
6507
+ * shape is enforced by the protocol types, not re-spelled here.
6508
+ */
6509
+ ackMutation(ws, requestId, result) {
6510
+ if (typeof requestId !== "string") return;
6511
+ this.sendTo(
6512
+ ws,
6513
+ result.ok ? serverBuild.cronAckSuccess(requestId, result.taskName) : serverBuild.cronAckFailure(requestId, result.reason, result.error, result.taskName)
6514
+ );
6515
+ }
6381
6516
  };
6382
6517
  function computeNextRunAt(task, from) {
6383
6518
  if (task.interval_minutes) {
@@ -8376,10 +8511,7 @@ async function cancelSubscription(c, opts) {
8376
8511
  });
8377
8512
  if (!res.ok) {
8378
8513
  const body = await res.json().catch(() => ({}));
8379
- throw new CancelSubscriptionError(
8380
- body.error ?? `cancel failed (${res.status})`,
8381
- res.status
8382
- );
8514
+ throw new CancelSubscriptionError(body.error ?? `cancel failed (${res.status})`, res.status);
8383
8515
  }
8384
8516
  return await res.json();
8385
8517
  }
@@ -8555,6 +8687,8 @@ export {
8555
8687
  meterAi,
8556
8688
  meterUsage,
8557
8689
  meterVectorize,
8690
+ mintAppId,
8691
+ mintUlid,
8558
8692
  noopPermissionContext,
8559
8693
  parseQuotaLimit,
8560
8694
  platformCorsPatterns,