@relayfile/sdk 0.10.46 → 0.10.48

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/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type AdminIngressStatusResponse, type AdminSyncStatusResponse, type BulkWriteInput, type BulkWriteResponse, type BackendStatusResponse, type AckResponse, type CommitForkInput, type CommitForkResponse, type AcceptDurableSubscriptionDeliveryInput, type CancelDurableResourceSubscriptionOptions, type ClaimDurableSubscriptionDeliveriesInput, type CreateOrRenewDurableResourceSubscriptionInput, type CreateForkInput, type DeleteFileInput, type DeadLetterItem, type DeadLetterFeedResponse, type DeleteWebhookOptions, type DiscardForkInput, type DurableResourceSubscription, type DurableResourceSubscriptionListResponse, type DurableSubscriptionDeliveryListResponse, type DurableSubscriptionDeliveryResponse, type EventFeedResponse, type ExportJsonResponse, type ExportOptions, type FileQueryResponse, type FileReadResponse, type FilesystemEvent, type GetEventsOptions, type GetAdminIngressStatusOptions, type GetAdminSyncStatusOptions, type GetOperationsOptions, type GetSyncDeadLettersOptions, type GetSyncIngressStatusOptions, type GetSyncStatusOptions, type GetWebhookDeadLettersOptions, type ListWebhooksOptions, type ListDurableResourceSubscriptionsOptions, type ListTreeOptions, type MergeFileInput, type MergeFileResponse, type OperationFeedResponse, type OperationStatusResponse, type QueuedResponse, type ResourceAtEventResult, type ReadFileInput, type QueryFilesOptions, type RebaseForkInput, type RebaseForkResponse, type RegisterWebhookInput, type RegisterWebhookResponse, type RelayFileReadCacheOptions, type Subscription, type SyncIngressStatusResponse, type SyncProviderStatus, type SyncStatusResponse, type TreeResponse, type WriteFileInput, type WriteQueuedResponse, type IngestWebhookInput, type WritebackItem, type WebhookDeliveryDeadLetterFeedResponse, type WebhookSubscription, type AckWritebackInput, type AckWritebackResponse, type SweepWritebackDraftsInput, type SweepWritebackDraftsResponse, type ChangeEvent, type ChangeLogQueryResult, type ChangeStreamConnection, type ChangeStreamConnectionOptions, type SubscribeOptions, type WaitForDataOptions } from "./types.js";
1
+ import { type AdminIngressStatusResponse, type AdminSyncStatusResponse, type BulkWriteInput, type BulkWriteResponse, type CheckpointSeal, type CheckpointSealRetentionRecord, type CheckpointSealRetentionSummary, type GetAdminCheckpointSealRetentionOptions, type ReconcileAdminCheckpointSealSourceInput, type ConsumedCheckpointSeal, type IssueCheckpointSealInput, type ConsumeCheckpointSealInput, type RecoverConsumedCheckpointSealInput, type VerifyCheckpointSealInput, type CheckpointSealOwnership, type HandbackCheckpointSealInput, type ResumeCheckpointSealInput, type BackendStatusResponse, type AckResponse, type CommitForkInput, type CommitForkResponse, type AcceptDurableSubscriptionDeliveryInput, type CancelDurableResourceSubscriptionOptions, type ClaimDurableSubscriptionDeliveriesInput, type CreateOrRenewDurableResourceSubscriptionInput, type CreateForkInput, type DeleteFileInput, type DeadLetterItem, type DeadLetterFeedResponse, type DeleteWebhookOptions, type DiscardForkInput, type DurableResourceSubscription, type DurableResourceSubscriptionListResponse, type DurableSubscriptionDeliveryListResponse, type DurableSubscriptionDeliveryResponse, type EventFeedResponse, type ExportJsonResponse, type ExportOptions, type FileQueryResponse, type FileReadResponse, type FilesystemEvent, type GetEventsOptions, type GetAdminIngressStatusOptions, type GetAdminSyncStatusOptions, type GetOperationsOptions, type GetSyncDeadLettersOptions, type GetSyncIngressStatusOptions, type GetSyncStatusOptions, type GetWebhookDeadLettersOptions, type ListWebhooksOptions, type ListDurableResourceSubscriptionsOptions, type ListTreeOptions, type MergeFileInput, type MergeFileResponse, type OperationFeedResponse, type OperationStatusResponse, type QueuedResponse, type ResourceAtEventResult, type ReadFileInput, type QueryFilesOptions, type RebaseForkInput, type RebaseForkResponse, type RegisterWebhookInput, type RegisterWebhookResponse, type RelayFileReadCacheOptions, type Subscription, type SyncIngressStatusResponse, type SyncProviderStatus, type SyncStatusResponse, type TreeResponse, type WriteFileInput, type WriteQueuedResponse, type IngestWebhookInput, type WritebackItem, type WebhookDeliveryDeadLetterFeedResponse, type WebhookSubscription, type AckWritebackInput, type AckWritebackResponse, type SweepWritebackDraftsInput, type SweepWritebackDraftsResponse, type ChangeEvent, type ChangeLogQueryResult, type ChangeStreamConnection, type ChangeStreamConnectionOptions, type SubscribeOptions, type WaitForDataOptions } from "./types.js";
2
2
  import type { ForkHandle } from "@relayfile/core";
3
3
  /**
4
4
  * Bearer token or token factory used for Relayfile API requests.
@@ -104,6 +104,12 @@ export declare class RelayFileClient {
104
104
  writeFile(input: WriteFileInput): Promise<WriteQueuedResponse>;
105
105
  mergeFile(input: MergeFileInput): Promise<MergeFileResponse>;
106
106
  bulkWrite(input: BulkWriteInput): Promise<BulkWriteResponse>;
107
+ issueCheckpointSeal(input: IssueCheckpointSealInput): Promise<CheckpointSeal>;
108
+ consumeCheckpointSeal(input: ConsumeCheckpointSealInput): Promise<ConsumedCheckpointSeal>;
109
+ recoverConsumedCheckpointSeal(input: RecoverConsumedCheckpointSealInput): Promise<ConsumedCheckpointSeal>;
110
+ verifyCheckpointSeal(input: VerifyCheckpointSealInput): Promise<ConsumedCheckpointSeal>;
111
+ handbackCheckpointSeal(input: HandbackCheckpointSealInput): Promise<CheckpointSealOwnership>;
112
+ resumeCheckpointSeal(input: ResumeCheckpointSealInput): Promise<CheckpointSealOwnership>;
107
113
  deleteFile(input: DeleteFileInput): Promise<WriteQueuedResponse>;
108
114
  createFork(input: CreateForkInput): Promise<ForkHandle>;
109
115
  discardFork(input: DiscardForkInput): Promise<void>;
@@ -130,6 +136,8 @@ export declare class RelayFileClient {
130
136
  getBackendStatus(correlationId?: string, signal?: AbortSignal): Promise<BackendStatusResponse>;
131
137
  getAdminIngressStatus(optionsOrCorrelationId?: GetAdminIngressStatusOptions | string, signal?: AbortSignal): Promise<AdminIngressStatusResponse>;
132
138
  getAdminSyncStatus(optionsOrCorrelationId?: GetAdminSyncStatusOptions | string, signal?: AbortSignal): Promise<AdminSyncStatusResponse>;
139
+ getAdminCheckpointSealRetention(options?: GetAdminCheckpointSealRetentionOptions): Promise<CheckpointSealRetentionSummary>;
140
+ reconcileAdminCheckpointSealSource(input: ReconcileAdminCheckpointSealSourceInput): Promise<CheckpointSealRetentionRecord>;
133
141
  getSyncStatus(workspaceId: string, options?: GetSyncStatusOptions): Promise<SyncStatusResponse>;
134
142
  /**
135
143
  * Wait until a provider is safe to read from the data plane.
package/dist/client.js CHANGED
@@ -1317,6 +1317,107 @@ export class RelayFileClient {
1317
1317
  }
1318
1318
  return result;
1319
1319
  }
1320
+ async issueCheckpointSeal(input) {
1321
+ return this.request({
1322
+ method: "POST",
1323
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals`,
1324
+ correlationId: input.correlationId,
1325
+ body: {
1326
+ root: input.root,
1327
+ sessionId: input.sessionId,
1328
+ generation: input.generation,
1329
+ expectedDigest: input.expectedDigest,
1330
+ issuanceIdempotencyKey: input.issuanceIdempotencyKey,
1331
+ ttlSeconds: input.ttlSeconds
1332
+ },
1333
+ signal: input.signal
1334
+ });
1335
+ }
1336
+ async consumeCheckpointSeal(input) {
1337
+ return this.request({
1338
+ method: "POST",
1339
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals/consume`,
1340
+ correlationId: input.correlationId,
1341
+ body: {
1342
+ sealToken: input.sealToken,
1343
+ root: input.root,
1344
+ sessionId: input.sessionId,
1345
+ generation: input.generation,
1346
+ consumerIdempotencyKey: input.consumerIdempotencyKey
1347
+ },
1348
+ signal: input.signal
1349
+ });
1350
+ }
1351
+ async recoverConsumedCheckpointSeal(input) {
1352
+ return this.request({
1353
+ method: "POST",
1354
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals/recover-consume`,
1355
+ correlationId: input.correlationId,
1356
+ body: {
1357
+ root: input.root,
1358
+ sessionId: input.sessionId,
1359
+ generation: input.generation,
1360
+ consumerIdempotencyKey: input.consumerIdempotencyKey
1361
+ },
1362
+ signal: input.signal
1363
+ });
1364
+ }
1365
+ async verifyCheckpointSeal(input) {
1366
+ const receipt = input.receipt;
1367
+ return this.request({
1368
+ method: "POST",
1369
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals/verify`,
1370
+ correlationId: input.correlationId,
1371
+ body: {
1372
+ sealId: receipt.sealId,
1373
+ root: receipt.root,
1374
+ sessionId: receipt.sessionId,
1375
+ generation: receipt.generation,
1376
+ digest: receipt.digest,
1377
+ workspaceRevision: receipt.workspaceRevision,
1378
+ eventCursor: receipt.eventCursor,
1379
+ issuedAt: receipt.issuedAt,
1380
+ expiresAt: receipt.expiresAt,
1381
+ consumedAt: receipt.consumedAt
1382
+ },
1383
+ signal: input.signal
1384
+ });
1385
+ }
1386
+ async handbackCheckpointSeal(input) {
1387
+ const receipt = input.receipt;
1388
+ return this.request({
1389
+ method: "POST",
1390
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals/handback`,
1391
+ correlationId: input.correlationId,
1392
+ body: {
1393
+ phase: input.phase,
1394
+ sealId: receipt.sealId,
1395
+ root: receipt.root,
1396
+ sessionId: receipt.sessionId,
1397
+ generation: receipt.generation,
1398
+ consumedAt: receipt.consumedAt,
1399
+ consumerIdempotencyKey: input.consumerIdempotencyKey,
1400
+ handbackIdempotencyKey: input.handbackIdempotencyKey,
1401
+ expectedDigest: input.expectedDigest
1402
+ },
1403
+ signal: input.signal
1404
+ });
1405
+ }
1406
+ async resumeCheckpointSeal(input) {
1407
+ return this.request({
1408
+ method: "POST",
1409
+ path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sync/checkpoint-seals/resume`,
1410
+ correlationId: input.correlationId,
1411
+ body: {
1412
+ sealToken: input.sealToken,
1413
+ root: input.root,
1414
+ sessionId: input.sessionId,
1415
+ generation: input.generation,
1416
+ resumeIdempotencyKey: input.resumeIdempotencyKey
1417
+ },
1418
+ signal: input.signal
1419
+ });
1420
+ }
1320
1421
  async deleteFile(input) {
1321
1422
  const query = buildQuery({ path: input.path, forkId: input.forkId });
1322
1423
  const result = await this.request({
@@ -1716,6 +1817,32 @@ export class RelayFileClient {
1716
1817
  signal: options.signal
1717
1818
  });
1718
1819
  }
1820
+ async getAdminCheckpointSealRetention(options = {}) {
1821
+ const query = buildQuery({ workspaceId: options.workspaceId });
1822
+ return this.request({
1823
+ method: "GET",
1824
+ path: `/v1/admin/checkpoint-seals${query}`,
1825
+ correlationId: options.correlationId,
1826
+ signal: options.signal
1827
+ });
1828
+ }
1829
+ async reconcileAdminCheckpointSealSource(input) {
1830
+ return this.request({
1831
+ method: "POST",
1832
+ path: `/v1/admin/checkpoint-seals/${encodeURIComponent(input.sealId)}/reconcile-source`,
1833
+ correlationId: input.correlationId,
1834
+ body: {
1835
+ workspaceId: input.workspaceId,
1836
+ root: input.root,
1837
+ sessionId: input.sessionId,
1838
+ generation: input.generation,
1839
+ expectedOwnershipStatus: input.expectedOwnershipStatus,
1840
+ reconciliationIdempotencyKey: input.reconciliationIdempotencyKey,
1841
+ confirmSourceReady: input.confirmSourceReady
1842
+ },
1843
+ signal: input.signal
1844
+ });
1845
+ }
1719
1846
  async getSyncStatus(workspaceId, options = {}) {
1720
1847
  const query = buildQuery({ provider: options.provider });
1721
1848
  return this.request({
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { RelayFileReadCacheOptions } from "./types.js";
3
3
  export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
4
4
  export { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
5
5
  export { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountModeUnavailableError, MountMultiPathUnsupportedError, MountReadyTimeoutError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
6
- export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountLocalLayout, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountSyncMode, type MountSupervisorEvent, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
6
+ export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CheckpointAndSealInput, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountLocalLayout, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountSyncMode, type MountSupervisorEvent, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
7
7
  export { RelayFileSync, type RelayFileSyncOptions, type RelayFileSyncPong, type RelayFileSyncReconnectOptions, type RelayFileSyncSocket, type RelayFileSyncStart, type RelayFileSyncState, type RelayFileSyncTokenProvider } from "./sync.js";
8
8
  export { onWrite, pathMatches, type OnWriteClient, type OnWriteHandler, type OnWriteHandlerError, type OnWriteOptions } from "./onWrite.js";
9
9
  export { InvalidStateError, MergeConflictError, ParentMovedError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
@@ -13,7 +13,7 @@ export type { ConnectCapableProvider, ConnectConnectionStatus, ConnectSession, C
13
13
  export { supportsConnect } from "./connection.js";
14
14
  export { SelfHostConnect } from "./self-host-connect.js";
15
15
  export type { SelfHostConnectOptions, SelfHostConnectResult, StartSelfHostConnectOptions, WaitForSelfHostConnectionOptions } from "./self-host-connect.js";
16
- export type { AckResponse, AcceptDurableSubscriptionDeliveryInput, AckWritebackInput, AckWritebackDraftDisposition, AckWritebackResponse, AdminIngressAlert, AdminIngressAlertProfile, AdminIngressEffectiveAlertProfile, AdminIngressAlertSeverity, AdminIngressAlertThresholds, AdminIngressAlertTotals, AdminIngressAlertType, AdminIngressStatusResponse, AdminSyncAlert, AdminSyncAlertSeverity, AdminSyncAlertThresholds, AdminSyncAlertTotals, AdminSyncAlertType, AdminSyncStatusResponse, BackendStatusResponse, BulkWriteFile, BulkWriteInput, BulkWriteResponse, CancelDurableResourceSubscriptionOptions, ChangeLogQueryResult, ChangeEvent, ChangeEventActor, ChangeEventResource, ChangeEventSummary, ChangeStreamConnection, ChangeStreamConnectionOptions, ClaimDurableSubscriptionDeliveriesInput, CommitForkInput, CommitForkResponse, ConflictErrorResponse, CreateOrRenewDurableResourceSubscriptionInput, CreateForkInput, ContentIdentity, DeleteFileInput, DeleteWebhookOptions, DeadLetterFeedResponse, DeadLetterItem, DigestBullet, DigestContext, DigestHandler, DigestSection, DigestWindow, DiscardForkInput, DurableResourceSubscription, DurableResourceSubscriptionListResponse, DurableResourceSubscriptionStatus, DurableSubscriptionDelivery, DurableSubscriptionDeliveryListResponse, DurableSubscriptionDeliveryResponse, DurableSubscriptionDeliveryStatus, DurableSubscriptionEvent, ErrorResponse, EventSummary, EventFeedResponse, ExportFormat, ExportJsonResponse, ExportOptions, FileQueryItem, FileQueryResponse, FileReadResponse, FileSemantics, FileWriteRequest, FilesystemEvent, FilesystemEventType, EventOrigin, Expansion, ExpansionLevel, GetEventsOptions, GetAdminSyncStatusOptions, GetAdminIngressStatusOptions, GetOperationsOptions, GetSyncDeadLettersOptions, GetSyncIngressStatusOptions, GetSyncStatusOptions, WaitForDataOptions, GetWebhookDeadLettersOptions, IngestWebhookInput, LayoutManifest, LayoutManifestAlias, LayoutManifestResource, ListWebhooksOptions, ListDurableResourceSubscriptionsOptions, ListTreeOptions, MergeConflictDetail, MergeFileInput, MergeFileResponse, OperationFeedResponse, OperationStatus, OperationStatusResponse, QueuedResponse, QueryFilesOptions, RebaseConflict, RebaseForkInput, RebaseForkResponse, ReadFileInput, RegisterWebhookInput, RegisterWebhookResponse, ReplayOptions, ResourceAtEventResult, SummaryExpansion, FullExpansion, DiffExpansion, ThreadExpansion, RelayFileJwtClaims, SubscribeOptions, Subscription, SyncIngressStatusResponse, SyncProviderStatus, SyncProviderStatusState, SyncRefreshRequest, SyncStatusResponse, SweepWritebackDraftsInput, SweepWritebackDraftsResponse, TreeEntry, TreeResponse, WebhookDeliveryDeadLetterFeedResponse, WebhookDeliveryDeadLetterItem, WebhookSubscription, WebhookSubscriptionHealth, WritebackActionType, WritebackDeadLetterError, WritebackDeadLetterErrorCode, WritebackListState, WritebackState, WritebackItem, WritebackItemDetail, WritebackSchemaRef, WriteFileInput, WriteQueuedResponse } from "./types.js";
16
+ export type { AckResponse, AcceptDurableSubscriptionDeliveryInput, AckWritebackInput, AckWritebackDraftDisposition, AckWritebackResponse, AdminIngressAlert, AdminIngressAlertProfile, AdminIngressEffectiveAlertProfile, AdminIngressAlertSeverity, AdminIngressAlertThresholds, AdminIngressAlertTotals, AdminIngressAlertType, AdminIngressStatusResponse, AdminSyncAlert, AdminSyncAlertSeverity, AdminSyncAlertThresholds, AdminSyncAlertTotals, AdminSyncAlertType, AdminSyncStatusResponse, BackendStatusResponse, BulkWriteFile, BulkWriteInput, BulkWriteResponse, CheckpointSeal, CheckpointSealOwnershipStatus, CheckpointSealRetentionRecord, CheckpointSealRetentionSummary, GetAdminCheckpointSealRetentionOptions, ReconcileAdminCheckpointSealSourceInput, ConsumedCheckpointSeal, IssueCheckpointSealInput, ConsumeCheckpointSealInput, RecoverConsumedCheckpointSealInput, VerifyCheckpointSealInput, CheckpointSealOwnership, HandbackCheckpointSealInput, ResumeCheckpointSealInput, CancelDurableResourceSubscriptionOptions, ChangeLogQueryResult, ChangeEvent, ChangeEventActor, ChangeEventResource, ChangeEventSummary, ChangeStreamConnection, ChangeStreamConnectionOptions, ClaimDurableSubscriptionDeliveriesInput, CommitForkInput, CommitForkResponse, ConflictErrorResponse, CreateOrRenewDurableResourceSubscriptionInput, CreateForkInput, ContentIdentity, DeleteFileInput, DeleteWebhookOptions, DeadLetterFeedResponse, DeadLetterItem, DigestBullet, DigestContext, DigestHandler, DigestSection, DigestWindow, DiscardForkInput, DurableResourceSubscription, DurableResourceSubscriptionListResponse, DurableResourceSubscriptionStatus, DurableSubscriptionDelivery, DurableSubscriptionDeliveryListResponse, DurableSubscriptionDeliveryResponse, DurableSubscriptionDeliveryStatus, DurableSubscriptionEvent, ErrorResponse, EventSummary, EventFeedResponse, ExportFormat, ExportJsonResponse, ExportOptions, FileQueryItem, FileQueryResponse, FileReadResponse, FileSemantics, FileWriteRequest, FilesystemEvent, FilesystemEventType, EventOrigin, Expansion, ExpansionLevel, GetEventsOptions, GetAdminSyncStatusOptions, GetAdminIngressStatusOptions, GetOperationsOptions, GetSyncDeadLettersOptions, GetSyncIngressStatusOptions, GetSyncStatusOptions, WaitForDataOptions, GetWebhookDeadLettersOptions, IngestWebhookInput, LayoutManifest, LayoutManifestAlias, LayoutManifestResource, ListWebhooksOptions, ListDurableResourceSubscriptionsOptions, ListTreeOptions, MergeConflictDetail, MergeFileInput, MergeFileResponse, OperationFeedResponse, OperationStatus, OperationStatusResponse, QueuedResponse, QueryFilesOptions, RebaseConflict, RebaseForkInput, RebaseForkResponse, ReadFileInput, RegisterWebhookInput, RegisterWebhookResponse, ReplayOptions, ResourceAtEventResult, SummaryExpansion, FullExpansion, DiffExpansion, ThreadExpansion, RelayFileJwtClaims, SubscribeOptions, Subscription, SyncIngressStatusResponse, SyncProviderStatus, SyncProviderStatusState, SyncRefreshRequest, SyncStatusResponse, SweepWritebackDraftsInput, SweepWritebackDraftsResponse, TreeEntry, TreeResponse, WebhookDeliveryDeadLetterFeedResponse, WebhookDeliveryDeadLetterItem, WebhookSubscription, WebhookSubscriptionHealth, WritebackActionType, WritebackDeadLetterError, WritebackDeadLetterErrorCode, WritebackListState, WritebackState, WritebackItem, WritebackItemDetail, WritebackSchemaRef, WriteFileInput, WriteQueuedResponse } from "./types.js";
17
17
  export type { ForkHandle, ForkOptions } from "@relayfile/core";
18
18
  export type { WriteEvent, WriteEventActor, WriteEventOperation, WriteEventSource } from "@relayfile/core";
19
19
  export { WritebackConsumer } from "./writeback-consumer.js";
@@ -11,6 +11,8 @@ const DEFAULT_READY_POLL_INTERVAL_MS = 250;
11
11
  const DEFAULT_STOP_TIMEOUT_MS = 10_000;
12
12
  const LOG_ROTATION_MAX_BYTES = 10 * 1024 * 1024;
13
13
  const LOG_ROTATION_FILES = 3;
14
+ const DEFAULT_CHECKPOINT_TIMEOUT_MS = 30_000;
15
+ const MAX_CHECKPOINT_OUTPUT_BYTES = 1024 * 1024;
14
16
  const FUSE_UNAVAILABLE_SIGNATURE = "fuse mode is not available in this build";
15
17
  export const defaultMountLauncher = createDefaultMountLauncher();
16
18
  export function createDefaultMountLauncher(options = {}) {
@@ -81,6 +83,10 @@ async function startRelayfileMount(input, options) {
81
83
  outputBuffer,
82
84
  input,
83
85
  localDir: mountLocalDir,
86
+ command,
87
+ effectiveEnv,
88
+ cwd: input.cwd ?? mountLocalDir,
89
+ spawnImpl: options.spawnImpl ?? spawn,
84
90
  now: options.now ?? Date.now,
85
91
  readyPollIntervalMs: options.readyPollIntervalMs ?? DEFAULT_READY_POLL_INTERVAL_MS
86
92
  });
@@ -96,9 +102,17 @@ class RelayfileMountProcessInstance {
96
102
  localDir;
97
103
  now;
98
104
  readyPollIntervalMs;
105
+ command;
106
+ effectiveEnv;
107
+ cwd;
108
+ spawnImpl;
99
109
  exited = false;
100
110
  stopping;
101
111
  readyResolved = false;
112
+ checkpointPromise;
113
+ get stopped() {
114
+ return this.exited;
115
+ }
102
116
  constructor(input) {
103
117
  this.child = input.child;
104
118
  this.logStream = input.logStream;
@@ -106,6 +120,10 @@ class RelayfileMountProcessInstance {
106
120
  this.outputBuffer = input.outputBuffer;
107
121
  this.input = input.input;
108
122
  this.localDir = input.localDir;
123
+ this.command = input.command;
124
+ this.effectiveEnv = input.effectiveEnv;
125
+ this.cwd = input.cwd;
126
+ this.spawnImpl = input.spawnImpl;
109
127
  this.pid = input.child.pid ?? undefined;
110
128
  this.now = input.now;
111
129
  this.readyPollIntervalMs = input.readyPollIntervalMs;
@@ -136,6 +154,46 @@ class RelayfileMountProcessInstance {
136
154
  }
137
155
  await this.stopping;
138
156
  }
157
+ async checkpointAndSeal(input) {
158
+ this.validateCheckpointPreconditions(input);
159
+ if (!this.checkpointPromise) {
160
+ this.checkpointPromise = this.performCheckpointAndSeal(input);
161
+ }
162
+ return this.checkpointPromise;
163
+ }
164
+ validateCheckpointPreconditions(input) {
165
+ validateCheckpointInput(input);
166
+ if (normalizeMountMode(this.effectiveEnv.RELAYFILE_MOUNT_MODE) !== "poll") {
167
+ throw new RelayfileSetupError("checkpointAndSeal currently requires a poll-mode mount; a FUSE mount must remain running until daemon checkpoint IPC is available.", "checkpoint_seal_mode_unavailable");
168
+ }
169
+ if (normalizeMountSyncMode(this.effectiveEnv.RELAYFILE_MOUNT_SYNC_MODE) === "pull-only") {
170
+ throw new RelayfileSetupError("checkpointAndSeal cannot drain a pull-only mount.", "checkpoint_seal_mode_unavailable");
171
+ }
172
+ if (normalizeRemotePath(this.effectiveEnv.RELAYFILE_REMOTE_PATH ?? "/") !== "/") {
173
+ throw new RelayfileSetupError("checkpointAndSeal v1 requires a full-root (/) mount.", "checkpoint_seal_root_unavailable");
174
+ }
175
+ const scopes = (this.effectiveEnv.RELAYFILE_MOUNT_SCOPES ?? "")
176
+ .split(/\s+/)
177
+ .map((scope) => scope.trim())
178
+ .filter(Boolean);
179
+ if (!scopeGrants(scopes, "sync", "trigger") ||
180
+ !scopeGrants(scopes, "ops", "read") ||
181
+ !scopeGrantsFullRoot(scopes, "read") ||
182
+ !scopeGrantsFullRoot(scopes, "write")) {
183
+ throw new RelayfileSetupError("checkpointAndSeal requires sync:trigger, ops:read, and full-root fs:read/fs:write scopes.", "checkpoint_seal_scope_unavailable");
184
+ }
185
+ }
186
+ async performCheckpointAndSeal(input) {
187
+ await this.ready;
188
+ await this.stop();
189
+ return runCheckpointSealProcess({
190
+ command: this.command,
191
+ cwd: this.cwd,
192
+ env: this.effectiveEnv,
193
+ spawnImpl: this.spawnImpl,
194
+ input
195
+ });
196
+ }
139
197
  async waitForReady() {
140
198
  const startedAt = this.now();
141
199
  const timeoutAt = startedAt + this.input.readyTimeoutMs;
@@ -189,6 +247,178 @@ class RelayfileMountProcessInstance {
189
247
  await unlinkIfExists(this.pidPath);
190
248
  }
191
249
  }
250
+ async function runCheckpointSealProcess(input) {
251
+ const { sessionId, generation, timeoutMs, ttlSeconds } = validateCheckpointInput(input.input);
252
+ const workspaceId = input.env.RELAYFILE_WORKSPACE?.trim() ?? "";
253
+ const root = normalizeRemotePath(input.env.RELAYFILE_REMOTE_PATH);
254
+ const args = [
255
+ "--checkpoint-and-seal",
256
+ "--checkpoint-session", sessionId,
257
+ "--checkpoint-generation", String(generation),
258
+ "--checkpoint-seal-ttl", `${ttlSeconds}s`,
259
+ "--timeout", `${timeoutMs}ms`
260
+ ];
261
+ const child = input.spawnImpl(input.command, args, {
262
+ cwd: input.cwd,
263
+ env: input.env,
264
+ stdio: ["ignore", "pipe", "pipe"]
265
+ });
266
+ return new Promise((resolve, reject) => {
267
+ let stdout = "";
268
+ let stderr = "";
269
+ let settled = false;
270
+ let timer;
271
+ const finish = (error, seal) => {
272
+ if (settled)
273
+ return;
274
+ settled = true;
275
+ if (timer)
276
+ clearTimeout(timer);
277
+ input.input.signal?.removeEventListener("abort", onAbort);
278
+ if (error)
279
+ reject(error);
280
+ else
281
+ resolve(seal);
282
+ };
283
+ const append = (current, chunk) => {
284
+ const next = current + (Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
285
+ if (Buffer.byteLength(next) > MAX_CHECKPOINT_OUTPUT_BYTES) {
286
+ child.kill("SIGKILL");
287
+ finish(new RelayfileSetupError("relayfile-mount checkpoint output exceeded 1 MiB.", "checkpoint_seal_invalid_output"));
288
+ }
289
+ return next;
290
+ };
291
+ child.stdout?.on("data", (chunk) => { stdout = append(stdout, chunk); });
292
+ child.stderr?.on("data", (chunk) => { stderr = append(stderr, chunk); });
293
+ child.once("error", (error) => {
294
+ finish(new RelayfileSetupError(`relayfile-mount checkpoint failed to start: ${error.message}`, "checkpoint_seal_failed"));
295
+ });
296
+ child.once("exit", (code, signal) => {
297
+ if (settled)
298
+ return;
299
+ if (code !== 0) {
300
+ const detail = stderr.trim().slice(-2_000);
301
+ finish(new RelayfileSetupError(`relayfile-mount checkpoint failed (${signal ?? `exit ${code ?? "unknown"}`})${detail ? `: ${detail}` : ""}`, "checkpoint_seal_failed"));
302
+ return;
303
+ }
304
+ let parsed;
305
+ try {
306
+ parsed = JSON.parse(stdout.trim());
307
+ }
308
+ catch {
309
+ finish(new RelayfileSetupError("relayfile-mount returned malformed checkpoint JSON.", "checkpoint_seal_invalid_output"));
310
+ return;
311
+ }
312
+ try {
313
+ finish(undefined, validateCheckpointSeal(parsed, workspaceId, root, sessionId, generation));
314
+ }
315
+ catch (error) {
316
+ finish(error instanceof Error ? error : new Error(String(error)));
317
+ }
318
+ });
319
+ const onAbort = () => {
320
+ child.kill("SIGKILL");
321
+ finish(new CloudAbortError("checkpointAndSeal"));
322
+ };
323
+ input.input.signal?.addEventListener("abort", onAbort, { once: true });
324
+ timer = setTimeout(() => {
325
+ child.kill("SIGKILL");
326
+ finish(new RelayfileSetupError(`checkpointAndSeal timed out after ${timeoutMs}ms.`, "checkpoint_seal_timeout"));
327
+ }, timeoutMs);
328
+ timer.unref?.();
329
+ });
330
+ }
331
+ function validateCheckpointInput(input) {
332
+ const sessionId = typeof input.sessionId === "string" ? input.sessionId.trim() : "";
333
+ const generation = input.generation;
334
+ const timeoutMs = input.timeoutMs ?? DEFAULT_CHECKPOINT_TIMEOUT_MS;
335
+ const ttlSeconds = input.ttlSeconds ?? 60;
336
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(sessionId)) {
337
+ throw new RelayfileSetupError("checkpointAndSeal requires a valid sessionId.", "checkpoint_seal_invalid_input");
338
+ }
339
+ if (!Number.isSafeInteger(generation) || generation <= 0 ||
340
+ !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 ||
341
+ !Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 300) {
342
+ throw new RelayfileSetupError("checkpointAndSeal requires positive safe generation/timeout values and ttlSeconds <= 300.", "checkpoint_seal_invalid_input");
343
+ }
344
+ if (input.signal?.aborted) {
345
+ throw new CloudAbortError("checkpointAndSeal");
346
+ }
347
+ return { sessionId, generation, timeoutMs, ttlSeconds };
348
+ }
349
+ function scopeGrants(scopes, resource, action) {
350
+ const bare = `${resource}:${action}`;
351
+ return scopes.some((scope) => {
352
+ if (scope === bare)
353
+ return true;
354
+ const [plane, grantedResource, grantedAction] = scope.split(":", 4);
355
+ return ((plane === "relayfile" || plane === "*") &&
356
+ (grantedResource === resource || grantedResource === "*") &&
357
+ (grantedAction === action || grantedAction === "*"));
358
+ });
359
+ }
360
+ function scopeGrantsFullRoot(scopes, action) {
361
+ const relevantNarrowScopes = [];
362
+ for (const scope of scopes) {
363
+ const parsed = parseStructuredScope(scope);
364
+ if (!parsed)
365
+ continue;
366
+ const { plane, resource, action: grantedAction, path: scopePath } = parsed;
367
+ const actionMatches = grantedAction === action ||
368
+ grantedAction === "*" ||
369
+ grantedAction === "manage";
370
+ if (!actionMatches)
371
+ continue;
372
+ if ((plane === "relayfile" || plane === "*") &&
373
+ (resource === "fs" || resource === "*")) {
374
+ if (scopePath === undefined || scopePath.trim() === "*")
375
+ return true;
376
+ relevantNarrowScopes.push(scopePath.trim());
377
+ continue;
378
+ }
379
+ if (plane === "workspace") {
380
+ if (scopePath === undefined || scopePath.trim() === "*")
381
+ return true;
382
+ relevantNarrowScopes.push(scopePath.trim());
383
+ }
384
+ }
385
+ if (relevantNarrowScopes.some((scopePath) => scopePath === "/**")) {
386
+ return true;
387
+ }
388
+ return relevantNarrowScopes.length === 0 && scopes.includes(`fs:${action}`);
389
+ }
390
+ function parseStructuredScope(scope) {
391
+ const first = scope.indexOf(":");
392
+ const second = first < 0 ? -1 : scope.indexOf(":", first + 1);
393
+ if (first < 1 || second <= first + 1)
394
+ return null;
395
+ const third = scope.indexOf(":", second + 1);
396
+ return {
397
+ plane: scope.slice(0, first),
398
+ resource: scope.slice(first + 1, second),
399
+ action: scope.slice(second + 1, third < 0 ? undefined : third),
400
+ path: third < 0 ? undefined : scope.slice(third + 1)
401
+ };
402
+ }
403
+ function validateCheckpointSeal(value, workspaceId, root, sessionId, generation) {
404
+ if (!value || typeof value !== "object") {
405
+ throw new RelayfileSetupError("relayfile-mount returned an invalid checkpoint seal.", "checkpoint_seal_invalid_output");
406
+ }
407
+ const seal = value;
408
+ const strings = [seal.sealId, seal.sealToken, seal.workspaceId, seal.root, seal.digest, seal.workspaceRevision, seal.eventCursor, seal.issuedAt, seal.expiresAt];
409
+ if (strings.some((field) => typeof field !== "string") ||
410
+ seal.sealToken.trim() === "" ||
411
+ seal.workspaceId !== workspaceId ||
412
+ normalizeRemotePath(seal.root) !== root ||
413
+ seal.sessionId !== sessionId ||
414
+ seal.generation !== generation ||
415
+ !seal.digest.startsWith("sha256:") ||
416
+ Number.isNaN(Date.parse(seal.issuedAt)) ||
417
+ Number.isNaN(Date.parse(seal.expiresAt))) {
418
+ throw new RelayfileSetupError("relayfile-mount returned an unbound or incomplete checkpoint seal.", "checkpoint_seal_invalid_output");
419
+ }
420
+ return seal;
421
+ }
192
422
  function pipeChildOutput(child, logStream, outputBuffer, input) {
193
423
  const appendChunk = (streamName, chunk) => {
194
424
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -1,5 +1,6 @@
1
1
  import type { WorkspaceHandle } from "./setup.js";
2
2
  import type { AccessTokenProvider } from "./client.js";
3
+ import type { CheckpointSeal } from "./types.js";
3
4
  export declare const WORKSPACE_INTEGRATION_PROVIDERS: readonly ["github", "slack-sage", "slack-my-senior-dev", "slack-nightcto", "notion", "linear"];
4
5
  export type WorkspaceIntegrationProvider = (typeof WORKSPACE_INTEGRATION_PROVIDERS)[number];
5
6
  export interface WorkspacePermissions {
@@ -121,6 +122,13 @@ export interface MountedWorkspaceStatus {
121
122
  pendingWriteback?: number;
122
123
  pendingConflicts?: number;
123
124
  }
125
+ export interface CheckpointAndSealInput {
126
+ sessionId: string;
127
+ generation: number;
128
+ timeoutMs?: number;
129
+ ttlSeconds?: number;
130
+ signal?: AbortSignal;
131
+ }
124
132
  export interface ReadMountedWorkspaceStatusInput {
125
133
  localDir: string;
126
134
  workspaceId: string;
@@ -144,6 +152,7 @@ export interface MountedWorkspaceHandle {
144
152
  readonly suggestedRefreshAt: string | null;
145
153
  env(): Record<string, string>;
146
154
  status(): Promise<MountedWorkspaceStatus>;
155
+ checkpointAndSeal(input: CheckpointAndSealInput): Promise<CheckpointSeal>;
147
156
  stop(): Promise<void>;
148
157
  }
149
158
  export interface MountSupervisorEvent {
@@ -166,12 +175,26 @@ export interface MountLauncherStart {
166
175
  onEvent?: (event: MountLauncherEvent) => void;
167
176
  background?: boolean;
168
177
  }
169
- export interface MountLauncherInstance {
178
+ interface MountLauncherInstanceBase {
170
179
  pid?: number;
171
180
  ready: Promise<void>;
172
181
  status(): Promise<MountedWorkspaceStatus>;
173
182
  stop(): Promise<void>;
174
183
  }
184
+ /** A launcher without checkpoint support may report stop state optionally. */
185
+ export interface BasicMountLauncherInstance extends MountLauncherInstanceBase {
186
+ readonly stopped?: boolean;
187
+ checkpointAndSeal?: never;
188
+ }
189
+ /**
190
+ * Checkpoint-capable launchers must report whether the physical daemon stopped.
191
+ * The SDK uses this signal to retire a failed checkpoint safely.
192
+ */
193
+ export interface CheckpointCapableMountLauncherInstance extends MountLauncherInstanceBase {
194
+ readonly stopped: boolean;
195
+ checkpointAndSeal(input: CheckpointAndSealInput): Promise<CheckpointSeal>;
196
+ }
197
+ export type MountLauncherInstance = BasicMountLauncherInstance | CheckpointCapableMountLauncherInstance;
175
198
  export interface MountLauncher {
176
199
  start(input: MountLauncherStart): Promise<MountLauncherInstance>;
177
200
  }
@@ -239,3 +262,4 @@ export interface AgentWorkspaceInvite {
239
262
  createdAt?: string;
240
263
  name?: string;
241
264
  }
265
+ export {};
package/dist/setup.js CHANGED
@@ -21,6 +21,11 @@ const DEFAULT_MOUNT_SYNC_MODE = "mirror";
21
21
  const DEFAULT_MOUNT_HEALTH_INTERVAL_MS = 30_000;
22
22
  const DEFAULT_MOUNT_REFRESH_RETRY_BASE_MS = 1_000;
23
23
  const DEFAULT_MOUNT_REFRESH_RETRY_MAX_MS = 60_000;
24
+ const confirmedMountStop = Symbol("confirmedMountStop");
25
+ function hasConfirmedMountStop(handle) {
26
+ const candidate = handle;
27
+ return candidate[confirmedMountStop]?.() === true;
28
+ }
24
29
  const TOKEN_REFRESH_AGE_MS = 55 * 60 * 1000;
25
30
  const nodeOnlyMountLauncher = {
26
31
  async start() {
@@ -752,6 +757,8 @@ class MountedWorkspaceHandleImpl {
752
757
  readStatus;
753
758
  readySnapshot = true;
754
759
  stopPromise;
760
+ checkpointPromise;
761
+ checkpointStopped = false;
755
762
  constructor(input) {
756
763
  this.mountSession = input.mountSession;
757
764
  this.workspaceId = input.mountSession.workspaceId;
@@ -803,6 +810,41 @@ class MountedWorkspaceHandleImpl {
803
810
  }
804
811
  await this.stopPromise;
805
812
  }
813
+ async checkpointAndSeal(input) {
814
+ if (!this.checkpointPromise) {
815
+ if (this.probeOnly || !this.launcherInstance?.checkpointAndSeal) {
816
+ throw new RelayfileSetupError("checkpointAndSeal requires a locally managed relayfile-mount launcher.", "checkpoint_seal_unavailable");
817
+ }
818
+ if (typeof this.launcherInstance.stopped !== "boolean") {
819
+ throw new RelayfileSetupError("checkpointAndSeal requires a launcher that reports physical stop state.", "checkpoint_seal_launcher_contract_invalid");
820
+ }
821
+ this.checkpointPromise = this.performCheckpointAndSeal(input);
822
+ }
823
+ return this.checkpointPromise;
824
+ }
825
+ [confirmedMountStop]() {
826
+ return this.checkpointStopped;
827
+ }
828
+ async performCheckpointAndSeal(input) {
829
+ try {
830
+ const seal = await this.launcherInstance.checkpointAndSeal(input);
831
+ this.checkpointStopped = true;
832
+ this.readySnapshot = false;
833
+ return seal;
834
+ }
835
+ catch (error) {
836
+ if (this.launcherInstance?.stopped === true) {
837
+ this.checkpointStopped = true;
838
+ this.readySnapshot = false;
839
+ }
840
+ else {
841
+ // A launcher precondition can reject before touching the daemon. Keep
842
+ // both readiness and retryability when no physical stop was confirmed.
843
+ this.checkpointPromise = undefined;
844
+ }
845
+ throw error;
846
+ }
847
+ }
806
848
  async performStop() {
807
849
  this.readySnapshot = false;
808
850
  if (this.probeOnly || !this.launcherInstance) {
@@ -846,6 +888,16 @@ class SharedMountedWorkspaceHandle {
846
888
  status() {
847
889
  return this.mounted.status();
848
890
  }
891
+ async checkpointAndSeal(input) {
892
+ try {
893
+ return await this.mounted.checkpointAndSeal(input);
894
+ }
895
+ finally {
896
+ if (hasConfirmedMountStop(this.mounted)) {
897
+ this.onStopped();
898
+ }
899
+ }
900
+ }
849
901
  async stop() {
850
902
  if (!this.stopPromise)
851
903
  this.stopPromise = this.performStop();
@@ -872,6 +924,8 @@ class SupervisedMountedWorkspaceHandle {
872
924
  refreshPromise;
873
925
  stopPromise;
874
926
  stopped = false;
927
+ checkpointing = false;
928
+ supervisorEpoch = 0;
875
929
  supervisorReady = true;
876
930
  refreshAttempts = 0;
877
931
  constructor(input) {
@@ -928,8 +982,52 @@ class SupervisedMountedWorkspaceHandle {
928
982
  this.stopPromise = this.performStop();
929
983
  await this.stopPromise;
930
984
  }
985
+ async checkpointAndSeal(input) {
986
+ this.checkpointing = true;
987
+ this.supervisorEpoch += 1;
988
+ this.supervisorReady = false;
989
+ if (this.timer)
990
+ clearTimeout(this.timer);
991
+ this.timer = undefined;
992
+ await this.refreshPromise?.catch(() => { });
993
+ try {
994
+ const seal = await this.mounted.checkpointAndSeal(input);
995
+ this.retireAfterCheckpointStop();
996
+ return seal;
997
+ }
998
+ catch (error) {
999
+ if (hasConfirmedMountStop(this.mounted)) {
1000
+ this.retireAfterCheckpointStop();
1001
+ }
1002
+ else {
1003
+ // Validation/mode failures occur before the daemon is stopped. Restore
1004
+ // the supervisor with a fresh epoch and leave the shared ensured
1005
+ // handle reusable. Work from the fenced epoch cannot replace it.
1006
+ this.checkpointing = false;
1007
+ this.supervisorEpoch += 1;
1008
+ this.supervisorReady = this.mounted.ready;
1009
+ this.schedule();
1010
+ }
1011
+ throw error;
1012
+ }
1013
+ }
1014
+ [confirmedMountStop]() {
1015
+ return this.stopped && hasConfirmedMountStop(this.mounted);
1016
+ }
1017
+ retireAfterCheckpointStop() {
1018
+ this.stopped = true;
1019
+ this.checkpointing = false;
1020
+ this.supervisorEpoch += 1;
1021
+ this.supervisorReady = false;
1022
+ this.signal?.removeEventListener("abort", this.handleAbort);
1023
+ if (this.timer)
1024
+ clearTimeout(this.timer);
1025
+ this.timer = undefined;
1026
+ }
931
1027
  async performStop() {
932
1028
  this.stopped = true;
1029
+ this.checkpointing = false;
1030
+ this.supervisorEpoch += 1;
933
1031
  this.supervisorReady = false;
934
1032
  this.signal?.removeEventListener("abort", this.handleAbort);
935
1033
  if (this.timer)
@@ -942,8 +1040,9 @@ class SupervisedMountedWorkspaceHandle {
942
1040
  void this.stop();
943
1041
  };
944
1042
  schedule(delayOverrideMs) {
945
- if (this.stopped || this.timer)
1043
+ if (this.stopped || this.checkpointing || this.timer)
946
1044
  return;
1045
+ const epoch = this.supervisorEpoch;
947
1046
  const suggestedRefreshAtMs = Date.parse(this.mounted.suggestedRefreshAt ?? "");
948
1047
  const untilRefresh = Number.isFinite(suggestedRefreshAtMs)
949
1048
  ? Math.max(0, suggestedRefreshAtMs - Date.now())
@@ -951,20 +1050,27 @@ class SupervisedMountedWorkspaceHandle {
951
1050
  const delayMs = delayOverrideMs ?? Math.min(this.healthCheckIntervalMs, untilRefresh);
952
1051
  this.timer = setTimeout(() => {
953
1052
  this.timer = undefined;
954
- this.refreshPromise = this.checkAndRefresh().finally(() => {
955
- this.refreshPromise = undefined;
1053
+ if (!this.isActiveEpoch(epoch))
1054
+ return;
1055
+ const refresh = this.checkAndRefresh(epoch);
1056
+ const tracked = refresh.finally(() => {
1057
+ if (this.refreshPromise === tracked)
1058
+ this.refreshPromise = undefined;
956
1059
  });
1060
+ this.refreshPromise = tracked;
957
1061
  }, Math.max(1_000, delayMs));
958
1062
  this.timer.unref?.();
959
1063
  }
960
- async checkAndRefresh() {
961
- if (this.stopped)
1064
+ async checkAndRefresh(epoch) {
1065
+ if (!this.isActiveEpoch(epoch))
962
1066
  return;
963
1067
  try {
964
1068
  const suggestedRefreshAtMs = Date.parse(this.mounted.suggestedRefreshAt ?? "");
965
1069
  const refreshDue = Number.isFinite(suggestedRefreshAtMs) && Date.now() >= suggestedRefreshAtMs;
966
1070
  if (!refreshDue) {
967
1071
  const status = await this.mounted.status();
1072
+ if (!this.isActiveEpoch(epoch))
1073
+ return;
968
1074
  if (status.ready) {
969
1075
  this.supervisorReady = true;
970
1076
  this.refreshAttempts = 0;
@@ -972,8 +1078,8 @@ class SupervisedMountedWorkspaceHandle {
972
1078
  return;
973
1079
  }
974
1080
  }
975
- await this.replaceMount();
976
- if (this.stopped)
1081
+ await this.replaceMount(epoch);
1082
+ if (!this.isActiveEpoch(epoch))
977
1083
  return;
978
1084
  this.supervisorReady = true;
979
1085
  this.refreshAttempts = 0;
@@ -986,7 +1092,7 @@ class SupervisedMountedWorkspaceHandle {
986
1092
  this.schedule();
987
1093
  }
988
1094
  catch (error) {
989
- if (this.stopped)
1095
+ if (!this.isActiveEpoch(epoch))
990
1096
  return;
991
1097
  this.supervisorReady = false;
992
1098
  this.refreshAttempts += 1;
@@ -1002,20 +1108,23 @@ class SupervisedMountedWorkspaceHandle {
1002
1108
  this.schedule(retryInMs);
1003
1109
  }
1004
1110
  }
1005
- async replaceMount() {
1111
+ async replaceMount(epoch) {
1006
1112
  const previous = this.mounted;
1007
- if (this.stopped)
1113
+ if (!this.isActiveEpoch(epoch))
1008
1114
  return;
1009
1115
  await previous.stop().catch(() => { });
1010
- if (this.stopped)
1116
+ if (!this.isActiveEpoch(epoch))
1011
1117
  return;
1012
1118
  const replacement = await this.launch();
1013
- if (this.stopped) {
1119
+ if (!this.isActiveEpoch(epoch)) {
1014
1120
  await replacement.stop().catch(() => { });
1015
1121
  return;
1016
1122
  }
1017
1123
  this.mounted = replacement;
1018
1124
  }
1125
+ isActiveEpoch(epoch) {
1126
+ return !this.stopped && !this.checkpointing && epoch === this.supervisorEpoch;
1127
+ }
1019
1128
  emit(event) {
1020
1129
  void Promise.resolve(this.onEvent?.(event)).catch(() => { });
1021
1130
  }
package/dist/types.d.ts CHANGED
@@ -128,6 +128,134 @@ export interface BulkWriteResponse {
128
128
  }>;
129
129
  correlationId: string;
130
130
  }
131
+ export interface CheckpointSeal {
132
+ sealId: string;
133
+ sealToken?: string;
134
+ workspaceId: string;
135
+ root: string;
136
+ sessionId: string;
137
+ generation: number;
138
+ digest: string;
139
+ workspaceRevision: string;
140
+ eventCursor: string;
141
+ issuedAt: string;
142
+ expiresAt: string;
143
+ consumedAt?: string;
144
+ }
145
+ export type CheckpointSealOwnershipStatus = "unconsumed" | "consumed" | "released" | "source-resumed";
146
+ export interface CheckpointSealRetentionRecord {
147
+ sealId: string;
148
+ workspaceId: string;
149
+ root: string;
150
+ sessionId: string;
151
+ generation: number;
152
+ ownershipStatus: CheckpointSealOwnershipStatus;
153
+ issuedAt: string;
154
+ expiresAt: string;
155
+ consumedAt?: string;
156
+ handbackReleasedAt?: string;
157
+ sourceResumedAt?: string;
158
+ adminReconciledAt?: string;
159
+ }
160
+ export interface CheckpointSealRetentionSummary {
161
+ generatedAt: string;
162
+ unresumedTotal: number;
163
+ unresumedByWorkspace: Record<string, number>;
164
+ records: CheckpointSealRetentionRecord[];
165
+ }
166
+ export interface GetAdminCheckpointSealRetentionOptions {
167
+ workspaceId?: string;
168
+ correlationId?: string;
169
+ signal?: AbortSignal;
170
+ }
171
+ export interface ReconcileAdminCheckpointSealSourceInput {
172
+ sealId: string;
173
+ workspaceId: string;
174
+ root: string;
175
+ sessionId: string;
176
+ generation: number;
177
+ expectedOwnershipStatus: "unconsumed" | "released";
178
+ reconciliationIdempotencyKey: string;
179
+ confirmSourceReady: true;
180
+ correlationId?: string;
181
+ signal?: AbortSignal;
182
+ }
183
+ export interface ConsumedCheckpointSeal extends Omit<CheckpointSeal, "sealToken" | "consumedAt"> {
184
+ sealToken?: never;
185
+ consumedAt: string;
186
+ }
187
+ export interface IssueCheckpointSealInput {
188
+ workspaceId: string;
189
+ root: string;
190
+ sessionId: string;
191
+ generation: number;
192
+ expectedDigest: string;
193
+ /** Stable per-attempt key. Response-loss retries rotate the lost bearer safely. */
194
+ issuanceIdempotencyKey: string;
195
+ ttlSeconds?: number;
196
+ correlationId?: string;
197
+ signal?: AbortSignal;
198
+ }
199
+ export interface ConsumeCheckpointSealInput {
200
+ workspaceId: string;
201
+ sealToken: string;
202
+ root: string;
203
+ sessionId: string;
204
+ generation: number;
205
+ consumerIdempotencyKey: string;
206
+ correlationId?: string;
207
+ signal?: AbortSignal;
208
+ }
209
+ export interface RecoverConsumedCheckpointSealInput {
210
+ workspaceId: string;
211
+ root: string;
212
+ sessionId: string;
213
+ generation: number;
214
+ consumerIdempotencyKey: string;
215
+ correlationId?: string;
216
+ signal?: AbortSignal;
217
+ }
218
+ export interface VerifyCheckpointSealInput {
219
+ workspaceId: string;
220
+ receipt: ConsumedCheckpointSeal;
221
+ correlationId?: string;
222
+ signal?: AbortSignal;
223
+ }
224
+ export interface CheckpointSealOwnership {
225
+ sealId: string;
226
+ workspaceId: string;
227
+ root: string;
228
+ sessionId: string;
229
+ generation: number;
230
+ status: "prepared" | "released" | "source-resumed";
231
+ digest: string;
232
+ workspaceRevision: string;
233
+ eventCursor: string;
234
+ consumedAt?: string;
235
+ preparedAt?: string;
236
+ releasedAt?: string;
237
+ sourceResumedAt?: string;
238
+ }
239
+ export interface HandbackCheckpointSealInput {
240
+ workspaceId: string;
241
+ phase: "prepare" | "commit";
242
+ receipt: ConsumedCheckpointSeal;
243
+ consumerIdempotencyKey: string;
244
+ handbackIdempotencyKey: string;
245
+ expectedDigest: string;
246
+ correlationId?: string;
247
+ signal?: AbortSignal;
248
+ }
249
+ export interface ResumeCheckpointSealInput {
250
+ workspaceId: string;
251
+ sealToken: string;
252
+ root: string;
253
+ sessionId: string;
254
+ generation: number;
255
+ resumeIdempotencyKey: string;
256
+ correlationId?: string;
257
+ signal?: AbortSignal;
258
+ }
131
259
  export interface FileQueryItem {
132
260
  path: string;
133
261
  revision: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.10.46",
3
+ "version": "0.10.48",
4
4
  "description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -59,15 +59,15 @@
59
59
  "prepublishOnly": "npm run build"
60
60
  },
61
61
  "dependencies": {
62
- "@relayfile/core": "0.10.46",
62
+ "@relayfile/core": "0.10.48",
63
63
  "ignore": "^7.0.5",
64
64
  "tar": "^7.5.10"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@relayfile/mount-darwin-arm64": "0.10.46",
68
- "@relayfile/mount-darwin-x64": "0.10.46",
69
- "@relayfile/mount-linux-arm64": "0.10.46",
70
- "@relayfile/mount-linux-x64": "0.10.46"
67
+ "@relayfile/mount-darwin-arm64": "0.10.48",
68
+ "@relayfile/mount-darwin-x64": "0.10.48",
69
+ "@relayfile/mount-linux-arm64": "0.10.48",
70
+ "@relayfile/mount-linux-x64": "0.10.48"
71
71
  },
72
72
  "devDependencies": {
73
73
  "typescript": "^5.7.3",