@taskforcehq/taskforce 0.3.328 → 0.3.329

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/dist/Taskforce.module.css +233 -34
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  8. package/dist/components/features/TaskSettings.d.ts +12 -1
  9. package/dist/components/features/TaskSettings.js +147 -40
  10. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  11. package/dist/components/features/planning/PlanningModule.js +2 -5
  12. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  13. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  14. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  15. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  16. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  17. package/dist/components/task/TaskCard.d.ts +5 -0
  18. package/dist/components/task/TaskCard.js +65 -10
  19. package/dist/components/task/TaskImageReviews.js +14 -5
  20. package/dist/components/task/TaskKanban.d.ts +6 -1
  21. package/dist/components/task/TaskKanban.js +152 -6
  22. package/dist/components/task/TaskList.d.ts +5 -0
  23. package/dist/components/task/TaskList.js +3 -3
  24. package/dist/components/ui/PasswordInput.d.ts +6 -0
  25. package/dist/components/ui/PasswordInput.js +11 -0
  26. package/dist/components/ui/TopNoticeLayer.js +1 -1
  27. package/dist/components/views/AppShellHeader.js +32 -11
  28. package/dist/components/views/PlanComparisonPage.js +12 -0
  29. package/dist/components/views/StandaloneLayout.js +7 -31
  30. package/dist/components/views/WidgetView.js +3 -3
  31. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  32. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  33. package/dist/config/envSchema.js +12 -0
  34. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  35. package/dist/config/localServiceRuntimeContract.js +135 -0
  36. package/dist/core/AiProfileService.js +46 -8
  37. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  38. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  39. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  40. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  41. package/dist/core/Taskforce.d.ts +62 -2
  42. package/dist/core/Taskforce.js +635 -32
  43. package/dist/core/types.d.ts +6 -0
  44. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  45. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  46. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  47. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  48. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  49. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  50. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  51. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  52. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  53. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  54. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  55. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  56. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  57. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  58. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  59. package/dist/hooks/ui/useSettingsModel.js +13 -2
  60. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  61. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  62. package/dist/hooks/useFilterSort.d.ts +7 -3
  63. package/dist/hooks/useFilterSort.js +15 -5
  64. package/dist/hooks/useTaskData.d.ts +10 -1
  65. package/dist/hooks/useTaskData.js +216 -7
  66. package/dist/hooks/useTaskMutations.d.ts +10 -2
  67. package/dist/hooks/useTaskMutations.js +223 -43
  68. package/dist/hooks/useTaskforce.d.ts +37 -9
  69. package/dist/hooks/useTaskforce.js +349 -39
  70. package/dist/hooks/useUiNotice.d.ts +4 -0
  71. package/dist/hooks/useUiNotice.js +4 -0
  72. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  73. package/dist/mcp/documentAssetRegistrar.js +44 -35
  74. package/dist/mcp/documentHelpers.d.ts +1 -0
  75. package/dist/mcp/documentHelpers.js +7 -3
  76. package/dist/mcp/runtime.js +172 -42
  77. package/dist/mcp/taskPlanningRegistrar.js +41 -38
  78. package/dist/mcp/toolCallCompatibility.js +38 -1
  79. package/dist/mcp/toolCatalog.d.ts +1 -0
  80. package/dist/migrations/taskSchemaMigrations.js +12 -0
  81. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  82. package/dist/runtime/appGateDefinitions.js +6 -6
  83. package/dist/runtime/appGates.d.ts +12 -13
  84. package/dist/runtime/appGates.js +29 -13
  85. package/dist/runtime/nodeAppGates.d.ts +8 -0
  86. package/dist/runtime/nodeAppGates.js +27 -0
  87. package/dist/server/cloudCollectionRead.d.ts +1 -1
  88. package/dist/server/cloudCollectionRead.js +2 -2
  89. package/dist/server/index.d.ts +3 -0
  90. package/dist/server/index.js +15 -3
  91. package/dist/server/localServiceLease.d.ts +3 -0
  92. package/dist/server/localServiceLease.js +9 -1
  93. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  94. package/dist/server/rateLimit.d.ts +1 -0
  95. package/dist/server/rateLimit.js +4 -0
  96. package/dist/server/realtimeSyncWs.js +36 -12
  97. package/dist/server/routes/admin.js +1 -0
  98. package/dist/server/routes/agentRoles.js +3 -2
  99. package/dist/server/routes/agentSkills.js +3 -2
  100. package/dist/server/routes/agents.js +118 -16
  101. package/dist/server/routes/annotatedAttachments.js +3 -2
  102. package/dist/server/routes/auth.js +3 -1
  103. package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
  104. package/dist/server/routes/localService.js +2 -0
  105. package/dist/server/routes/primitives.d.ts +2 -0
  106. package/dist/server/routes/syncAuxRoutes.js +5 -2
  107. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  108. package/dist/server/routes/syncV3FeedRoutes.js +18 -16
  109. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  110. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  111. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  112. package/dist/server/routes/syncV3OperationRoutes.js +10 -0
  113. package/dist/server/routes/tasks.js +103 -11
  114. package/dist/server/routes/workspaces.js +43 -1
  115. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  116. package/dist/server/workspaceDeletionAdmission.js +50 -0
  117. package/dist/service/localServiceCli.js +16 -0
  118. package/dist/service/localServiceClientLifecycle.js +36 -10
  119. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  120. package/dist/services/codexAppServerModelGateway.js +438 -0
  121. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  122. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  123. package/dist/shared/taskforceAgentDefinition.js +7 -1
  124. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  125. package/dist/shared/taskforceAgentModels.js +30 -1
  126. package/dist/storage/postgresAdapter.d.ts +20 -3
  127. package/dist/storage/postgresAdapter.js +106 -14
  128. package/dist/storage/postgresWorker.js +13 -4
  129. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  130. package/dist/storage/postgresWorkerProtocol.js +30 -0
  131. package/dist/sync/cloudSyncApi.d.ts +4 -1
  132. package/dist/sync/cloudSyncApi.js +7 -2
  133. package/dist/sync/contentSyncState.d.ts +1 -1
  134. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  135. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
  136. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  137. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  138. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  139. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  140. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  141. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  142. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  143. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  144. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
  145. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  146. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  147. package/dist/sync/syncService.d.ts +2 -0
  148. package/dist/sync/syncService.js +4 -1
  149. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  150. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  151. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
  152. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  153. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  154. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  155. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  156. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  157. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  158. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  159. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  160. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  161. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  162. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  163. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  164. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  165. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  166. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  167. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  168. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  169. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  170. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  171. package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
  172. package/dist/sync/v3/syncDurabilityStore.js +31 -11
  173. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  174. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  175. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  176. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  177. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  178. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  179. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  180. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  181. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  182. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  183. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  184. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  185. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  186. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  187. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  188. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  189. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  190. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  191. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  192. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  193. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
  194. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  195. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  196. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
  197. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
  198. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
  199. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
  200. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  201. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
  202. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  203. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  204. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  205. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  206. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  207. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  208. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  209. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  210. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  211. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  212. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  213. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  214. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  215. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  216. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
  217. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  218. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  219. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  220. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  221. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  222. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  223. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  224. package/dist/sync/workspacePullFeed.js +2 -1
  225. package/dist/sync/workspaceRepair.d.ts +3 -0
  226. package/dist/sync/workspaceRepair.js +67 -6
  227. package/dist/types.d.ts +47 -1
  228. package/dist/types.js +6 -0
  229. package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  230. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  231. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  232. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  233. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
  234. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
  235. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
  236. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
  237. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  238. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  239. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  240. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  241. package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  242. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
  243. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  244. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  245. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
  246. package/dist/ui/index.html +3 -3
  247. package/dist/utils/taskActivity.d.ts +3 -2
  248. package/dist/utils/taskActivity.js +19 -2
  249. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  250. package/dist/utils/taskEditorReconciliation.js +217 -0
  251. package/dist/utils/taskNormalization.d.ts +1 -0
  252. package/dist/utils/taskNormalization.js +39 -0
  253. package/dist/utils/taskSearch.js +2 -2
  254. package/dist/utils/uiNotice.d.ts +4 -0
  255. package/package.json +16 -4
  256. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  257. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  258. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  259. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  260. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  261. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  262. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  263. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  264. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -3,13 +3,30 @@ import { Worker } from 'worker_threads';
3
3
  import { performance } from 'node:perf_hooks';
4
4
  import { assertSynchronousTransactionResult, } from './databaseAdapter.js';
5
5
  import { recordPostgresWorkerWait } from './postgresRequestDiagnostics.js';
6
+ import { resolvePostgresWorkerDeadlines, } from './postgresWorkerProtocol.js';
6
7
  const RESPONSE_STATUS_INDEX = 0;
7
8
  const RESPONSE_LENGTH_INDEX = 1;
8
9
  const RESPONSE_READY = 1;
9
10
  const RESPONSE_INITIAL = 0;
10
- const RESPONSE_TIMEOUT_MS = 20_000;
11
11
  const RESPONSE_BUFFER_BYTES = 2 * 1024 * 1024;
12
12
  const MAX_RESPONSE_BUFFER_BYTES = 64 * 1024 * 1024;
13
+ export class PostgresWorkerOutcomeUnknownError extends Error {
14
+ code = 'POSTGRES_WORKER_OUTCOME_UNKNOWN';
15
+ operation;
16
+ constructor(operation, detail = 'response deadline exceeded') {
17
+ super(`[Taskforce] Postgres worker ${operation} outcome is unknown (${detail}). `
18
+ + 'Do not retry a write without the same idempotency identity.');
19
+ this.name = 'PostgresWorkerOutcomeUnknownError';
20
+ this.operation = operation;
21
+ }
22
+ }
23
+ export class PostgresWorkerUnavailableError extends Error {
24
+ code = 'POSTGRES_WORKER_UNAVAILABLE';
25
+ constructor() {
26
+ super('[Taskforce] Postgres worker is recovering after a failed operation.');
27
+ this.name = 'PostgresWorkerUnavailableError';
28
+ }
29
+ }
13
30
  function isPlainObject(value) {
14
31
  return !!value && typeof value === 'object' && !Array.isArray(value);
15
32
  }
@@ -62,39 +79,102 @@ function toRowCount(result) {
62
79
  }
63
80
  export class PostgresAdapter {
64
81
  provider = 'postgres';
65
- open = true;
66
- worker;
82
+ worker = null;
83
+ connectionString;
84
+ deadlines;
85
+ retiringWorkers = new WeakSet();
86
+ consecutiveUnexpectedExits = 0;
87
+ closed = false;
67
88
  encoder = new TextEncoder();
68
89
  decoder = new TextDecoder();
69
90
  txId = null;
70
91
  savepointCounter = 0;
71
- constructor(connectionString) {
92
+ constructor(connectionString, deadlineOptions = {}) {
72
93
  if (!connectionString || !connectionString.trim()) {
73
94
  throw new Error('[Taskforce] Postgres adapter requires a non-empty connection string.');
74
95
  }
75
- this.worker = new Worker(new URL('./postgresWorker.js', import.meta.url), {
96
+ this.connectionString = connectionString;
97
+ this.deadlines = resolvePostgresWorkerDeadlines(deadlineOptions);
98
+ this.worker = this.createWorker();
99
+ }
100
+ get open() {
101
+ return !this.closed;
102
+ }
103
+ createWorker() {
104
+ const worker = new Worker(new URL('./postgresWorker.js', import.meta.url), {
76
105
  workerData: {
77
- connectionString
106
+ connectionString: this.connectionString,
107
+ deadlines: this.deadlines,
108
+ }
109
+ });
110
+ worker.on('error', () => {
111
+ // The exit handler replaces an unexpectedly failed worker. Keeping an error
112
+ // listener here also prevents Worker from escalating it as an uncaught error.
113
+ });
114
+ worker.on('exit', () => {
115
+ if (this.closed || this.retiringWorkers.has(worker) || this.worker !== worker)
116
+ return;
117
+ this.worker = null;
118
+ if (this.consecutiveUnexpectedExits >= 1)
119
+ return;
120
+ this.consecutiveUnexpectedExits += 1;
121
+ try {
122
+ this.worker = this.createWorker();
123
+ }
124
+ catch {
125
+ // Leave the adapter unavailable instead of creating an unbounded restart loop.
126
+ }
127
+ });
128
+ return worker;
129
+ }
130
+ quarantineWorker(worker) {
131
+ if (this.worker === worker)
132
+ this.worker = null;
133
+ this.txId = null;
134
+ this.retiringWorkers.add(worker);
135
+ void worker.terminate()
136
+ .catch(() => undefined)
137
+ .finally(() => {
138
+ if (this.closed || this.worker)
139
+ return;
140
+ try {
141
+ this.worker = this.createWorker();
142
+ }
143
+ catch {
144
+ // A later process-level health check can restart a persistently broken runtime.
78
145
  }
79
146
  });
80
147
  }
81
148
  callWorker(payload, responseBufferBytes = RESPONSE_BUFFER_BYTES) {
149
+ if (this.closed)
150
+ throw new Error('[Taskforce] Postgres adapter is closed.');
151
+ const worker = this.worker;
152
+ if (!worker)
153
+ throw new PostgresWorkerUnavailableError();
82
154
  const statusBuffer = new SharedArrayBuffer(8);
83
155
  const status = new Int32Array(statusBuffer);
84
156
  status[RESPONSE_STATUS_INDEX] = RESPONSE_INITIAL;
85
157
  status[RESPONSE_LENGTH_INDEX] = 0;
86
158
  const responseBuffer = new SharedArrayBuffer(responseBufferBytes);
87
159
  const responseBytes = new Uint8Array(responseBuffer);
88
- this.worker.postMessage({
89
- payload,
90
- statusBuffer,
91
- responseBuffer
92
- });
160
+ try {
161
+ worker.postMessage({
162
+ payload,
163
+ statusBuffer,
164
+ responseBuffer
165
+ });
166
+ }
167
+ catch (error) {
168
+ this.quarantineWorker(worker);
169
+ const detail = error instanceof Error ? error.message : 'worker transport failed';
170
+ throw new PostgresWorkerOutcomeUnknownError(payload.op, detail);
171
+ }
93
172
  const waitStartedAt = performance.now();
94
- const waitResult = Atomics.wait(status, RESPONSE_STATUS_INDEX, RESPONSE_INITIAL, RESPONSE_TIMEOUT_MS);
173
+ const waitResult = Atomics.wait(status, RESPONSE_STATUS_INDEX, RESPONSE_INITIAL, this.deadlines.responseTimeoutMs);
95
174
  recordPostgresWorkerWait(payload.op, performance.now() - waitStartedAt);
96
175
  if (waitResult === 'timed-out') {
97
- throw new Error('[Taskforce] Postgres worker timed out.');
176
+ this.quarantineWorker(worker);
177
+ throw new PostgresWorkerOutcomeUnknownError(payload.op);
98
178
  }
99
179
  const responseLength = status[RESPONSE_LENGTH_INDEX];
100
180
  if (!Number.isFinite(responseLength) || responseLength <= 0 || responseLength > responseBufferBytes) {
@@ -102,6 +182,7 @@ export class PostgresAdapter {
102
182
  }
103
183
  const body = this.decoder.decode(responseBytes.subarray(0, responseLength));
104
184
  const parsed = JSON.parse(body);
185
+ this.consecutiveUnexpectedExits = 0;
105
186
  if (!parsed.ok && parsed.errorCode === 'RESPONSE_BUFFER_TOO_SMALL') {
106
187
  const requiredBytes = Number(parsed.requiredBytes);
107
188
  const responseToken = String(parsed.responseToken || '').trim();
@@ -230,11 +311,22 @@ export class PostgresAdapter {
230
311
  return options?.simple ? null : [];
231
312
  }
232
313
  close() {
314
+ if (this.closed)
315
+ return;
316
+ const worker = this.worker;
317
+ if (!worker) {
318
+ this.closed = true;
319
+ return;
320
+ }
233
321
  try {
234
322
  this.callWorker({ op: 'close' });
235
323
  }
236
324
  finally {
237
- this.worker.terminate();
325
+ this.closed = true;
326
+ if (this.worker === worker)
327
+ this.worker = null;
328
+ this.retiringWorkers.add(worker);
329
+ void worker.terminate();
238
330
  }
239
331
  }
240
332
  }
@@ -14,11 +14,16 @@ function pruneRetainedResponses(now = Date.now()) {
14
14
  retainedResponses.delete(token);
15
15
  }
16
16
  }
17
- function createPool(connectionString) {
17
+ function createPool(connectionString, deadlines) {
18
18
  const require = createRequire(import.meta.url);
19
19
  try {
20
20
  const pg = require('pg');
21
- return new pg.Pool({ connectionString });
21
+ return new pg.Pool({
22
+ connectionString,
23
+ connectionTimeoutMillis: deadlines.connectionTimeoutMs,
24
+ statement_timeout: deadlines.statementTimeoutMs,
25
+ idle_in_transaction_session_timeout: deadlines.idleTransactionTimeoutMs,
26
+ });
22
27
  }
23
28
  catch (error) {
24
29
  const detail = error instanceof Error ? ` (${error.message})` : '';
@@ -96,11 +101,15 @@ function writeRetainedResponse(statusBuffer, responseBuffer, responseToken) {
96
101
  Atomics.store(status, RESPONSE_STATUS_INDEX, RESPONSE_READY);
97
102
  Atomics.notify(status, RESPONSE_STATUS_INDEX, 1);
98
103
  }
99
- const connectionString = String(workerData?.connectionString || '').trim();
104
+ const runtimeOptions = workerData;
105
+ const connectionString = String(runtimeOptions?.connectionString || '').trim();
100
106
  if (!connectionString) {
101
107
  throw new Error('[Taskforce] Postgres worker requires connection string.');
102
108
  }
103
- const pool = createPool(connectionString);
109
+ if (!runtimeOptions.deadlines) {
110
+ throw new Error('[Taskforce] Postgres worker requires deadline configuration.');
111
+ }
112
+ const pool = createPool(connectionString, runtimeOptions.deadlines);
104
113
  const txClients = new Map();
105
114
  async function handleMessage(message) {
106
115
  const { payload } = message;
@@ -0,0 +1,29 @@
1
+ export type PostgresWorkerOperation = 'query' | 'begin' | 'commit' | 'rollback' | 'close' | 'read-response';
2
+ export type PostgresWorkerRequestPayload = {
3
+ op: PostgresWorkerOperation;
4
+ sql?: string;
5
+ values?: unknown[];
6
+ txId?: string;
7
+ responseToken?: string;
8
+ };
9
+ export type PostgresWorkerQueryResult = {
10
+ rows: Record<string, unknown>[];
11
+ rowCount?: number;
12
+ };
13
+ export type PostgresWorkerResponsePayload = {
14
+ ok: boolean;
15
+ result?: PostgresWorkerQueryResult;
16
+ error?: string;
17
+ errorCode?: 'RESPONSE_BUFFER_TOO_SMALL';
18
+ requiredBytes?: number;
19
+ responseToken?: string;
20
+ };
21
+ export type PostgresWorkerDeadlines = {
22
+ responseTimeoutMs: number;
23
+ statementTimeoutMs: number;
24
+ connectionTimeoutMs: number;
25
+ idleTransactionTimeoutMs: number;
26
+ };
27
+ export type PostgresAdapterDeadlineOptions = Partial<PostgresWorkerDeadlines>;
28
+ export declare const DEFAULT_POSTGRES_WORKER_DEADLINES: PostgresWorkerDeadlines;
29
+ export declare function resolvePostgresWorkerDeadlines(options?: PostgresAdapterDeadlineOptions): PostgresWorkerDeadlines;
@@ -0,0 +1,30 @@
1
+ export const DEFAULT_POSTGRES_WORKER_DEADLINES = {
2
+ responseTimeoutMs: 20_000,
3
+ statementTimeoutMs: 15_000,
4
+ connectionTimeoutMs: 3_000,
5
+ idleTransactionTimeoutMs: 60_000,
6
+ };
7
+ const MIN_RESPONSE_MARGIN_MS = 100;
8
+ function positiveInteger(value, name) {
9
+ const parsed = Number(value);
10
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
11
+ throw new Error(`[Taskforce] ${name} must be a positive integer.`);
12
+ }
13
+ return parsed;
14
+ }
15
+ export function resolvePostgresWorkerDeadlines(options = {}) {
16
+ const deadlines = {
17
+ responseTimeoutMs: positiveInteger(options.responseTimeoutMs ?? DEFAULT_POSTGRES_WORKER_DEADLINES.responseTimeoutMs, 'Postgres worker response timeout'),
18
+ statementTimeoutMs: positiveInteger(options.statementTimeoutMs ?? DEFAULT_POSTGRES_WORKER_DEADLINES.statementTimeoutMs, 'Postgres statement timeout'),
19
+ connectionTimeoutMs: positiveInteger(options.connectionTimeoutMs ?? DEFAULT_POSTGRES_WORKER_DEADLINES.connectionTimeoutMs, 'Postgres connection timeout'),
20
+ idleTransactionTimeoutMs: positiveInteger(options.idleTransactionTimeoutMs ?? DEFAULT_POSTGRES_WORKER_DEADLINES.idleTransactionTimeoutMs, 'Postgres idle transaction timeout'),
21
+ };
22
+ if (deadlines.connectionTimeoutMs
23
+ + deadlines.statementTimeoutMs
24
+ + MIN_RESPONSE_MARGIN_MS
25
+ > deadlines.responseTimeoutMs) {
26
+ throw new Error('[Taskforce] Postgres connection and statement timeouts must together leave at least '
27
+ + `${MIN_RESPONSE_MARGIN_MS}ms before the worker response timeout.`);
28
+ }
29
+ return deadlines;
30
+ }
@@ -23,10 +23,13 @@ export type WorkspaceContentRequest = Pick<WorkspaceContentManifestEntry, 'kind'
23
23
  kind: 'asset';
24
24
  assetId: string;
25
25
  };
26
+ export type WorkspaceContentMissingRequest = WorkspaceContentRequest & {
27
+ disposition?: 'retryable' | 'terminal-unavailable';
28
+ };
26
29
  export interface WorkspaceContentPullResponse {
27
30
  success?: boolean;
28
31
  changes?: WorkspaceSyncChange[];
29
- missing?: WorkspaceContentRequest[];
32
+ missing?: WorkspaceContentMissingRequest[];
30
33
  remaining?: WorkspaceContentRequest[];
31
34
  contentMetadata?: Array<{
32
35
  kind: 'document' | 'asset';
@@ -1,4 +1,4 @@
1
- import { partitionWorkspaceSyncV2LegacyChanges } from './v3/workspaceSyncV2ProjectionExclusions.js';
1
+ import { normalizeWorkspaceSyncV2ProjectionExclusions, partitionWorkspaceSyncV2LegacyChanges, } from './v3/workspaceSyncV2ProjectionExclusions.js';
2
2
  import { validateWorkspaceSyncEngineTransferCompletionGroup, } from './engine/workspaceSyncEngineTransferCompletion.js';
3
3
  import { WORKSPACE_SYNC_V3_COMBINED_COVERAGE_SELECTOR, resolveWorkspaceSyncV3CombinedCoverageForExclusions, resolveWorkspaceSyncV3CombinedWireContract } from './v3/workspaceSyncV3CombinedWireContract.js';
4
4
  const WORKSPACE_PULL_TIMEOUT_MS = 45_000;
@@ -696,12 +696,17 @@ export async function bootstrapPushWorkspaceSnapshot(resolveCloudAuthUrl, input)
696
696
  }));
697
697
  }
698
698
  export async function applyLocalWorkspaceChanges(workspaceId, changes, options) {
699
+ const excludedProjections = normalizeWorkspaceSyncV2ProjectionExclusions(options?.excludedProjections || []);
700
+ const combinedCoverage = excludedProjections.length
701
+ ? resolveWorkspaceSyncV3CombinedCoverageForExclusions(excludedProjections)
702
+ : null;
699
703
  const applyPayload = JSON.stringify({
700
704
  workspaceId,
701
705
  changes,
702
706
  workspaceMembers: Array.isArray(options?.workspaceMembers) ? options.workspaceMembers : undefined,
703
707
  ...(options?.repairMode === true ? { repair: true } : {}),
704
- excludedProjections: options?.excludedProjections,
708
+ excludedProjections,
709
+ ...(combinedCoverage ? { combinedCoverage } : {}),
705
710
  contentPullObligations: options?.contentPullObligations,
706
711
  contentPullResolutionClaims: options?.contentPullResolutionClaims,
707
712
  ...localBrowserApplyPermitContext(workspaceId, options),
@@ -130,7 +130,7 @@ export declare function createContentSyncState(deps: ContentSyncStateDeps): {
130
130
  activeLinks: WorkspaceAssetLinkEntry[];
131
131
  effectiveUpdatedAt: string;
132
132
  };
133
- getCanonicalSyncDocumentMetadata: (relativePath: string, workspaceId: string) => Omit<DocumentSyncPayload, "path" | "updatedAt" | "content">;
133
+ getCanonicalSyncDocumentMetadata: (relativePath: string, workspaceId: string) => Omit<DocumentSyncPayload, "updatedAt" | "path" | "content">;
134
134
  hasDocumentSyncMetadataDrift: (workspaceId: string, doc: DocumentSyncPayload, _options?: {
135
135
  allowReferenceNumberReassignment?: boolean;
136
136
  }) => boolean;
@@ -152,6 +152,7 @@ export declare class LocalSyncExecutionPermitStore {
152
152
  coordinatorGeneration: number;
153
153
  serverInstanceId: string;
154
154
  errorCode?: string;
155
+ readResponseUnavailableBeforeApply?: boolean;
155
156
  }): LocalSyncExecutionPermit;
156
157
  recoverExpiredPermits(input: {
157
158
  currentServerInstanceId: string;
@@ -619,9 +619,15 @@ export class LocalSyncExecutionPermitStore {
619
619
  if (current.state === 'local-commit-entered') {
620
620
  throw new LocalSyncExecutionPermitError(`Execution permit ${input.permitId} is owned by the server local commit.`, 'SYNC_COORDINATOR_PERMIT_STATE_CONFLICT');
621
621
  }
622
+ const cleanReadFailure = input.readResponseUnavailableBeforeApply === true
623
+ && input.errorCode === 'SYNC_COORDINATOR_GATEWAY_REQUEST_FAILED'
624
+ && current.state === 'gateway-complete-awaiting-apply'
625
+ && current.upstreamDisposition === 'unknown'
626
+ && (current.operationClass === 'pull' || current.operationClass === 'repair');
622
627
  const clean = current.state === 'issued-idle'
623
628
  || (current.state === 'gateway-complete-awaiting-apply'
624
- && current.upstreamDisposition === 'rejected');
629
+ && current.upstreamDisposition === 'rejected')
630
+ || cleanReadFailure;
625
631
  const disposition = current.upstreamDisposition === 'accepted'
626
632
  ? 'accepted'
627
633
  : current.upstreamDisposition === 'rejected'
@@ -17,6 +17,10 @@ const RUNNER_COMMANDS = new Set([
17
17
  ]);
18
18
  const SCHEDULED_RETRY_BASE_DELAY_MS = 1_000;
19
19
  const SCHEDULED_RETRY_MAX_DELAY_MS = 5 * 60_000;
20
+ const CONTENT_PREREQUISITE_BYPASSED_RETRY_KINDS = new Set([
21
+ 'pull',
22
+ 'repair',
23
+ ]);
20
24
  function cloneCheckpoint(checkpoint) {
21
25
  return JSON.parse(canonicalWorkspaceSyncJson(checkpoint));
22
26
  }
@@ -159,7 +163,7 @@ export class LocalWorkspaceSyncRunner {
159
163
  if (!this.context || this.terminated || !this.acceptingWork) {
160
164
  return Promise.resolve();
161
165
  }
162
- return this.enqueue(async () => this.runInspection(this.options.operations.pushContentPrerequisite, { bypassRetryKind: 'repair' }));
166
+ return this.enqueue(async () => this.runInspection(this.options.operations.pushContentPrerequisite, { bypassRetryKinds: CONTENT_PREREQUISITE_BYPASSED_RETRY_KINDS }));
163
167
  }
164
168
  observeRemoteHint(observedAt = this.now()) {
165
169
  if (!this.context || this.terminated || !this.acceptingWork) {
@@ -476,7 +480,7 @@ export class LocalWorkspaceSyncRunner {
476
480
  if (!state.checkpoint.syncEnabled || !this.hasExactCapability(context))
477
481
  return;
478
482
  if (state.checkpoint.retry
479
- && state.checkpoint.retry.kind !== options.bypassRetryKind
483
+ && !options.bypassRetryKinds?.has(state.checkpoint.retry.kind)
480
484
  && Date.parse(state.checkpoint.retry.retryAt) > Date.parse(this.now())) {
481
485
  this.schedulePersistedRetry(state, this.lifecycleToken);
482
486
  return;
@@ -40,6 +40,18 @@ function v3Boundary(input) {
40
40
  ...(input.coverage ? { coverage: { ...input.coverage } } : {}),
41
41
  };
42
42
  }
43
+ function independentContentBoundary(identity) {
44
+ const normalizedIdentity = String(identity || '').trim();
45
+ if (!normalizedIdentity) {
46
+ throw new TypeError('Independent content transfer identity is required.');
47
+ }
48
+ return {
49
+ version: 1,
50
+ projection: 'independent-content',
51
+ cursor: null,
52
+ identity: normalizedIdentity,
53
+ };
54
+ }
43
55
  async function decodeGatewayResponse(response, nowMs) {
44
56
  const rawText = await response.text();
45
57
  let data = {};
@@ -405,10 +417,12 @@ export function createBrowserGatewayWorkspaceSyncEngineRemoteServices(options) {
405
417
  });
406
418
  },
407
419
  pullWorkspaceContent: (input) => {
408
- const { previousCursor, ...request } = input;
420
+ const { previousCursor, independentCompletionIdentity, ...request } = input;
409
421
  return execute({
410
422
  operation: { type: 'content-pull', ...request },
411
- previousBoundary: v2Boundary(previousCursor),
423
+ previousBoundary: independentCompletionIdentity
424
+ ? independentContentBoundary(independentCompletionIdentity)
425
+ : v2Boundary(previousCursor),
412
426
  applying: true,
413
427
  });
414
428
  },
@@ -510,6 +524,10 @@ export function createBrowserGatewayWorkspaceSyncEngineRemoteServices(options) {
510
524
  }));
511
525
  },
512
526
  buildV2ApplyCompletionGroup: ({ workspaceId, previousCursor, nextCursor, receipts, }) => completionGroup(workspaceId, receipts, v2Boundary(previousCursor), v2Boundary(nextCursor)),
527
+ buildIndependentContentApplyCompletionGroup: ({ workspaceId, identity, receipts, }) => {
528
+ const boundary = independentContentBoundary(identity);
529
+ return completionGroup(workspaceId, receipts, boundary, boundary);
530
+ },
513
531
  buildV2PushApplyCompletionGroup: ({ workspaceId, pushBoundary, receipts, }) => completionGroup(workspaceId, receipts, parseWorkspaceSyncEngineV2PushBoundary(pushBoundary), parseWorkspaceSyncEngineV2PushBoundary(pushBoundary)),
514
532
  buildV3ApplyCompletionGroup: ({ workspaceId, apply, receipts }) => {
515
533
  const metadata = readReceipts(workspaceId, receipts);
@@ -24,6 +24,10 @@ export interface WorkspaceSyncEngineV2ApplyOptions {
24
24
  export interface WorkspaceSyncEngineV3ApplyOptions {
25
25
  completionGroup?: WorkspaceSyncEngineTransferCompletionGroup;
26
26
  }
27
+ export declare function authoritativeV2ApplyFailureMessage(input: {
28
+ status?: number;
29
+ data?: unknown;
30
+ }): string;
27
31
  /**
28
32
  * Local-runtime operations used by the workspace sync engine.
29
33
  *
@@ -1,5 +1,16 @@
1
1
  import { applyLocalWorkspaceChanges, applyLocalWorkspaceSyncV3CombinedProjection, applyLocalWorkspaceSyncV3Projection, applyLocalWorkspaceSyncV3TaskCommentAudit, completeLocalSyncAuthoritativeRepair, getLocalWorkspaceContentPullObligations, recordLocalWorkspaceContentPullObligationFailures, getLocalTaskMetadataOwnership, getLocalWorkspaceContentManifest, getLocalWorkspaceSyncV3CombinedProjection, getLocalWorkspaceSyncV3Projection, getLocalWorkspaceSyncV3TaskCommentAudit, } from '../cloudSyncApi.js';
2
2
  import { prepareLocalTaskRootOutboxRecovery, } from '../v3/localOutboxRecoveryClient.js';
3
+ export function authoritativeV2ApplyFailureMessage(input) {
4
+ const data = input.data && typeof input.data === 'object' && !Array.isArray(input.data)
5
+ ? input.data
6
+ : {};
7
+ const detail = String(data.error || '').trim();
8
+ const code = String(data.code || '').trim();
9
+ const status = Number(input.status) || 0;
10
+ return detail
11
+ ? `Authoritative V2 push echo apply failed (${status}): ${detail}${code ? ` [${code}]` : ''}`
12
+ : `Authoritative V2 push echo apply failed (${status}).`;
13
+ }
3
14
  /**
4
15
  * Same-origin HTTP implementation used while the browser owns orchestration.
5
16
  */
@@ -37,6 +37,12 @@ export type WorkspaceSyncEngineContentPullInput = Parameters<typeof pullWorkspac
37
37
  * commit against one exact prior boundary.
38
38
  */
39
39
  previousCursor: string | null;
40
+ /**
41
+ * Separates an independent content lookup from a V2 feed page. The
42
+ * identity becomes an apply boundary that settles the transfer
43
+ * without advancing or validating the runner's V2 cursor.
44
+ */
45
+ independentCompletionIdentity?: string;
40
46
  };
41
47
  type RemoteResult<Result> = Awaited<Result> & {
42
48
  transferReceipt?: WorkspaceSyncEngineRemoteTransferReceipt;
@@ -94,6 +100,11 @@ export interface WorkspaceSyncEngineRemoteServices {
94
100
  nextCursor: string | null;
95
101
  receipts: readonly WorkspaceSyncEngineRemoteTransferReceipt[];
96
102
  }): WorkspaceSyncEngineTransferCompletionGroup | undefined;
103
+ buildIndependentContentApplyCompletionGroup(input: {
104
+ workspaceId: string;
105
+ identity: string;
106
+ receipts: readonly WorkspaceSyncEngineRemoteTransferReceipt[];
107
+ }): WorkspaceSyncEngineTransferCompletionGroup | undefined;
97
108
  buildV2PushApplyCompletionGroup(input: {
98
109
  workspaceId: string;
99
110
  pushBoundary: WorkspaceSyncEngineV2PushBoundary;
@@ -6,7 +6,7 @@ export function createBrowserWorkspaceSyncEngineRemoteServices(resolveCloudAuthU
6
6
  provisionWorkspace: (input) => provisionWorkspace(resolveCloudAuthUrl, input),
7
7
  pushWorkspaceChanges: ({ ownership: _ownership, pushBoundary: _pushBoundary, ...input }) => (pushWorkspaceChanges(resolveCloudAuthUrl, input)),
8
8
  pullWorkspaceChanges: (input) => pullWorkspaceChanges(resolveCloudAuthUrl, input),
9
- pullWorkspaceContent: ({ previousCursor: _previousCursor, ...input }) => (pullWorkspaceContent(resolveCloudAuthUrl, input)),
9
+ pullWorkspaceContent: ({ previousCursor: _previousCursor, independentCompletionIdentity: _independentCompletionIdentity, ...input }) => (pullWorkspaceContent(resolveCloudAuthUrl, input)),
10
10
  pullInitiativeFeed: (input) => pullWorkspaceSyncV3Feed(resolveCloudAuthUrl, input),
11
11
  pullInitiativeRepair: (input) => pullWorkspaceInitiativeRepair(resolveCloudAuthUrl, input),
12
12
  pullCombinedFeed: (input) => pullWorkspaceSyncV3CombinedFeed(resolveCloudAuthUrl, input),
@@ -15,6 +15,7 @@ export function createBrowserWorkspaceSyncEngineRemoteServices(resolveCloudAuthU
15
15
  abandonTransferReceipts: () => undefined,
16
16
  acknowledgeNonApplyingTransferReceipts: () => undefined,
17
17
  buildV2ApplyCompletionGroup: () => undefined,
18
+ buildIndependentContentApplyCompletionGroup: () => undefined,
18
19
  buildV2PushApplyCompletionGroup: () => undefined,
19
20
  buildV3ApplyCompletionGroup: () => undefined,
20
21
  };
@@ -40,6 +40,18 @@ function v3Boundary(input) {
40
40
  ...(input.coverage ? { coverage: { ...input.coverage } } : {}),
41
41
  };
42
42
  }
43
+ function independentContentBoundary(identity) {
44
+ const normalizedIdentity = String(identity || '').trim();
45
+ if (!normalizedIdentity) {
46
+ throw new TypeError('Independent content transfer identity is required.');
47
+ }
48
+ return {
49
+ version: 1,
50
+ projection: 'independent-content',
51
+ cursor: null,
52
+ identity: normalizedIdentity,
53
+ };
54
+ }
43
55
  function decodeGatewayResult(result, nowMs) {
44
56
  const rawText = new TextDecoder().decode(result.body);
45
57
  let data = {};
@@ -96,12 +108,15 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
96
108
  throw new TypeError('The server sync operation workspace does not match its runner.');
97
109
  }
98
110
  };
99
- const abandon = (permitId, errorCode) => (options.permitStore.abandonPermit({
111
+ const abandon = (permitId, errorCode, readResponseUnavailableBeforeApply = false) => (options.permitStore.abandonPermit({
100
112
  permitId,
101
113
  workspaceId: options.workspaceId,
102
114
  coordinatorGeneration: options.coordinatorGeneration,
103
115
  serverInstanceId: options.serverInstanceId,
104
116
  errorCode,
117
+ ...(readResponseUnavailableBeforeApply
118
+ ? { readResponseUnavailableBeforeApply: true }
119
+ : {}),
105
120
  }));
106
121
  const execute = async (input) => {
107
122
  assertWorkspace(input.operation.workspaceId);
@@ -131,7 +146,7 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
131
146
  });
132
147
  }
133
148
  catch (error) {
134
- abandon(permit.permitId, 'SYNC_COORDINATOR_GATEWAY_REQUEST_FAILED');
149
+ abandon(permit.permitId, 'SYNC_COORDINATOR_GATEWAY_REQUEST_FAILED', operationClass === 'pull' || operationClass === 'repair');
135
150
  throw error;
136
151
  }
137
152
  const decoded = decodeGatewayResult(gatewayResult, issuedAt.getTime());
@@ -171,6 +186,12 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
171
186
  abandon(permit.permitId, 'SYNC_COORDINATOR_APPLYING_REJECTED');
172
187
  return result;
173
188
  }
189
+ if (!result.ok
190
+ && completedPermit.upstreamDisposition === 'unknown'
191
+ && (operationClass === 'pull' || operationClass === 'repair')) {
192
+ abandon(permit.permitId, 'SYNC_COORDINATOR_GATEWAY_REQUEST_FAILED', true);
193
+ return result;
194
+ }
174
195
  if (!input.previousBoundary) {
175
196
  abandon(permit.permitId, 'SYNC_COORDINATOR_APPLY_BOUNDARY_MISSING');
176
197
  throw new Error('A server applying request did not declare its prior boundary.');
@@ -319,10 +340,12 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
319
340
  });
320
341
  },
321
342
  pullWorkspaceContent: (input) => {
322
- const { previousCursor, ...request } = input;
343
+ const { previousCursor, independentCompletionIdentity, ...request } = input;
323
344
  return execute({
324
345
  operation: { type: 'content-pull', ...request },
325
- previousBoundary: v2Boundary(previousCursor),
346
+ previousBoundary: independentCompletionIdentity
347
+ ? independentContentBoundary(independentCompletionIdentity)
348
+ : v2Boundary(previousCursor),
326
349
  applying: true,
327
350
  });
328
351
  },
@@ -433,6 +456,10 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
433
456
  }
434
457
  },
435
458
  buildV2ApplyCompletionGroup: ({ workspaceId, previousCursor, nextCursor, receipts, }) => completionGroup(workspaceId, receipts, v2Boundary(previousCursor), v2Boundary(nextCursor)),
459
+ buildIndependentContentApplyCompletionGroup: ({ workspaceId, identity, receipts, }) => {
460
+ const boundary = independentContentBoundary(identity);
461
+ return completionGroup(workspaceId, receipts, boundary, boundary);
462
+ },
436
463
  buildV2PushApplyCompletionGroup: ({ workspaceId, pushBoundary, receipts, }) => completionGroup(workspaceId, receipts, parseWorkspaceSyncEngineV2PushBoundary(pushBoundary), parseWorkspaceSyncEngineV2PushBoundary(pushBoundary)),
437
464
  buildV3ApplyCompletionGroup: ({ workspaceId, apply, receipts }) => {
438
465
  const metadata = readReceipts(workspaceId, receipts);
@@ -2,7 +2,7 @@ import type { LocalWorkspaceSyncRunnerOperations } from '../coordinator/localWor
2
2
  import { type EnsureWorkspaceReadyResult } from '../syncService.js';
3
3
  import type { WorkspaceSyncV2ProjectionExclusion } from '../v3/workspaceSyncV2ProjectionExclusions.js';
4
4
  import type { WorkspaceSyncV3CombinedCoverageSelector } from '../v3/workspaceSyncV3CombinedWireContract.js';
5
- import type { WorkspaceSyncEngineLocalServices } from './workspaceSyncEngineLocalServices.js';
5
+ import { type WorkspaceSyncEngineLocalServices } from './workspaceSyncEngineLocalServices.js';
6
6
  import type { WorkspaceSyncEngineRemoteServices } from './workspaceSyncEngineRemoteServices.js';
7
7
  import type { WorkspaceSyncEngineServerPushCandidateProvider } from './workspaceSyncEngineServerSnapshotReader.js';
8
8
  import type { WorkspaceSyncEngineServerCheckpointPort } from './workspaceSyncEngineServerApplyBoundary.js';
@@ -46,11 +46,13 @@ export interface WorkspaceSyncEngineServerAttachmentApplyPort {
46
46
  } | {
47
47
  success: false;
48
48
  error: string;
49
+ disposition?: 'retryable' | 'terminal-unavailable';
49
50
  }>;
50
51
  }): Promise<{
51
52
  attempted: number;
52
53
  resolved: number;
53
54
  remaining: number;
55
+ terminal: number;
54
56
  nextAttemptAt: string | null;
55
57
  lastError: string | null;
56
58
  }>;