@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 +9 -1
- package/dist/client.js +127 -0
- package/dist/index.d.ts +2 -2
- package/dist/mount-launcher.js +230 -0
- package/dist/setup-types.d.ts +25 -1
- package/dist/setup.js +121 -12
- package/dist/types.d.ts +128 -0
- package/package.json +6 -6
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";
|
package/dist/mount-launcher.js
CHANGED
|
@@ -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");
|
package/dist/setup-types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
955
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1113
|
+
if (!this.isActiveEpoch(epoch))
|
|
1008
1114
|
return;
|
|
1009
1115
|
await previous.stop().catch(() => { });
|
|
1010
|
-
if (this.
|
|
1116
|
+
if (!this.isActiveEpoch(epoch))
|
|
1011
1117
|
return;
|
|
1012
1118
|
const replacement = await this.launch();
|
|
1013
|
-
if (this.
|
|
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.
|
|
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.
|
|
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.
|
|
68
|
-
"@relayfile/mount-darwin-x64": "0.10.
|
|
69
|
-
"@relayfile/mount-linux-arm64": "0.10.
|
|
70
|
-
"@relayfile/mount-linux-x64": "0.10.
|
|
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",
|