@taskforcehq/taskforce 0.3.327 → 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 (360) hide show
  1. package/dist/Taskforce.module.css +257 -87
  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/activity/EntityActivityTimeline.js +15 -5
  8. package/dist/components/context/AttachmentPreviewHost.js +1 -1
  9. package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
  10. package/dist/components/context/ContextAttachmentManager.js +116 -20
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  12. package/dist/components/features/TaskSettings.d.ts +12 -1
  13. package/dist/components/features/TaskSettings.js +147 -40
  14. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  15. package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
  16. package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
  17. package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
  18. package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
  19. package/dist/components/features/planning/PlanningModule.js +28 -14
  20. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  21. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  22. package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
  23. package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
  24. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  25. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  26. package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
  27. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  28. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  29. package/dist/components/task/TaskActionHeader.js +2 -2
  30. package/dist/components/task/TaskCard.d.ts +5 -0
  31. package/dist/components/task/TaskCard.js +101 -13
  32. package/dist/components/task/TaskContextUpload.d.ts +3 -0
  33. package/dist/components/task/TaskContextUpload.js +2 -2
  34. package/dist/components/task/TaskForm.d.ts +4 -0
  35. package/dist/components/task/TaskForm.js +32 -7
  36. package/dist/components/task/TaskImageReviews.js +28 -19
  37. package/dist/components/task/TaskKanban.d.ts +6 -1
  38. package/dist/components/task/TaskKanban.js +152 -6
  39. package/dist/components/task/TaskList.d.ts +5 -0
  40. package/dist/components/task/TaskList.js +3 -3
  41. package/dist/components/task/TaskStatusActions.js +11 -2
  42. package/dist/components/task/TaskWorkstreamPicker.js +2 -1
  43. package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
  44. package/dist/components/ui/DocumentTypeIcon.js +8 -0
  45. package/dist/components/ui/PasswordInput.d.ts +6 -0
  46. package/dist/components/ui/PasswordInput.js +11 -0
  47. package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
  48. package/dist/components/ui/TaxonomyDropdown.js +30 -3
  49. package/dist/components/ui/TopNoticeLayer.js +1 -1
  50. package/dist/components/views/AppShellHeader.js +32 -11
  51. package/dist/components/views/PlanComparisonPage.js +12 -0
  52. package/dist/components/views/StandaloneLayout.js +68 -39
  53. package/dist/components/views/WidgetView.js +10 -7
  54. package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
  55. package/dist/components/views/panels/PlanningDrawer.js +51 -54
  56. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  57. package/dist/config/envSchema.js +13 -0
  58. package/dist/config/localServiceCloudTarget.d.ts +10 -0
  59. package/dist/config/localServiceCloudTarget.js +76 -0
  60. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  61. package/dist/config/localServiceRuntimeContract.js +135 -0
  62. package/dist/core/AiProfileService.d.ts +1 -0
  63. package/dist/core/AiProfileService.js +48 -9
  64. package/dist/core/AttachmentLinkService.js +7 -4
  65. package/dist/core/DeletedTaskLifecycleService.d.ts +5 -0
  66. package/dist/core/DeletedTaskLifecycleService.js +55 -3
  67. package/dist/core/McpTokenService.d.ts +4 -0
  68. package/dist/core/McpTokenService.js +52 -0
  69. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +8 -2
  70. package/dist/core/TaskAttachmentLinksDurableMutationService.js +51 -18
  71. package/dist/core/Taskforce.d.ts +124 -2
  72. package/dist/core/Taskforce.js +1175 -87
  73. package/dist/core/types.d.ts +8 -1
  74. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
  75. package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
  76. package/dist/documentReviews/service.d.ts +4 -0
  77. package/dist/documentReviews/service.js +141 -46
  78. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  79. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  80. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  81. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  82. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  83. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  84. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  85. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  86. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  87. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  88. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  89. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  90. package/dist/hooks/tasks/useTaskFormActions.d.ts +9 -0
  91. package/dist/hooks/tasks/useTaskFormActions.js +96 -17
  92. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  93. package/dist/hooks/ui/useSettingsModel.js +13 -2
  94. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  95. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  96. package/dist/hooks/useFilterSort.d.ts +7 -3
  97. package/dist/hooks/useFilterSort.js +15 -5
  98. package/dist/hooks/useTaskData.d.ts +10 -1
  99. package/dist/hooks/useTaskData.js +216 -7
  100. package/dist/hooks/useTaskMutations.d.ts +10 -2
  101. package/dist/hooks/useTaskMutations.js +223 -43
  102. package/dist/hooks/useTaskforce.d.ts +94 -57
  103. package/dist/hooks/useTaskforce.js +428 -16
  104. package/dist/hooks/useUiNotice.d.ts +4 -0
  105. package/dist/hooks/useUiNotice.js +4 -0
  106. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +39 -5
  109. package/dist/mcp/collaborationRegistrar.js +15 -13
  110. package/dist/mcp/documentAssetRegistrar.js +60 -44
  111. package/dist/mcp/documentHelpers.d.ts +1 -0
  112. package/dist/mcp/documentHelpers.js +7 -3
  113. package/dist/mcp/localServiceProxy.js +4 -1
  114. package/dist/mcp/runtime.d.ts +16 -0
  115. package/dist/mcp/runtime.js +735 -210
  116. package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
  117. package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
  118. package/dist/mcp/taskPlanningRegistrar.js +78 -67
  119. package/dist/mcp/toolCallCompatibility.js +38 -1
  120. package/dist/mcp/toolCatalog.d.ts +1 -0
  121. package/dist/mcp/toolRegistry.js +2 -2
  122. package/dist/migrations/taskSchemaMigrations.js +47 -0
  123. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  124. package/dist/runtime/appGateDefinitions.js +6 -6
  125. package/dist/runtime/appGates.d.ts +12 -13
  126. package/dist/runtime/appGates.js +29 -13
  127. package/dist/runtime/nodeAppGates.d.ts +8 -0
  128. package/dist/runtime/nodeAppGates.js +27 -0
  129. package/dist/server/appAccess.d.ts +12 -0
  130. package/dist/server/appAccess.js +21 -0
  131. package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
  132. package/dist/server/authorizedWorkspaceAccess.js +40 -0
  133. package/dist/server/cloudCollectionRead.d.ts +25 -0
  134. package/dist/server/cloudCollectionRead.js +120 -0
  135. package/dist/server/cloudCollectionReadPool.d.ts +52 -0
  136. package/dist/server/cloudCollectionReadPool.js +140 -0
  137. package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
  138. package/dist/server/cloudCollectionReadWorker.js +36 -0
  139. package/dist/server/index.d.ts +6 -11
  140. package/dist/server/index.js +67 -42
  141. package/dist/server/localServiceLease.d.ts +6 -0
  142. package/dist/server/localServiceLease.js +17 -1
  143. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  144. package/dist/server/rateLimit.d.ts +1 -0
  145. package/dist/server/rateLimit.js +4 -0
  146. package/dist/server/realtimeSyncWs.js +36 -12
  147. package/dist/server/routes/admin.js +1 -0
  148. package/dist/server/routes/agentRoles.js +3 -2
  149. package/dist/server/routes/agentSkills.js +3 -2
  150. package/dist/server/routes/agents.js +118 -16
  151. package/dist/server/routes/annotatedAttachments.js +3 -2
  152. package/dist/server/routes/auth.js +3 -1
  153. package/dist/server/routes/documents.d.ts +5 -2
  154. package/dist/server/routes/documents.js +160 -13
  155. package/dist/server/routes/durableTaskHttpRouters.d.ts +11 -2
  156. package/dist/server/routes/durableTaskHttpRouters.js +2 -2
  157. package/dist/server/routes/localService.js +4 -0
  158. package/dist/server/routes/primitives.d.ts +4 -0
  159. package/dist/server/routes/shared.d.ts +24 -0
  160. package/dist/server/routes/shared.js +114 -18
  161. package/dist/server/routes/sync.js +22 -0
  162. package/dist/server/routes/syncAuxRoutes.js +5 -2
  163. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  164. package/dist/server/routes/syncV3FeedRoutes.js +22 -18
  165. package/dist/server/routes/syncV3LocalCaptureRoutes.js +48 -4
  166. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  167. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  168. package/dist/server/routes/syncV3OperationRoutes.js +27 -2
  169. package/dist/server/routes/tasks.js +299 -97
  170. package/dist/server/routes/workspaces.js +43 -1
  171. package/dist/server/routes.js +35 -7
  172. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  173. package/dist/server/workspaceDeletionAdmission.js +50 -0
  174. package/dist/service/localServiceCli.js +52 -25
  175. package/dist/service/localServiceClientLifecycle.js +93 -6
  176. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  177. package/dist/services/codexAppServerModelGateway.js +438 -0
  178. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  179. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  180. package/dist/shared/planningIdentity.d.ts +20 -0
  181. package/dist/shared/planningIdentity.js +79 -0
  182. package/dist/shared/taskforceAgentDefinition.js +7 -1
  183. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  184. package/dist/shared/taskforceAgentModels.js +30 -1
  185. package/dist/storage/postgresAdapter.d.ts +20 -3
  186. package/dist/storage/postgresAdapter.js +110 -14
  187. package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
  188. package/dist/storage/postgresRequestDiagnostics.js +24 -0
  189. package/dist/storage/postgresWorker.js +13 -4
  190. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  191. package/dist/storage/postgresWorkerProtocol.js +30 -0
  192. package/dist/storage/workspaceAssetStore.d.ts +32 -0
  193. package/dist/storage/workspaceAssetStore.js +70 -0
  194. package/dist/sync/cloudSyncApi.d.ts +4 -1
  195. package/dist/sync/cloudSyncApi.js +7 -2
  196. package/dist/sync/contentSyncState.d.ts +1 -1
  197. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +17 -0
  198. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +77 -23
  199. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  200. package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
  201. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  202. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  203. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  204. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +16 -0
  205. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  206. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
  207. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +46 -4
  208. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  209. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +51 -11
  210. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  211. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  212. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
  213. package/dist/sync/syncApplyHandlers.d.ts +3 -1
  214. package/dist/sync/syncApplyHandlers.js +29 -0
  215. package/dist/sync/syncService.d.ts +2 -0
  216. package/dist/sync/syncService.js +13 -1
  217. package/dist/sync/taskSyncPayload.d.ts +1 -0
  218. package/dist/sync/taskSyncPayload.js +6 -0
  219. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  220. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +3 -0
  221. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +31 -3
  222. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  223. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  224. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  225. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +47 -12
  226. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  227. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  228. package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
  229. package/dist/sync/v3/localOutboxDispatcher.js +5 -1
  230. package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
  231. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +3 -0
  232. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +62 -10
  233. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  234. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  235. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  236. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  237. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  238. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  239. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  240. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  241. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  242. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  243. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  244. package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
  245. package/dist/sync/v3/syncDurabilityStore.d.ts +15 -0
  246. package/dist/sync/v3/syncDurabilityStore.js +201 -43
  247. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +7 -4
  248. package/dist/sync/v3/taskAttachmentLinksMutationService.js +37 -12
  249. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  250. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  251. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  252. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  253. package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
  254. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  255. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  256. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  257. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  258. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  259. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  260. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  261. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  262. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  263. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +14 -12
  264. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  265. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  266. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  267. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  268. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +10 -1
  269. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +15 -4
  270. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +66 -13
  271. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +11 -0
  272. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +57 -22
  273. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +10 -4
  274. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +124 -51
  275. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  276. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +120 -28
  277. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  278. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  279. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  280. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  281. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  282. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  283. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  284. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  285. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  286. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  287. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  288. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  289. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  290. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  291. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +3 -0
  292. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  293. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  294. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
  295. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
  296. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  297. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  298. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  299. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  300. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  301. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
  302. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
  303. package/dist/sync/workspacePullFeed.d.ts +6 -1
  304. package/dist/sync/workspacePullFeed.js +174 -162
  305. package/dist/sync/workspaceRepair.d.ts +3 -0
  306. package/dist/sync/workspaceRepair.js +67 -6
  307. package/dist/sync/workspaceSyncModel.d.ts +11 -0
  308. package/dist/sync/workspaceSyncModel.js +27 -2
  309. package/dist/types.d.ts +55 -1
  310. package/dist/types.js +6 -0
  311. package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  312. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  313. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  314. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  315. package/dist/ui/assets/AssetTraySortControl-C8Rztyal.js +1 -0
  316. package/dist/ui/assets/ContextAttachmentManager-Cj3c7X9y.js +3 -0
  317. package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-B5uvzems.js} +4 -4
  318. package/dist/ui/assets/EntityActivityTimeline-BVFvCB5R.js +1 -0
  319. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  320. package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
  321. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  322. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  323. package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
  324. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  325. package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  326. package/dist/ui/assets/WorkflowManagerModule-DEr5di_r.js +1 -0
  327. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  328. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  329. package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-D9Lpw-j4.js} +1 -1
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/constants.d.ts +2 -2
  332. package/dist/utils/constants.js +1 -1
  333. package/dist/utils/contextAttachmentUpload.d.ts +2 -0
  334. package/dist/utils/contextAttachmentUpload.js +7 -0
  335. package/dist/utils/contextFiles.d.ts +1 -1
  336. package/dist/utils/contextFiles.js +4 -4
  337. package/dist/utils/taskActivity.d.ts +3 -2
  338. package/dist/utils/taskActivity.js +19 -2
  339. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  340. package/dist/utils/taskEditorReconciliation.js +217 -0
  341. package/dist/utils/taskNormalization.d.ts +1 -0
  342. package/dist/utils/taskNormalization.js +39 -0
  343. package/dist/utils/taskSearch.js +2 -2
  344. package/dist/utils/uiNotice.d.ts +4 -0
  345. package/package.json +21 -4
  346. package/dist/ui/assets/AiProfilesModule-DPxQeF9N.js +0 -1
  347. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  348. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DHNYaNg6.js +0 -3
  349. package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
  350. package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
  351. package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
  352. package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
  353. package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
  354. package/dist/ui/assets/PlansPage-BVH_NfgT.js +0 -1
  355. package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
  356. package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
  357. package/dist/ui/assets/TaskSettings-xVsxSa0g.js +0 -12
  358. package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
  359. package/dist/ui/assets/index-BzJlVnNS.css +0 -1
  360. package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
@@ -11,6 +11,7 @@ const SPECS = [
11
11
  { key: 'TASKFORCE_DB_PROVIDER', description: 'Database provider (sqlite|postgres).', category: 'Core', defaultValue: 'sqlite', example: 'postgres', targets: ['server', 'all'] },
12
12
  { key: 'TASKFORCE_BILLING_ENABLED', description: 'Enable Stripe billing routes and webhook processing.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
13
13
  { key: 'TASKFORCE_DATABASE_URL', description: 'Postgres connection string when provider=postgres.', category: 'Core', example: 'postgresql://user:pass@host:5432/db', secret: true, targets: ['server', 'all'] },
14
+ { key: 'TASKFORCE_CLOUD_READ_WORKERS', description: 'Bounded cloud collection-read worker count (1-8, or 0 to disable).', category: 'Core', defaultValue: '2', targets: ['server', 'all'] },
14
15
  { key: 'TASKFORCE_ALLOW_SQLITE_IN_CLOUD', description: 'Emergency override to allow sqlite in cloud runtime.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
15
16
  { key: 'TASKFORCE_FORCE_SETUP', description: 'Force local setup gate for development.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
16
17
  { key: 'TASKFORCE_FORCE_GLOBAL_SETUP', description: 'Force global setup step for development.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
@@ -142,10 +143,15 @@ const SPECS = [
142
143
  { key: 'TASKFORCE_BEDROCK_SMOKE_MAX_TOKENS', description: 'Maximum output tokens for live Bedrock smoke tests.', category: 'AI', defaultValue: '128', targets: ['server', 'all'] },
143
144
  { key: 'TASKFORCE_BEDROCK_MANTLE_MODEL_KEY', description: 'Optional model key for Bedrock Mantle smoke tests (openai.gpt-5-4|xai.grok-4-3).', category: 'AI', defaultValue: 'openai.gpt-5-4', targets: ['server', 'all'] },
144
145
  { key: 'TASKFORCE_BEDROCK_ENV_FILE', description: 'Optional env file path for the Bedrock smoke script.', category: 'AI', defaultValue: '.env.local', targets: ['server', 'all'] },
146
+ { key: 'TASKFORCE_AGENT_SUBSCRIPTION_DEV_MODELS', description: 'Enable internal local-development-only Taskforce Agent subscription-auth model adapters such as OpenAI Codex app-server.', category: 'AI', defaultValue: 'false', targets: ['server', 'all'] },
147
+ { key: 'TASKFORCE_CODEX_APP_SERVER_COMMAND', description: 'Optional Codex CLI command path for the internal OpenAI Codex app-server Taskforce Agent adapter.', category: 'AI', defaultValue: 'codex', targets: ['server', 'all'] },
148
+ { key: 'TASKFORCE_CODEX_APP_SERVER_CWD', description: 'Optional working directory for the internal Codex app-server subprocess. Defaults to the system temp directory.', category: 'AI', targets: ['server', 'all'] },
145
149
  { key: 'TASKFORCE_ALLOWED_ORIGINS', description: 'Allowed CORS origins (comma-separated).', category: 'Security', targets: ['server', 'all'] },
146
150
  { key: 'TASKFORCE_API_RATE_LIMIT_ENABLED', description: 'Enable API rate limiter.', category: 'Security', defaultValue: 'true', targets: ['server', 'all'] },
147
151
  { key: 'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS', description: 'API rate limit max requests per window.', category: 'Security', defaultValue: '120', targets: ['server', 'all'] },
148
152
  { key: 'TASKFORCE_API_RATE_LIMIT_WINDOW_MS', description: 'API rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
153
+ { key: 'TASKFORCE_SYNC_FEED_RATE_LIMIT_MAX_REQUESTS', description: 'Workspace sync feed read limit per window.', category: 'Security', defaultValue: '600', targets: ['server', 'all'] },
154
+ { key: 'TASKFORCE_SYNC_FEED_RATE_LIMIT_WINDOW_MS', description: 'Workspace sync feed read limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
149
155
  { key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS', description: 'Stripe webhook-specific rate limit max requests per window.', category: 'Security', defaultValue: '600', targets: ['server', 'all'] },
150
156
  { key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS', description: 'Stripe webhook-specific rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
151
157
  { key: 'TASKFORCE_MAX_PAYLOAD_BYTES', description: 'Maximum accepted API payload size in bytes.', category: 'Security', defaultValue: '1048576', targets: ['server', 'all'] },
@@ -167,6 +173,8 @@ const SPECS = [
167
173
  { key: 'TASKFORCE_BUILD_DEPLOY_ID', description: 'Build deploy ID metadata.', category: 'Build', targets: ['server', 'all'] },
168
174
  { key: 'TASKFORCE_UI_DIST_DIR', description: 'Override the standalone UI distribution directory for isolated local QA runtimes.', category: 'Build', targets: ['server', 'all'] },
169
175
  { key: 'TASKFORCE_REQUIRE_EXACT_PORT', description: 'Require the standalone server to use only its requested port.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
176
+ { key: 'TASKFORCE_QA_EXPECTED_WORKSPACE_ID', description: 'Require isolated cloud QA to resolve the exact expected workspace ID before mutation.', category: 'Core', targets: ['server', 'all'] },
177
+ { key: 'TASKFORCE_QA_EXPECTED_WORKSPACE_SLUG', description: 'Require isolated cloud QA to resolve the expected workspace slug before mutation.', category: 'Core', targets: ['server', 'all'] },
170
178
  { key: 'DEBUG_MODE', description: 'Expose debug-only client diagnostics such as pricing source metadata.', category: 'Build', defaultValue: 'false', targets: ['all'] },
171
179
  { key: 'VITE_TASKFORCE_BASE_URL', description: 'Client base URL for both API and cloud auth when specific overrides are not set.', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
172
180
  { key: 'VITE_TASKFORCE_API_BASE_URL', description: 'Client API base URL (overrides VITE_TASKFORCE_BASE_URL when set).', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
@@ -184,17 +192,20 @@ const SPECS = [
184
192
  { key: 'SYNC_SOAK_PROFILE', description: 'Soak profile (smoke|stress|overnight).', category: 'Soak', defaultValue: 'smoke', targets: ['soak', 'all'] },
185
193
  { key: 'SYNC_SOAK_DURATION_MIN', description: 'Soak duration in minutes.', category: 'Soak', defaultValue: '60', targets: ['soak', 'all'] },
186
194
  { key: 'SYNC_SOAK_SEED', description: 'Deterministic soak seed.', category: 'Soak', example: '4242', targets: ['soak', 'all'] },
195
+ { key: 'SYNC_SOAK_TASK_ID_NAMESPACE', description: 'Optional per-run namespace for deterministic generated soak task IDs.', category: 'Soak', targets: ['soak', 'all'] },
187
196
  { key: 'SYNC_SOAK_ARTIFACT_DIR', description: 'Soak artifacts output directory.', category: 'Soak', defaultValue: '.taskforce/soak-artifacts', targets: ['soak', 'all'] },
188
197
  { key: 'SYNC_SOAK_ENABLE_SYNC_DEBUG', description: 'Enable sync debug diagnostics during soak.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
189
198
  { key: 'SYNC_SOAK_STRESS_ACTIONS', description: 'Stress action count.', category: 'Soak', defaultValue: '150', targets: ['soak', 'all'] },
190
199
  { key: 'SYNC_SOAK_STRESS_CHECKPOINT_INTERVAL', description: 'Stress checkpoint interval.', category: 'Soak', defaultValue: '50', targets: ['soak', 'all'] },
191
200
  { key: 'SYNC_SOAK_STRESS_SETTLE_RETRIES', description: 'Stress settle retry count.', category: 'Soak', defaultValue: '3', targets: ['soak', 'all'] },
192
201
  { key: 'SYNC_SOAK_STRESS_SETTLE_WAIT_MS', description: 'Stress settle wait time in ms.', category: 'Soak', defaultValue: '500', targets: ['soak', 'all'] },
202
+ { key: 'SYNC_SOAK_STRESS_BOOTSTRAP_PUSH_SETTLE', description: 'Enable legacy bootstrap-push checkpoint settlement in stress and overnight profiles.', category: 'Soak', defaultValue: 'true', targets: ['soak', 'all'] },
193
203
  { key: 'SYNC_SOAK_STRESS_HARD_RESET_SETTLE', description: 'Enable hard reset settle path in stress.', category: 'Soak', defaultValue: 'true', targets: ['soak', 'all'] },
194
204
  { key: 'SYNC_SOAK_OVERNIGHT_ACTION_INTERVAL_MS', description: 'Overnight action pacing interval in ms.', category: 'Soak', defaultValue: '60000', targets: ['soak', 'all'] },
195
205
  { key: 'SYNC_SOAK_OVERNIGHT_CHECKPOINT_MIN', description: 'Overnight checkpoint interval in minutes.', category: 'Soak', defaultValue: '15', targets: ['soak', 'all'] },
196
206
  { key: 'SYNC_SOAK_OVERNIGHT_MAX_ACTIONS', description: 'Overnight hard cap on action count.', category: 'Soak', defaultValue: '10000', targets: ['soak', 'all'] },
197
207
  { key: 'SYNC_SOAK_OVERNIGHT_MIN_ACTIONS', description: 'Overnight minimum actions expected before pass.', category: 'Soak', defaultValue: '80', targets: ['soak', 'all'] },
208
+ { key: 'SYNC_SOAK_OVERNIGHT_CLEAN_BASELINE_EVERY_ACTION', description: 'Require coordinator settlement before each accelerated overnight action.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
198
209
  { key: 'CF_ACCESS_CLIENT_ID', description: 'Cloudflare Access service token client id (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] },
199
210
  { key: 'CF_ACCESS_CLIENT_SECRET', description: 'Cloudflare Access service token client secret (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] }
200
211
  ];
@@ -254,6 +265,8 @@ function validateCommonNumbers(env, errors) {
254
265
  'PORT',
255
266
  'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS',
256
267
  'TASKFORCE_API_RATE_LIMIT_WINDOW_MS',
268
+ 'TASKFORCE_SYNC_FEED_RATE_LIMIT_MAX_REQUESTS',
269
+ 'TASKFORCE_SYNC_FEED_RATE_LIMIT_WINDOW_MS',
257
270
  'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS',
258
271
  'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS',
259
272
  'TASKFORCE_MAX_PAYLOAD_BYTES',
@@ -0,0 +1,10 @@
1
+ import { type TaskforceCloudEnvironment } from './cloudEnvironment.js';
2
+ export interface LocalServiceCloudTarget {
3
+ environment: TaskforceCloudEnvironment | null;
4
+ cloudProxyBaseUrl: string;
5
+ cloudAuthBaseUrl: string;
6
+ }
7
+ export declare function resolveLocalServiceCloudTarget(env: NodeJS.ProcessEnv): LocalServiceCloudTarget;
8
+ export declare function applyLocalServiceCloudTarget(env: NodeJS.ProcessEnv, target: LocalServiceCloudTarget): void;
9
+ export declare function parseLocalServiceCloudTarget(value: unknown): LocalServiceCloudTarget | null;
10
+ export declare function localServiceCloudTargetsEqual(left: LocalServiceCloudTarget | null | undefined, right: LocalServiceCloudTarget | null | undefined): boolean;
@@ -0,0 +1,76 @@
1
+ import { TASKFORCE_CLOUD_ENVIRONMENTS, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl, } from './cloudEnvironment.js';
2
+ import { resolveDeploymentConfig } from './deployment.js';
3
+ function normalizeCloudBaseUrl(raw, label) {
4
+ let parsed;
5
+ try {
6
+ parsed = new URL(String(raw || '').trim());
7
+ }
8
+ catch {
9
+ throw new Error(`${label} must be a valid HTTP(S) URL.`);
10
+ }
11
+ if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password) {
12
+ throw new Error(`${label} must be an HTTP(S) URL without embedded credentials.`);
13
+ }
14
+ if (parsed.search || parsed.hash) {
15
+ throw new Error(`${label} must not include a query string or fragment.`);
16
+ }
17
+ const pathname = parsed.pathname.replace(/\/+$/, '');
18
+ return pathname ? `${parsed.origin}${pathname}` : parsed.origin;
19
+ }
20
+ export function resolveLocalServiceCloudTarget(env) {
21
+ const rawEnvironment = String(env.TASKFORCE_CLOUD_ENVIRONMENT || '').trim();
22
+ const explicitEnvironment = parseTaskforceCloudEnvironment(rawEnvironment);
23
+ if (rawEnvironment && !explicitEnvironment) {
24
+ throw new Error(`Unknown Taskforce cloud environment: ${rawEnvironment}.`);
25
+ }
26
+ const deployment = resolveDeploymentConfig(env);
27
+ const productionOrigin = TASKFORCE_CLOUD_ENVIRONMENTS.production.appBaseUrl;
28
+ const cloudProxyBaseUrl = normalizeCloudBaseUrl(deployment.cloudBaseUrl || productionOrigin, 'Taskforce cloud proxy URL');
29
+ const cloudAuthBaseUrl = normalizeCloudBaseUrl(deployment.cloudAuthBaseUrl || cloudProxyBaseUrl, 'Taskforce cloud auth URL');
30
+ const proxyEnvironment = resolveTaskforceCloudEnvironmentFromBaseUrl(cloudProxyBaseUrl);
31
+ const authEnvironment = resolveTaskforceCloudEnvironmentFromBaseUrl(cloudAuthBaseUrl);
32
+ const knownEnvironments = [explicitEnvironment, proxyEnvironment, authEnvironment]
33
+ .filter((value) => Boolean(value));
34
+ if (new Set(knownEnvironments).size > 1) {
35
+ throw new Error('Taskforce cloud environment, proxy URL, and auth URL must identify the same cloud target.');
36
+ }
37
+ return {
38
+ environment: explicitEnvironment || proxyEnvironment || authEnvironment,
39
+ cloudProxyBaseUrl,
40
+ cloudAuthBaseUrl,
41
+ };
42
+ }
43
+ export function applyLocalServiceCloudTarget(env, target) {
44
+ if (target.environment)
45
+ env.TASKFORCE_CLOUD_ENVIRONMENT = target.environment;
46
+ env.TASKFORCE_CLOUD_PROXY_BASE_URL ||= target.cloudProxyBaseUrl;
47
+ env.VITE_TASKFORCE_CLOUD_AUTH_BASE_URL ||= target.cloudAuthBaseUrl;
48
+ }
49
+ export function parseLocalServiceCloudTarget(value) {
50
+ if (!value || typeof value !== 'object' || Array.isArray(value))
51
+ return null;
52
+ const record = value;
53
+ const environment = record.environment === null
54
+ ? null
55
+ : parseTaskforceCloudEnvironment(record.environment);
56
+ if (record.environment !== null && !environment)
57
+ return null;
58
+ if (typeof record.cloudProxyBaseUrl !== 'string' || typeof record.cloudAuthBaseUrl !== 'string')
59
+ return null;
60
+ try {
61
+ const cloudProxyBaseUrl = normalizeCloudBaseUrl(record.cloudProxyBaseUrl, 'Taskforce cloud proxy base URL');
62
+ const cloudAuthBaseUrl = normalizeCloudBaseUrl(record.cloudAuthBaseUrl, 'Taskforce cloud auth base URL');
63
+ if (cloudProxyBaseUrl !== record.cloudProxyBaseUrl || cloudAuthBaseUrl !== record.cloudAuthBaseUrl)
64
+ return null;
65
+ return { environment, cloudProxyBaseUrl, cloudAuthBaseUrl };
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ export function localServiceCloudTargetsEqual(left, right) {
72
+ return Boolean(left && right
73
+ && left.environment === right.environment
74
+ && left.cloudProxyBaseUrl === right.cloudProxyBaseUrl
75
+ && left.cloudAuthBaseUrl === right.cloudAuthBaseUrl);
76
+ }
@@ -0,0 +1,13 @@
1
+ export interface LocalServiceRuntimeContract {
2
+ schemaVersion: 1;
3
+ fingerprint: string;
4
+ coverageVersion: number | null;
5
+ }
6
+ export declare function hydrateSourceCheckoutServiceEnvironment(input: {
7
+ env: NodeJS.ProcessEnv;
8
+ projectRoot: string;
9
+ packageRoot: string;
10
+ }): string | null;
11
+ export declare function resolveLocalServiceRuntimeContract(env: NodeJS.ProcessEnv): LocalServiceRuntimeContract;
12
+ export declare function parseLocalServiceRuntimeContract(value: unknown): LocalServiceRuntimeContract | null;
13
+ export declare function localServiceRuntimeContractsEqual(left: LocalServiceRuntimeContract | null | undefined, right: LocalServiceRuntimeContract | null | undefined): boolean;
@@ -0,0 +1,135 @@
1
+ import { createHash } from 'crypto';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { parseTaskforceCloudEnvironment } from './cloudEnvironment.js';
5
+ const SERVICE_ENV_EXACT_KEYS = new Set([
6
+ 'CF_ACCESS_CLIENT_ID',
7
+ 'CF_ACCESS_CLIENT_SECRET',
8
+ 'TASKFORCE_CF_ACCESS_CLIENT_ID',
9
+ 'TASKFORCE_CF_ACCESS_CLIENT_SECRET',
10
+ 'TASKFORCE_REALTIME_SYNC',
11
+ 'TASKFORCE_RUNTIME_MODE',
12
+ ]);
13
+ function isServiceEnvironmentKey(key) {
14
+ if (SERVICE_ENV_EXACT_KEYS.has(key))
15
+ return true;
16
+ if (key.startsWith('TASKFORCE_E2E_') || key.startsWith('TASKFORCE_PLAYWRIGHT_'))
17
+ return false;
18
+ return key.startsWith('TASKFORCE_SYNC_')
19
+ || key.startsWith('TASKFORCE_LOCAL_SYNC_')
20
+ || key.startsWith('VITE_TASKFORCE_');
21
+ }
22
+ function isContractKey(key) {
23
+ return key === 'TASKFORCE_RUNTIME_MODE'
24
+ || key === 'TASKFORCE_REALTIME_SYNC'
25
+ || key.startsWith('TASKFORCE_SYNC_')
26
+ || key.startsWith('TASKFORCE_LOCAL_SYNC_');
27
+ }
28
+ function stripQuotes(value) {
29
+ const trimmed = value.trim();
30
+ if (trimmed.length >= 2) {
31
+ const first = trimmed[0];
32
+ const last = trimmed[trimmed.length - 1];
33
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
34
+ return trimmed.slice(1, -1);
35
+ }
36
+ }
37
+ return trimmed;
38
+ }
39
+ function isInside(parent, child) {
40
+ const relative = path.relative(parent, child);
41
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
42
+ }
43
+ function loadTrustedEnvFile(filePath, packageRoot, loaded, visited) {
44
+ const resolved = fs.realpathSync(filePath);
45
+ if (!isInside(packageRoot, resolved) || !path.basename(resolved).startsWith('.env')) {
46
+ throw new Error('Taskforce source-checkout environment includes must remain trusted .env files inside the package root.');
47
+ }
48
+ if (visited.has(resolved))
49
+ return;
50
+ visited.add(resolved);
51
+ const lines = fs.readFileSync(resolved, 'utf8').split(/\r?\n/);
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed || trimmed.startsWith('#'))
55
+ continue;
56
+ const include = trimmed.match(/^(?:\.|source)\s+([^\s]+)$/);
57
+ if (include) {
58
+ const includePath = path.resolve(path.dirname(resolved), stripQuotes(include[1]));
59
+ loadTrustedEnvFile(includePath, packageRoot, loaded, visited);
60
+ continue;
61
+ }
62
+ const normalized = trimmed.startsWith('export ') ? trimmed.slice(7).trim() : trimmed;
63
+ const separator = normalized.indexOf('=');
64
+ if (separator <= 0)
65
+ continue;
66
+ const key = normalized.slice(0, separator).trim();
67
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key) || !isServiceEnvironmentKey(key))
68
+ continue;
69
+ loaded.set(key, stripQuotes(normalized.slice(separator + 1)));
70
+ }
71
+ }
72
+ export function hydrateSourceCheckoutServiceEnvironment(input) {
73
+ const projectRoot = fs.realpathSync(input.projectRoot);
74
+ const packageRoot = fs.realpathSync(input.packageRoot);
75
+ if (projectRoot !== packageRoot)
76
+ return null;
77
+ const environment = parseTaskforceCloudEnvironment(input.env.TASKFORCE_CLOUD_ENVIRONMENT);
78
+ if (environment !== 'performance' && environment !== 'staging')
79
+ return null;
80
+ const envFile = path.join(packageRoot, `.env.${environment}`);
81
+ if (!fs.existsSync(envFile))
82
+ return null;
83
+ const explicitKeys = new Set(Object.keys(input.env));
84
+ const loaded = new Map();
85
+ loadTrustedEnvFile(envFile, packageRoot, loaded, new Set());
86
+ for (const [key, value] of loaded) {
87
+ if (!explicitKeys.has(key))
88
+ input.env[key] = value;
89
+ }
90
+ return envFile;
91
+ }
92
+ export function resolveLocalServiceRuntimeContract(env) {
93
+ const effectiveEnv = {
94
+ ...env,
95
+ TASKFORCE_RUNTIME_MODE: String(env.TASKFORCE_RUNTIME_MODE || 'local').trim().toLowerCase(),
96
+ };
97
+ const entries = Object.entries(effectiveEnv)
98
+ .filter(([key, value]) => isContractKey(key) && value !== undefined)
99
+ .map(([key, value]) => [key, String(value)])
100
+ .sort(([left], [right]) => left.localeCompare(right));
101
+ const fingerprint = createHash('sha256')
102
+ .update(JSON.stringify(entries))
103
+ .digest('hex');
104
+ const rawCoverageVersion = Number.parseInt(String(env.TASKFORCE_SYNC_V3_COMBINED_FEED_COVERAGE_VERSION || ''), 10);
105
+ return {
106
+ schemaVersion: 1,
107
+ fingerprint,
108
+ coverageVersion: Number.isSafeInteger(rawCoverageVersion) && rawCoverageVersion > 0
109
+ ? rawCoverageVersion
110
+ : null,
111
+ };
112
+ }
113
+ export function parseLocalServiceRuntimeContract(value) {
114
+ if (!value || typeof value !== 'object' || Array.isArray(value))
115
+ return null;
116
+ const record = value;
117
+ if (record.schemaVersion !== 1
118
+ || typeof record.fingerprint !== 'string'
119
+ || !/^[a-f0-9]{64}$/.test(record.fingerprint)
120
+ || (record.coverageVersion !== null
121
+ && (!Number.isSafeInteger(record.coverageVersion) || Number(record.coverageVersion) < 1))) {
122
+ return null;
123
+ }
124
+ return {
125
+ schemaVersion: 1,
126
+ fingerprint: record.fingerprint,
127
+ coverageVersion: record.coverageVersion === null ? null : Number(record.coverageVersion),
128
+ };
129
+ }
130
+ export function localServiceRuntimeContractsEqual(left, right) {
131
+ return Boolean(left && right
132
+ && left.schemaVersion === right.schemaVersion
133
+ && left.fingerprint === right.fingerprint
134
+ && left.coverageVersion === right.coverageVersion);
135
+ }
@@ -138,6 +138,7 @@ export declare function getAiProfileByConnectionService(deps: AiProfileServiceDe
138
138
  workspaceId: string;
139
139
  connectionId: string;
140
140
  seatScope?: AiProfileSeatScope | null;
141
+ repairLegacyBinding?: boolean;
141
142
  }): AiProfileRecordLike | null;
142
143
  export declare function archiveAiProfileService(deps: AiProfileServiceDeps, input: {
143
144
  workspaceId: string;
@@ -931,15 +931,27 @@ function reconcileSyncedAiProfileConnectionBindings(deps, workspaceId, profileId
931
931
  .sort()
932
932
  : [];
933
933
  const clientId = String(connection.clientId || '').trim() || null;
934
- if (bindingIds.length === 0) {
935
- deps.db.prepare(`DELETE FROM ai_profile_connection_bindings WHERE tenant_id = ? AND workspace_id = ? AND profile_id = ?`)
934
+ // Credential-derived bindings are authentication state owned by the cloud
935
+ // runtime. Synced profile metadata must never create, remove, or reassign
936
+ // them; doing so would let a stale replica change write attribution.
937
+ const syncedBindingIds = bindingIds.filter((bindingId) => !isCredentialOwnedConnectionBindingId(bindingId));
938
+ if (syncedBindingIds.length === 0) {
939
+ deps.db.prepare(`DELETE FROM ai_profile_connection_bindings
940
+ WHERE tenant_id = ? AND workspace_id = ? AND profile_id = ?
941
+ AND connection_id NOT LIKE 'oauth:%'
942
+ AND connection_id NOT LIKE 'oauth-client:%'
943
+ AND connection_id NOT LIKE 'token:%'`)
936
944
  .run(deps.tenantId, workspaceId, profileId);
937
945
  return;
938
946
  }
939
- const placeholders = bindingIds.map(() => '?').join(', ');
947
+ const placeholders = syncedBindingIds.map(() => '?').join(', ');
940
948
  deps.db.prepare(`DELETE FROM ai_profile_connection_bindings
941
- WHERE tenant_id = ? AND workspace_id = ? AND profile_id = ? AND connection_id NOT IN (${placeholders})`)
942
- .run(deps.tenantId, workspaceId, profileId, ...bindingIds);
949
+ WHERE tenant_id = ? AND workspace_id = ? AND profile_id = ?
950
+ AND connection_id NOT LIKE 'oauth:%'
951
+ AND connection_id NOT LIKE 'oauth-client:%'
952
+ AND connection_id NOT LIKE 'token:%'
953
+ AND connection_id NOT IN (${placeholders})`)
954
+ .run(deps.tenantId, workspaceId, profileId, ...syncedBindingIds);
943
955
  const now = new Date().toISOString();
944
956
  const upsert = deps.db.prepare(`INSERT INTO ai_profile_connection_bindings
945
957
  (tenant_id, workspace_id, connection_id, profile_id, client_id, created_at, updated_at)
@@ -948,7 +960,7 @@ function reconcileSyncedAiProfileConnectionBindings(deps, workspaceId, profileId
948
960
  profile_id = excluded.profile_id,
949
961
  client_id = excluded.client_id,
950
962
  updated_at = excluded.updated_at`);
951
- for (const bindingId of bindingIds) {
963
+ for (const bindingId of syncedBindingIds) {
952
964
  upsert.run(deps.tenantId, workspaceId, bindingId, profileId, clientId, now, now);
953
965
  }
954
966
  }
@@ -1282,6 +1294,11 @@ function normalizeConnectionBindingId(deps, value) {
1282
1294
  }
1283
1295
  return normalized;
1284
1296
  }
1297
+ function isCredentialOwnedConnectionBindingId(connectionId) {
1298
+ return connectionId.startsWith('oauth:')
1299
+ || connectionId.startsWith('oauth-client:')
1300
+ || connectionId.startsWith('token:');
1301
+ }
1285
1302
  function parseProviderMetadataRow(row) {
1286
1303
  if (typeof row.provider_metadata_json !== 'string' || !row.provider_metadata_json.trim())
1287
1304
  return {};
@@ -1304,10 +1321,29 @@ export function bindAiProfileConnectionService(deps, input) {
1304
1321
  if (!target)
1305
1322
  throw deps.createRuleError(`Profile ${profileId} not found`, 404, 'AI_PROFILE_NOT_FOUND');
1306
1323
  const now = new Date().toISOString();
1307
- deps.db.prepare(`INSERT INTO ai_profile_connection_bindings (tenant_id, workspace_id, connection_id, profile_id, client_id, created_at, updated_at)
1324
+ deps.db.prepare(`INSERT INTO ai_profile_connection_bindings
1325
+ (tenant_id, workspace_id, connection_id, profile_id, client_id, created_at, updated_at)
1308
1326
  VALUES (?, ?, ?, ?, ?, ?, ?)
1309
- ON CONFLICT (tenant_id, workspace_id, connection_id) DO UPDATE SET profile_id = excluded.profile_id, client_id = excluded.client_id, updated_at = excluded.updated_at`)
1327
+ ON CONFLICT (tenant_id, workspace_id, connection_id) DO NOTHING`)
1310
1328
  .run(deps.tenantId, workspaceId, connectionId, profileId, clientId, now, now);
1329
+ const existing = deps.db.prepare(`SELECT b.profile_id, p.archived_at
1330
+ FROM ai_profile_connection_bindings b
1331
+ LEFT JOIN ai_profiles p ON p.tenant_id = b.tenant_id AND p.workspace_id = b.workspace_id AND p.id = b.profile_id
1332
+ WHERE b.tenant_id = ? AND b.workspace_id = ? AND b.connection_id = ? LIMIT 1`)
1333
+ .get(deps.tenantId, workspaceId, connectionId);
1334
+ if (!existing?.profile_id) {
1335
+ throw deps.createRuleError('Failed to establish the MCP connection binding.', 500, 'AI_PROFILE_CONNECTION_BIND_FAILED');
1336
+ }
1337
+ if (existing && existing.profile_id !== profileId && !existing.archived_at) {
1338
+ throw deps.createRuleError('This MCP connection is already bound to a different active AI profile. Reconnect or use an authorized profile recovery flow.', 409, 'AI_PROFILE_CONNECTION_CONFLICT', { workspaceId, connectionId, existingProfileId: existing.profile_id || null, requestedProfileId: profileId });
1339
+ }
1340
+ const updateResult = deps.db.prepare(`UPDATE ai_profile_connection_bindings
1341
+ SET profile_id = ?, client_id = ?, updated_at = ?
1342
+ WHERE tenant_id = ? AND workspace_id = ? AND connection_id = ? AND profile_id = ?`)
1343
+ .run(profileId, clientId, now, deps.tenantId, workspaceId, connectionId, existing.profile_id);
1344
+ if (updateResult.changes !== 1) {
1345
+ throw deps.createRuleError('The MCP connection binding changed concurrently. Retry after refreshing the intended profile.', 409, 'AI_PROFILE_CONNECTION_CONFLICT', { workspaceId, connectionId, expectedProfileId: existing.profile_id || null, requestedProfileId: profileId });
1346
+ }
1311
1347
  const rows = deps.db.prepare(`SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ?`).all(deps.tenantId, workspaceId);
1312
1348
  for (const row of rows) {
1313
1349
  const metadata = parseProviderMetadataRow(row);
@@ -1351,6 +1387,8 @@ export function getAiProfileByConnectionService(deps, input) {
1351
1387
  const record = hydrateAiProfileRecord(deps, binding);
1352
1388
  return input.seatScope !== undefined && (record.seatScope ?? null) !== input.seatScope ? null : record;
1353
1389
  }
1390
+ if (isCredentialOwnedConnectionBindingId(connectionId))
1391
+ return null;
1354
1392
  const legacyRows = deps.db.prepare(`SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ?`).all(deps.tenantId, workspaceId)
1355
1393
  .filter((row) => {
1356
1394
  const record = hydrateAiProfileRecord(deps, row);
@@ -1367,8 +1405,9 @@ export function getAiProfileByConnectionService(deps, input) {
1367
1405
  if (legacyRows.length === 0)
1368
1406
  return null;
1369
1407
  const record = hydrateAiProfileRecord(deps, legacyRows[0]);
1370
- if (!record.archivedAt)
1408
+ if (!record.archivedAt && input.repairLegacyBinding !== false) {
1371
1409
  bindAiProfileConnectionService(deps, { workspaceId, profileId: record.id, connectionId });
1410
+ }
1372
1411
  return record;
1373
1412
  }
1374
1413
  export function archiveAiProfileService(deps, input) {
@@ -262,11 +262,14 @@ export function syncCanonicalAttachmentLinksForTargetService(deps, targetType, t
262
262
  : String(row?.kind || '').trim() === 'image'
263
263
  ? 'image'
264
264
  : 'attachment';
265
- const displayName = String(row?.kind || '').trim() === 'document' && !isExternalStorageKey
265
+ const displayName = typeof attachment === 'string'
266
266
  ? null
267
- : (typeof attachment === 'string'
268
- ? null
269
- : buildAttachmentDisplayName(attachment, isExternalStorageKey || !storageKey ? null : path.basename(storageKey)));
267
+ : safeTargetType === 'task'
268
+ ? String(attachment.displayName || attachment.originalFilename || attachment.caption
269
+ || (isExternalStorageKey || !storageKey ? '' : path.basename(storageKey))).trim() || null
270
+ : String(row?.kind || '').trim() === 'document' && !isExternalStorageKey
271
+ ? null
272
+ : buildAttachmentDisplayName(attachment, isExternalStorageKey || !storageKey ? null : path.basename(storageKey));
270
273
  const timestamp = typeof attachment === 'string' ? new Date().toISOString() : (attachment.timestamp || new Date().toISOString());
271
274
  upsertLink.run(deps.tenantId, normalizedWorkspaceId, assetId, safeTargetType, normalizedTargetId, role, displayName, index, timestamp, timestamp);
272
275
  });
@@ -8,6 +8,7 @@ export interface DeletedTaskLifecycleServiceDeps {
8
8
  generateEntityId: (prefix: string) => string;
9
9
  normalizeWorkspaceId: (workspaceId: string | undefined) => string;
10
10
  resolveWorkspaceActor: (workspaceId: string, actorRef: unknown) => TaskActor | null;
11
+ resolveWorkspaceActors: (workspaceId: string, actorRefs: unknown[]) => Map<string, TaskActor | null>;
11
12
  sanitizeOrderInDay: (value: unknown) => number | null;
12
13
  sanitizeDateOnly: (value: unknown) => string | null;
13
14
  normalizeTaskEventActor: (actorRef: unknown, workspaceId: string) => {
@@ -16,6 +17,7 @@ export interface DeletedTaskLifecycleServiceDeps {
16
17
  actorProfile: TaskActor | null;
17
18
  };
18
19
  runRetentionPurgeTransaction: <T>(workspaceId: string | null, apply: (durableDeleteOwnedKeys: ReadonlySet<string>) => T) => T;
20
+ listDurableDeleteOwnedTaskIds: (workspaceId: string) => ReadonlySet<string>;
19
21
  }
20
22
  export declare function normalizeTaskSnapshotForRestoreService(deps: DeletedTaskLifecycleServiceDeps, task: TaskItem): TaskItem;
21
23
  export declare function normalizeDeletedTaskRestoreLifecycleService(deps: DeletedTaskLifecycleServiceDeps, task: TaskItem): Pick<TaskItem, 'status' | 'isArchived' | 'completedAt' | 'canceledReason'>;
@@ -33,4 +35,7 @@ export declare function addDeletedTaskRecordService(deps: DeletedTaskLifecycleSe
33
35
  export declare function listDeletedTasksService(deps: DeletedTaskLifecycleServiceDeps, workspaceId?: string): {
34
36
  deleted: DeletedTaskRecord[];
35
37
  };
38
+ export declare function listDeletedTasksReadOnlyService(deps: DeletedTaskLifecycleServiceDeps, workspaceId?: string): {
39
+ deleted: DeletedTaskRecord[];
40
+ };
36
41
  export declare function getDeletedTaskService(deps: DeletedTaskLifecycleServiceDeps, id: string, workspaceId?: string): DeletedTaskRecord | null;
@@ -119,6 +119,41 @@ export function mapDeletedTaskRowService(deps, row) {
119
119
  actorProfile: actor === 'system' ? null : deps.resolveWorkspaceActor(workspaceId, actor)
120
120
  };
121
121
  }
122
+ function mapDeletedTaskRowsService(deps, rows) {
123
+ const actorProfiles = new Map();
124
+ const actorRefsByWorkspace = new Map();
125
+ for (const row of rows) {
126
+ const workspaceId = deps.normalizeWorkspaceId(typeof row.workspace_id === 'string' ? row.workspace_id : undefined);
127
+ const actorRef = String(row.actor || 'system').trim() || 'system';
128
+ if (actorRef === 'system') {
129
+ actorProfiles.set(`${workspaceId}\u0000${actorRef}`, null);
130
+ continue;
131
+ }
132
+ const workspaceActorRefs = actorRefsByWorkspace.get(workspaceId) ?? new Set();
133
+ workspaceActorRefs.add(actorRef);
134
+ actorRefsByWorkspace.set(workspaceId, workspaceActorRefs);
135
+ }
136
+ for (const [workspaceId, actorRefs] of actorRefsByWorkspace) {
137
+ const resolvedWorkspaceActors = deps.resolveWorkspaceActors(workspaceId, [...actorRefs]);
138
+ for (const actorRef of actorRefs) {
139
+ actorProfiles.set(`${workspaceId}\u0000${actorRef}`, resolvedWorkspaceActors.get(actorRef) ?? null);
140
+ }
141
+ }
142
+ const cachedDeps = {
143
+ ...deps,
144
+ resolveWorkspaceActor: (workspaceId, actorRef) => {
145
+ const cacheKey = `${workspaceId}\u0000${String(actorRef || '').trim()}`;
146
+ if (actorProfiles.has(cacheKey))
147
+ return actorProfiles.get(cacheKey) ?? null;
148
+ const actorProfile = deps.resolveWorkspaceActor(workspaceId, actorRef);
149
+ actorProfiles.set(cacheKey, actorProfile);
150
+ return actorProfile;
151
+ },
152
+ };
153
+ return rows
154
+ .map((row) => mapDeletedTaskRowService(cachedDeps, row))
155
+ .filter((row) => Boolean(row));
156
+ }
122
157
  function deletedTaskListSnapshotSelect() {
123
158
  const snapshotPairs = DELETED_TASK_LIST_SNAPSHOT_FIELDS
124
159
  .map((field) => `'${field}', snapshot -> '${field}'`)
@@ -220,9 +255,26 @@ export function listDeletedTasksService(deps, workspaceId) {
220
255
  `;
221
256
  const rows = deps.db.prepare(listSql).all(deps.tenantId, normalizedWorkspaceId);
222
257
  return {
223
- deleted: rows
224
- .map((row) => mapDeletedTaskRowService(deps, row))
225
- .filter((row) => Boolean(row))
258
+ deleted: mapDeletedTaskRowsService(deps, rows)
259
+ };
260
+ }
261
+ export function listDeletedTasksReadOnlyService(deps, workspaceId) {
262
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
263
+ const cutoff = new Date(Date.now() - (DELETED_TASK_RETENTION_DAYS * 24 * 60 * 60 * 1000)).toISOString();
264
+ const durableDeleteOwnedTaskIds = deps.listDurableDeleteOwnedTaskIds(normalizedWorkspaceId);
265
+ const listSql = deps.db.provider === 'postgres'
266
+ ? deletedTaskListSnapshotSelect()
267
+ : `
268
+ SELECT *
269
+ FROM deleted_tasks
270
+ WHERE tenant_id = ?
271
+ AND workspace_id = ?
272
+ ORDER BY deleted_at DESC, id DESC
273
+ `;
274
+ const rows = deps.db.prepare(listSql).all(deps.tenantId, normalizedWorkspaceId);
275
+ return {
276
+ deleted: mapDeletedTaskRowsService(deps, rows)
277
+ .filter((row) => row.deletedAt >= cutoff || durableDeleteOwnedTaskIds.has(row.taskId))
226
278
  };
227
279
  }
228
280
  export function getDeletedTaskService(deps, id, workspaceId) {
@@ -124,6 +124,10 @@ export declare class McpTokenService {
124
124
  profileToken: string;
125
125
  profileName: string;
126
126
  }): boolean;
127
+ listMcpOAuthRestorableAiProfileIds(input: {
128
+ grantId: string;
129
+ workspaceId: string;
130
+ }): string[];
127
131
  recordMcpOAuthConnectorGrantUsage(input: {
128
132
  grantId: string;
129
133
  workspaceId: string;
@@ -973,6 +973,58 @@ export class McpTokenService {
973
973
  `).run(profileId, profileToken, profileName, now, this.tenantId, workspaceId, grantId);
974
974
  return Number(result?.changes || 0) > 0;
975
975
  }
976
+ listMcpOAuthRestorableAiProfileIds(input) {
977
+ const grantId = String(input.grantId || '').trim();
978
+ const workspaceId = String(input.workspaceId || '').trim();
979
+ if (!grantId || !workspaceId)
980
+ return [];
981
+ const current = this.db.prepare(`
982
+ SELECT current.provider, current.user_id, current_client.client_name,
983
+ current_client.redirect_uris_json, current_client.product_client_id
984
+ FROM mcp_oauth_connector_grants current
985
+ JOIN mcp_oauth_clients current_client
986
+ ON current_client.tenant_id = current.tenant_id
987
+ AND current_client.id = current.client_id
988
+ WHERE current.tenant_id = ?
989
+ AND current.workspace_id = ?
990
+ AND current.id = ?
991
+ AND current.status = 'active'
992
+ LIMIT 1
993
+ `).get(this.tenantId, workspaceId, grantId);
994
+ if (!current?.provider || !current.user_id)
995
+ return [];
996
+ const canonicalRedirectUris = (value) => {
997
+ try {
998
+ const parsed = JSON.parse(String(value || '[]'));
999
+ if (!Array.isArray(parsed))
1000
+ return null;
1001
+ return JSON.stringify([...new Set(parsed.map((uri) => String(uri || '').trim()).filter(Boolean))].sort());
1002
+ }
1003
+ catch {
1004
+ return null;
1005
+ }
1006
+ };
1007
+ const currentRedirectUris = canonicalRedirectUris(current.redirect_uris_json);
1008
+ if (!currentRedirectUris)
1009
+ return [];
1010
+ const previousRows = this.db.prepare(`
1011
+ SELECT previous.ai_profile_id, previous_client.client_name,
1012
+ previous_client.redirect_uris_json, previous_client.product_client_id
1013
+ FROM mcp_oauth_connector_grants previous
1014
+ JOIN mcp_oauth_clients previous_client
1015
+ ON previous_client.tenant_id = previous.tenant_id
1016
+ AND previous_client.id = previous.client_id
1017
+ WHERE previous.tenant_id = ?
1018
+ AND previous.workspace_id = ?
1019
+ AND previous.user_id = ?
1020
+ AND previous.provider = ?
1021
+ AND previous.id <> ?
1022
+ AND previous.ai_profile_id IS NOT NULL
1023
+ `).all(this.tenantId, workspaceId, String(current.user_id), String(current.provider), grantId);
1024
+ return [...new Set(previousRows.filter((row) => (String(row.client_name || '') === String(current.client_name || '')
1025
+ && String(row.product_client_id || '') === String(current.product_client_id || '')
1026
+ && canonicalRedirectUris(row.redirect_uris_json) === currentRedirectUris)).map((row) => String(row.ai_profile_id || '').trim()).filter(Boolean))].sort();
1027
+ }
976
1028
  recordMcpOAuthConnectorGrantUsage(input) {
977
1029
  const grantId = String(input.grantId || '').trim();
978
1030
  const workspaceId = String(input.workspaceId || '').trim();