@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
@@ -16,7 +16,7 @@ import { createRoutes, matchRoute } from './routes.js';
16
16
  import { formatApiErrorResponse } from './routes/primitives.js';
17
17
  import { logSlowBootstrapRoute } from './routes/shared.js';
18
18
  import { applyCorsHeaders, getAllowedOriginsFromEnv } from './cors.js';
19
- import { applyPrefixedRateLimitHeaders, applyRateLimitHeaders, InMemoryRateLimiter, resolveRateLimitKey } from './rateLimit.js';
19
+ import { applyPrefixedRateLimitHeaders, applyRateLimitHeaders, InMemoryRateLimiter, isWorkspaceSyncFeedRead, resolveRateLimitKey, } from './rateLimit.js';
20
20
  import { isRequestAuthorized, resolveAuthConfigFromEnv, resolveRequestAccess } from './auth.js';
21
21
  import { resolveAuthorizedWorkspaceAccess, resolveDisabledAccessError, } from './authorizedWorkspaceAccess.js';
22
22
  export { resolveAuthorizedWorkspaceAccess, resolveDisabledAccessError, isWorkspaceAccessRecoveryRoute, } from './authorizedWorkspaceAccess.js';
@@ -178,6 +178,12 @@ export const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = 'x-taskforce-origin-proto';
178
178
  export const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = 'x-taskforce-origin-base-url';
179
179
  export const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = 'x-taskforce-origin-runtime';
180
180
  export const TASKFORCE_PROXY_CLOUD_ENVIRONMENT_HEADER = 'x-taskforce-cloud-environment';
181
+ export function sanitizeQaRunIdHeader(raw) {
182
+ const normalized = String(Array.isArray(raw) ? raw[0] : raw || '')
183
+ .trim()
184
+ .replace(/[^A-Za-z0-9._-]+/g, '-');
185
+ return normalized.slice(0, 128) || '-';
186
+ }
181
187
  function shouldLogRefreshDiagnostic(pathname) {
182
188
  return REFRESH_DIAGNOSTIC_PATH_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(prefix));
183
189
  }
@@ -658,10 +664,13 @@ export function createStandaloneServer(options) {
658
664
  : (runtimeMode === 'cloud');
659
665
  const rateLimitLimit = Number.parseInt(process.env.TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS || '', 10) || 120;
660
666
  const rateLimitWindowMs = Number.parseInt(process.env.TASKFORCE_API_RATE_LIMIT_WINDOW_MS || '', 10) || 60_000;
667
+ const syncFeedRateLimit = Number.parseInt(process.env.TASKFORCE_SYNC_FEED_RATE_LIMIT_MAX_REQUESTS || '', 10) || 600;
668
+ const syncFeedRateLimitWindowMs = Number.parseInt(process.env.TASKFORCE_SYNC_FEED_RATE_LIMIT_WINDOW_MS || '', 10) || 60_000;
661
669
  const webhookRateLimitMaxRequests = Number.parseInt(process.env.TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS || '', 10) || 600;
662
670
  const webhookRateLimitWindowMs = Number.parseInt(process.env.TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS || '', 10) || 60_000;
663
671
  const maxPayloadBytes = Number.parseInt(process.env.TASKFORCE_MAX_PAYLOAD_BYTES || '', 10) || 1_048_576;
664
672
  const rateLimiter = new InMemoryRateLimiter(rateLimitLimit, rateLimitWindowMs);
673
+ const syncFeedRateLimiter = new InMemoryRateLimiter(syncFeedRateLimit, syncFeedRateLimitWindowMs);
665
674
  const webhookRateLimiter = new InMemoryRateLimiter(webhookRateLimitMaxRequests, webhookRateLimitWindowMs);
666
675
  const resolveAuthRateLimitSettings = () => {
667
676
  const auth = core.getGlobalSettings().auth || {};
@@ -765,7 +774,8 @@ export function createStandaloneServer(options) {
765
774
  const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || '-';
766
775
  const workspaceId = String(req.headers['x-taskforce-workspace-id'] || '').trim() || '-';
767
776
  const userId = String(req.headers['x-taskforce-user-id'] || '').trim() || '-';
768
- console.info(`[Taskforce RefreshDiag] method=${method} path=${pathname} status=${statusCode} durationMs=${durationMs} requestId=${requestId} workspaceId=${workspaceId} userId=${userId}`);
777
+ const qaRunId = sanitizeQaRunIdHeader(req.headers['x-taskforce-qa-run-id']);
778
+ console.info(`[Taskforce RefreshDiag] method=${method} path=${pathname} status=${statusCode} durationMs=${durationMs} requestId=${requestId} workspaceId=${workspaceId} userId=${userId} qaRunId=${qaRunId}`);
769
779
  };
770
780
  // Resolve static files directory at runtime
771
781
  // Standalone UI source now lives in apps/app and builds to dist/ui.
@@ -992,7 +1002,9 @@ export function createStandaloneServer(options) {
992
1002
  });
993
1003
  const rateLimitResult = isBillingWebhook
994
1004
  ? webhookRateLimiter.check(key)
995
- : rateLimiter.check(key);
1005
+ : isWorkspaceSyncFeedRead(req.method, url.pathname)
1006
+ ? syncFeedRateLimiter.check(key)
1007
+ : rateLimiter.check(key);
996
1008
  if (isBillingWebhook) {
997
1009
  applyPrefixedRateLimitHeaders(res, rateLimitResult, 'X-Webhook-RateLimit');
998
1010
  }
@@ -1,4 +1,5 @@
1
1
  import { type LocalServiceCloudTarget } from '../config/localServiceCloudTarget.js';
2
+ import { type LocalServiceRuntimeContract } from '../config/localServiceRuntimeContract.js';
2
3
  import { type WindowsAclCommandRunner } from './localServiceWindowsAcl.js';
3
4
  export interface LocalServiceDescriptor {
4
5
  schemaVersion: 1;
@@ -11,6 +12,7 @@ export interface LocalServiceDescriptor {
11
12
  bootstrapCapability: string;
12
13
  publishedAt: string;
13
14
  cloudTarget?: LocalServiceCloudTarget | null;
15
+ runtimeContract?: LocalServiceRuntimeContract | null;
14
16
  }
15
17
  export interface LocalServiceLeaseIdentityInput {
16
18
  projectRoot: string;
@@ -30,6 +32,7 @@ export interface LocalServiceDescriptorPublication {
30
32
  protocolVersion: string;
31
33
  appVersion: string;
32
34
  cloudTarget: LocalServiceCloudTarget;
35
+ runtimeContract: LocalServiceRuntimeContract;
33
36
  }
34
37
  export interface LocalServiceLease {
35
38
  readonly identity: LocalServiceLeaseIdentity;
@@ -4,6 +4,7 @@ import * as net from 'net';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
6
  import { parseLocalServiceCloudTarget, } from '../config/localServiceCloudTarget.js';
7
+ import { parseLocalServiceRuntimeContract, } from '../config/localServiceRuntimeContract.js';
7
8
  import { resolveLocalSyncProcessLockIdentity } from './localDatabaseIdentity.js';
8
9
  import { ensureWindowsUserOnlyDirectoryAcl, } from './localServiceWindowsAcl.js';
9
10
  const DESCRIPTOR_SCHEMA_VERSION = 1;
@@ -168,8 +169,14 @@ function parseDescriptor(value, ownershipKey) {
168
169
  if (value.cloudTarget != null && !cloudTarget) {
169
170
  throw new Error('Local service descriptor cloud target is invalid.');
170
171
  }
172
+ const runtimeContract = value.runtimeContract == null
173
+ ? null
174
+ : parseLocalServiceRuntimeContract(value.runtimeContract);
175
+ if (value.runtimeContract != null && !runtimeContract) {
176
+ throw new Error('Local service descriptor runtime contract is invalid.');
177
+ }
171
178
  assertLoopbackEndpoint(value.endpoint);
172
- return { ...value, cloudTarget };
179
+ return { ...value, cloudTarget, runtimeContract };
173
180
  }
174
181
  function readOptionalDescriptor(identity) {
175
182
  try {
@@ -371,6 +378,7 @@ export async function acquireLocalServiceLease(input, dependencies = {}) {
371
378
  bootstrapCapability,
372
379
  publishedAt: (dependencies.now?.() || new Date()).toISOString(),
373
380
  cloudTarget: publication.cloudTarget,
381
+ runtimeContract: publication.runtimeContract,
374
382
  };
375
383
  publishJsonFileAtomically(identity.descriptorPath, descriptor, platform);
376
384
  return descriptor;
@@ -15,6 +15,7 @@ import { createWorkspaceSyncEngineServerRunnerOperations } from '../sync/engine/
15
15
  import { workspaceSyncTransferEvidenceEvent } from '../sync/engine/workspaceSyncTransferEvidence.js';
16
16
  import { createWorkspaceSyncEngineServerPushCandidateProvider, createWorkspaceSyncEngineServerSnapshotReader, } from '../sync/engine/workspaceSyncEngineServerSnapshotReader.js';
17
17
  import { readWorkspaceSyncLocalOwnershipSnapshot } from '../sync/v3/workspaceSyncLocalOwnershipSnapshot.js';
18
+ import { resolveRequestedCombinedFeedCoverage } from '../sync/v3/combinedFeedCapability.js';
18
19
  import { resolveWorkspaceSyncV3CombinedWireContract, } from '../sync/v3/workspaceSyncV3CombinedWireContract.js';
19
20
  import { LocalWorkspaceSyncServerOutboxDispatcher, } from '../sync/v3/localWorkspaceSyncServerOutboxDispatcher.js';
20
21
  import { readWorkspaceSyncV3LocalOutboxStatus, } from '../sync/v3/workspaceSyncV3LocalOutboxStatus.js';
@@ -950,15 +951,21 @@ export class LocalWorkspaceSyncServerRuntime {
950
951
  if (combined.owner === 'v3' && !combinedContract) {
951
952
  throw Object.assign(new Error('The persisted combined-feed ownership contract is invalid.'), { code: 'SYNC_RUNNER_V3_OWNERSHIP_INVALID' });
952
953
  }
954
+ const desiredCombinedCoverage = combined.owner === 'v3'
955
+ ? resolveRequestedCombinedFeedCoverage(this.options.env || process.env)
956
+ : null;
957
+ const desiredCombinedContract = desiredCombinedCoverage
958
+ ? resolveWorkspaceSyncV3CombinedWireContract(desiredCombinedCoverage)
959
+ : null;
953
960
  return {
954
961
  initiativeFeedActivationEnabled: ownership.initiativeProjection.owner === 'v3',
955
962
  initiativeProjection: ownership.initiativeProjection,
956
963
  combinedFeedActivationEnabled: combined.owner === 'v3',
957
964
  combinedProjection: combined,
958
- ...(combinedContract
965
+ ...(combinedContract && desiredCombinedContract
959
966
  ? {
960
- combinedFeedCoverage: combinedContract.coverage,
961
- combinedFeedExcludedProjections: combinedContract.excludedProjections,
967
+ combinedFeedCoverage: desiredCombinedContract.coverage,
968
+ combinedFeedExcludedProjections: desiredCombinedContract.excludedProjections,
962
969
  }
963
970
  : {}),
964
971
  };
@@ -26,5 +26,6 @@ export declare function resolveRateLimitKey(params: {
26
26
  sessionId?: string | null;
27
27
  userId?: string | null;
28
28
  }): string;
29
+ export declare function isWorkspaceSyncFeedRead(method: string | undefined, pathname: string): boolean;
29
30
  export declare function applyRateLimitHeaders(res: ServerResponse, result: RateLimitResult): void;
30
31
  export declare function applyPrefixedRateLimitHeaders(res: ServerResponse, result: RateLimitResult, prefix: string): void;
@@ -97,6 +97,10 @@ export function resolveRateLimitKey(params) {
97
97
  }
98
98
  return `ip:${ip}:${method}:${route}`;
99
99
  }
100
+ export function isWorkspaceSyncFeedRead(method, pathname) {
101
+ return String(method || 'GET').trim().toUpperCase() === 'GET'
102
+ && pathname === '/api/taskforce/sync/v3/workspace/feed';
103
+ }
100
104
  export function applyRateLimitHeaders(res, result) {
101
105
  res.setHeader('X-RateLimit-Limit', String(result.limit));
102
106
  res.setHeader('X-RateLimit-Remaining', String(result.remaining));
@@ -68,23 +68,34 @@ export function createRealtimeSyncWsManager(core, authConfig, logger = console,
68
68
  }
69
69
  connections.delete(ws);
70
70
  };
71
- const canSubscribe = (state, workspaceId) => {
71
+ const resolveSubscriptionAccess = (state, workspaceId) => {
72
72
  if (!workspaceId)
73
- return false;
73
+ return 'forbidden';
74
74
  if (!authConfig.enabled || !authConfig.requiredForApi)
75
- return true;
75
+ return 'allowed';
76
76
  if (!state.authenticated || !state.userId || state.userId === 'anonymous')
77
- return false;
78
- const resolved = core.resolveUserAccess(state.userId, workspaceId, state.role);
79
- return Boolean(resolved?.workspaceId === workspaceId);
77
+ return 'forbidden';
78
+ try {
79
+ const resolved = core.resolveUserAccess(state.userId, workspaceId, state.role);
80
+ return resolved?.workspaceId === workspaceId ? 'allowed' : 'forbidden';
81
+ }
82
+ catch (error) {
83
+ logger.warn(`[Taskforce] WebSocket access check unavailable workspace=${workspaceId}: ${String(error instanceof Error ? error.message : error)}`);
84
+ return 'unavailable';
85
+ }
80
86
  };
81
87
  const closeUnauthorized = (ws, reason) => {
82
88
  logger.warn(`[Taskforce] WebSocket rejected: ${reason}`);
83
89
  ws.close(1008, reason);
84
90
  };
85
91
  const subscribeWorkspace = (ws, state, workspaceId) => {
86
- if (!canSubscribe(state, workspaceId)) {
87
- send(ws, { type: 'taskforce:subscription-error', workspaceId, reason: 'forbidden' });
92
+ const accessDecision = resolveSubscriptionAccess(state, workspaceId);
93
+ if (accessDecision !== 'allowed') {
94
+ send(ws, {
95
+ type: 'taskforce:subscription-error',
96
+ workspaceId,
97
+ reason: accessDecision === 'unavailable' ? 'temporarily-unavailable' : 'forbidden'
98
+ });
88
99
  return;
89
100
  }
90
101
  state.workspaceIds.add(workspaceId);
@@ -132,8 +143,13 @@ export function createRealtimeSyncWsManager(core, authConfig, logger = console,
132
143
  send(ws, { type: 'taskforce:replay-error', reason: 'workspace-required' });
133
144
  return;
134
145
  }
135
- if (!canSubscribe(state, workspaceId)) {
136
- send(ws, { type: 'taskforce:replay-error', workspaceId, reason: 'forbidden' });
146
+ const accessDecision = resolveSubscriptionAccess(state, workspaceId);
147
+ if (accessDecision !== 'allowed') {
148
+ send(ws, {
149
+ type: 'taskforce:replay-error',
150
+ workspaceId,
151
+ reason: accessDecision === 'unavailable' ? 'temporarily-unavailable' : 'forbidden'
152
+ });
137
153
  return;
138
154
  }
139
155
  state.workspaceIds.add(workspaceId);
@@ -191,8 +207,16 @@ export function createRealtimeSyncWsManager(core, authConfig, logger = console,
191
207
  connections.set(ws, state);
192
208
  logger.log(`[Taskforce] WebSocket client connected user=${state.userId}`);
193
209
  const initialWorkspaceId = parseWorkspaceId(access.workspaceId);
194
- if (initialWorkspaceId && canSubscribe(state, initialWorkspaceId)) {
195
- state.workspaceIds.add(initialWorkspaceId);
210
+ if (initialWorkspaceId) {
211
+ const accessDecision = resolveSubscriptionAccess(state, initialWorkspaceId);
212
+ if (accessDecision === 'unavailable') {
213
+ ws.close(1013, 'access-unavailable');
214
+ removeConnection(ws);
215
+ return;
216
+ }
217
+ if (accessDecision === 'allowed') {
218
+ state.workspaceIds.add(initialWorkspaceId);
219
+ }
196
220
  }
197
221
  ws.on('message', (rawData) => {
198
222
  const payload = parseJson(rawData);
@@ -1953,6 +1953,7 @@ export function registerAdminRoutes(deps) {
1953
1953
  workspaceAssetStore,
1954
1954
  publishedContentResolver,
1955
1955
  repairMissingAssetLinks: true,
1956
+ repairMissingContentFiles: body?.repairMissingContentFiles === true,
1956
1957
  aiProfiles: typeof core.listAiProfiles === 'function'
1957
1958
  ? core.listAiProfiles(workspaceId, { includeArchived: true }) || []
1958
1959
  : [],
@@ -1,4 +1,5 @@
1
- import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../../runtime/appGates.js';
1
+ import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, } from '../../runtime/appGates.js';
2
+ import { resolveNodeAppGateAccess } from '../../runtime/nodeAppGates.js';
2
3
  import { AgentRoleDefinitionValidationError } from '../../shared/agentRoleDefinition.js';
3
4
  import { AgentRoleConflictError, AgentRoleNotFoundError, AgentRoleRetiredError, AgentRoleStore, } from '../../storage/agentRoleStore.js';
4
5
  import { parseJsonBody } from './primitives.js';
@@ -128,7 +129,7 @@ export function registerAgentRolesRoutes(deps) {
128
129
  const guard = (req, res) => {
129
130
  if (!ensureAppAccess(req, res))
130
131
  return false;
131
- const gate = resolveAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
132
+ const gate = resolveNodeAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
132
133
  if (gate.allowed)
133
134
  return true;
134
135
  writeJson(res, 403, {
@@ -1,4 +1,5 @@
1
- import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../../runtime/appGates.js';
1
+ import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, } from '../../runtime/appGates.js';
2
+ import { resolveNodeAppGateAccess } from '../../runtime/nodeAppGates.js';
2
3
  import { compileTaskforceAgentSkillPrompt } from '../../shared/taskforceAgentSkill.js';
3
4
  import { TaskforceAgentStore } from '../../storage/taskforceAgentStore.js';
4
5
  import { TaskforceAgentSkillConflictError, TaskforceAgentSkillStore, } from '../../storage/taskforceAgentSkillStore.js';
@@ -73,7 +74,7 @@ export function registerAgentSkillsRoutes(deps) {
73
74
  const ensureFeatureAccess = (req, res) => {
74
75
  if (!ensureAppAccess(req, res))
75
76
  return false;
76
- const gate = resolveAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
77
+ const gate = resolveNodeAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
77
78
  if (gate.allowed)
78
79
  return true;
79
80
  writeJson(res, 403, {
@@ -11,10 +11,12 @@
11
11
  * POST /api/taskforce/agents/model-gateway/test
12
12
  * PATCH /api/taskforce/agents/:agentId/conversations/:conversationId
13
13
  */
14
+ import { createHash } from 'node:crypto';
14
15
  import { assignedAiProfileColor, normalizeAiProfileColor } from '../../shared/aiProfileColors.js';
15
16
  import { BedrockAnthropicMessagesModelGateway } from '../../services/bedrockAnthropicMessagesModelGateway.js';
16
17
  import { BedrockMantleModelGateway } from '../../services/bedrockMantleModelGateway.js';
17
18
  import { BedrockModelGateway } from '../../services/bedrockModelGateway.js';
19
+ import { CodexAppServerModelGateway } from '../../services/codexAppServerModelGateway.js';
18
20
  import { saveAiProfileAvatarAssets } from '../aiProfileAvatarAssets.js';
19
21
  import { ModelGatewayConfigurationError, normalizePersistedModelGatewayMessages } from '../../services/modelGateway.js';
20
22
  import { BedrockAvatarImageProvider, buildTaskforceAgentAvatarPrompt, } from '../../services/taskforceAgentAvatarImageProvider.js';
@@ -25,10 +27,11 @@ import { TaskforceAgentSkillStore } from '../../storage/taskforceAgentSkillStore
25
27
  import { WorkflowStore } from '../../storage/workflowStore.js';
26
28
  import { WorkspaceSyncDurabilityStore } from '../../sync/v3/syncDurabilityStore.js';
27
29
  import { projectLegacyTaskforceAgentResponse } from '../../compat/taskforceAgentDefinitionCompat.js';
28
- import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../../runtime/appGates.js';
30
+ import { TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, } from '../../runtime/appGates.js';
31
+ import { resolveNodeAppGateAccess } from '../../runtime/nodeAppGates.js';
29
32
  import { DEFAULT_TASKFORCE_AGENT_MODEL_KEY, DEFAULT_TASKFORCE_AGENT_PROVIDER_KEY, getTaskforceAgentModel, isTaskforceAgentModelVisible, labelForTaskforceAgentModel, labelForTaskforceAgentProvider, normalizeTaskforceAgentModelKey, normalizeTaskforceAgentModelSelection, normalizeTaskforceAgentProviderKey, } from '../../shared/taskforceAgentModels.js';
30
33
  import { buildTaskforceAgentRunContext } from '../../shared/taskforceAgentRunContext.js';
31
- import { inspectAgentDefinition } from '../../shared/taskforceAgentDefinition.js';
34
+ import { inspectAgentDefinition, serializeCanonicalJson } from '../../shared/taskforceAgentDefinition.js';
32
35
  import { buildResolvedTaskforceAgentSystemPrompt, resolveTaskforceAgentRuntimeConfiguration, TaskforceAgentRuntimeResolutionError, } from '../../shared/taskforceAgentRuntimeResolution.js';
33
36
  import { serializeTaskforceAgentExecutionSnapshot } from '../../shared/taskforceAgentExecutionSnapshot.js';
34
37
  import { getTaskReferenceLabel } from '../../utils/taskReferences.js';
@@ -113,6 +116,11 @@ function normalizeMessages(value, fallbackPrompt) {
113
116
  return { error: 'Prompt must be 4,000 characters or less.' };
114
117
  return { messages: [{ role: 'user', content: prompt }] };
115
118
  }
119
+ function buildManagedAgentExecutionRequestId(input) {
120
+ return createHash('sha256')
121
+ .update(serializeCanonicalJson(input))
122
+ .digest('hex');
123
+ }
116
124
  function toGatewayMessages(messages) {
117
125
  return messages.map((message) => ({
118
126
  role: message.role,
@@ -122,7 +130,13 @@ function toGatewayMessages(messages) {
122
130
  function requestsWorkflowStepCompletion(messages) {
123
131
  const latestUserMessage = [...messages].reverse().find((message) => message.role === 'user');
124
132
  const content = String(latestUserMessage?.content || '').trim().toLowerCase();
125
- return /^(?:please\s+|(?:can|could|would)\s+you\s+)?(?:perform\s+and\s+complete|complete|finish)\s+(?:your|the|this)\s+(?:current\s+)?workflow\s+step[.!?]*$/.test(content);
133
+ return /^(?:(?:hey|hi|hello)\b[^,!?]{0,80},?\s+)?(?:please\s+|(?:can|could|would)\s+you\s+)?(?:perform\s+and\s+complete|complete|finish)\s+(?:your|the|this)\s+(?:current\s+)?workflow\s+step(?:\s+(?:on|for)\s+(?:task\s+)?(?:t-\d+|[a-z0-9][a-z0-9-]{0,119}))?[.!?]*$/.test(content);
134
+ }
135
+ function extractLatestExplicitTaskReference(messages) {
136
+ const latestUserMessage = [...messages].reverse().find((message) => message.role === 'user');
137
+ const matches = String(latestUserMessage?.content || '').match(/\bT-\d+\b/gi) || [];
138
+ const references = [...new Set(matches.map((match) => match.toUpperCase()))];
139
+ return references.length === 1 ? references[0] : null;
126
140
  }
127
141
  function normalizeTaskId(value) {
128
142
  const taskId = String(value || '').trim();
@@ -144,6 +158,18 @@ function resolveTaskContextTask(core, workspaceId, taskId) {
144
158
  return core.getTask(taskId, workspaceId)
145
159
  || (typeof core.resolveTaskIdentifier === 'function' ? core.resolveTaskIdentifier(taskId, workspaceId) : null);
146
160
  }
161
+ function workflowToolCallsTargetSameTask(core, workspaceId, left, right) {
162
+ const resolveCanonicalId = (value) => {
163
+ const taskId = normalizeTaskId(value);
164
+ if (!taskId)
165
+ return null;
166
+ const task = resolveTaskContextTask(core, workspaceId, taskId);
167
+ return normalizeTaskId(task?.id)?.toLowerCase() || taskId.toLowerCase();
168
+ };
169
+ const leftTaskId = resolveCanonicalId(left);
170
+ const rightTaskId = resolveCanonicalId(right);
171
+ return Boolean(leftTaskId && rightTaskId && leftTaskId === rightTaskId);
172
+ }
147
173
  function resolveTaskScopeContext(core, workspaceId, input) {
148
174
  const taskId = String(input.taskId || '').trim();
149
175
  if (!taskId)
@@ -244,6 +270,12 @@ function createDefaultModelGateway(selection) {
244
270
  }
245
271
  const source = selection?.modelSource || model?.source || 'bedrock';
246
272
  const runtime = selection?.runtime || model?.runtime || 'bedrock-runtime';
273
+ if (source === 'subscription_dev' && runtime === 'subscription-dev') {
274
+ if (selection?.connectionScope !== 'local_dev') {
275
+ throw new ModelGatewayConfigurationError('Subscription-dev models require local_dev connection scope.', 'MODEL_SOURCE_UNAVAILABLE');
276
+ }
277
+ return new CodexAppServerModelGateway();
278
+ }
247
279
  if (source !== 'bedrock') {
248
280
  throw new ModelGatewayConfigurationError('Selected model source is not configured yet.', 'MODEL_SOURCE_NOT_IMPLEMENTED');
249
281
  }
@@ -693,7 +725,7 @@ export function registerAgentsRoutes(deps) {
693
725
  const ensureTaskforceAgentsAccess = (req, res) => {
694
726
  if (!ensureAppAccess(req, res))
695
727
  return false;
696
- const gate = resolveAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
728
+ const gate = resolveNodeAppGateAccess(TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY);
697
729
  if (gate.allowed)
698
730
  return true;
699
731
  writeJson(res, 403, {
@@ -1149,9 +1181,30 @@ export function registerAgentsRoutes(deps) {
1149
1181
  }
1150
1182
  }
1151
1183
  const savedConversationTaskId = savedConversation?.taskId ? normalizeTaskId(savedConversation.taskId) : null;
1152
- const taskId = hasTaskId ? bodyTaskId : savedConversationTaskId;
1153
- const taskReference = taskId ? normalizeTaskContextValue(body?.taskReference) : '';
1154
- const taskTitle = taskId ? normalizeTaskContextValue(body?.taskTitle) : '';
1184
+ const explicitTaskReference = extractLatestExplicitTaskReference(normalizedMessages.messages);
1185
+ const explicitTask = explicitTaskReference
1186
+ ? resolveTaskContextTask(core, workspaceId, explicitTaskReference)
1187
+ : null;
1188
+ if (explicitTaskReference
1189
+ && !explicitTask
1190
+ && requestsWorkflowStepCompletion(normalizedMessages.messages)) {
1191
+ writeJson(res, 404, {
1192
+ success: false,
1193
+ error: `Task ${explicitTaskReference} was not found.`,
1194
+ code: 'TASK_NOT_FOUND',
1195
+ });
1196
+ return;
1197
+ }
1198
+ const taskId = normalizeTaskId(explicitTask?.id)
1199
+ || (explicitTaskReference ? null : hasTaskId ? bodyTaskId : savedConversationTaskId);
1200
+ const taskReference = taskId
1201
+ ? normalizeTaskContextValue(getTaskReferenceLabel(explicitTask)
1202
+ || (explicitTask ? explicitTaskReference : null)
1203
+ || body?.taskReference)
1204
+ : '';
1205
+ const taskTitle = taskId
1206
+ ? normalizeTaskContextValue(explicitTask?.title || body?.taskTitle)
1207
+ : '';
1155
1208
  if (resolvedRuntimeConfiguration) {
1156
1209
  const definition = resolvedRuntimeConfiguration.definition;
1157
1210
  const draftDiffers = ((body?.providerKey !== undefined
@@ -1272,6 +1325,13 @@ export function registerAgentsRoutes(deps) {
1272
1325
  allowedMutationTaskId: taskId ?? null,
1273
1326
  readOnly: !savedAgent,
1274
1327
  toolPolicy: resolvedRuntimeConfiguration?.definition.toolPolicy,
1328
+ executionRequestId: buildManagedAgentExecutionRequestId({
1329
+ workspaceId,
1330
+ agentId: savedAgent?.id || agentId || null,
1331
+ conversationId,
1332
+ taskId: taskId ?? null,
1333
+ messages: normalizedMessages.messages,
1334
+ }),
1275
1335
  system: scopedAgentSystem,
1276
1336
  });
1277
1337
  const executionSnapshot = savedAgent && resolvedRuntimeConfiguration
@@ -1304,21 +1364,40 @@ export function registerAgentsRoutes(deps) {
1304
1364
  && mcpBridge?.availableToolNames.includes('complete_workflow_step')
1305
1365
  && requestsWorkflowStepCompletion(gatewayInput.messages));
1306
1366
  let workflowCompletionCorrectionSent = false;
1367
+ let reservedWorkflowCompletionUsed = false;
1307
1368
  while (mcpBridge) {
1308
1369
  if (requestedToolCall) {
1309
- if (mcpToolResults.length >= MAX_MCP_TOOL_ITERATIONS)
1370
+ const activeToolCall = requestedToolCall;
1371
+ const workflowStepAlreadyCompleted = mcpToolResults.some((toolResult) => (toolResult.name === 'complete_workflow_step' && toolResult.ok));
1372
+ const workflowStepLoaded = activeToolCall.name === 'complete_workflow_step'
1373
+ && mcpToolResults.some((toolResult) => (toolResult.name === 'get_task_workflow'
1374
+ && toolResult.ok
1375
+ && workflowToolCallsTargetSameTask(core, workspaceId, toolResult.arguments?.taskId, activeToolCall.arguments?.taskId)));
1376
+ const canUseReservedWorkflowCompletion = Boolean(workflowCompletionRequested
1377
+ && !workflowStepAlreadyCompleted
1378
+ && !reservedWorkflowCompletionUsed
1379
+ && activeToolCall.name === 'complete_workflow_step'
1380
+ && workflowStepLoaded
1381
+ && unresolvedFailedMutations.size === 0);
1382
+ if (mcpToolResults.length >= MAX_MCP_TOOL_ITERATIONS
1383
+ && !canUseReservedWorkflowCompletion)
1310
1384
  break;
1311
- const blocksWorkflowCompletion = requestedToolCall.name === 'complete_workflow_step'
1312
- && unresolvedFailedMutations.size > 0;
1385
+ if (mcpToolResults.length >= MAX_MCP_TOOL_ITERATIONS) {
1386
+ reservedWorkflowCompletionUsed = true;
1387
+ }
1388
+ const blocksWorkflowCompletion = activeToolCall.name === 'complete_workflow_step'
1389
+ && (!workflowStepLoaded || unresolvedFailedMutations.size > 0);
1313
1390
  const unresolvedMutationNames = [...new Set(unresolvedFailedMutations.values())];
1314
1391
  const toolResult = blocksWorkflowCompletion
1315
1392
  ? {
1316
- name: requestedToolCall.name,
1317
- arguments: requestedToolCall.arguments,
1393
+ name: activeToolCall.name,
1394
+ arguments: activeToolCall.arguments,
1318
1395
  ok: false,
1319
- text: `Taskforce refused workflow completion because these earlier changes failed: ${unresolvedMutationNames.join(', ')}. Retry the failed changes successfully before completing the step.`,
1396
+ text: !workflowStepLoaded
1397
+ ? 'Taskforce refused workflow completion because the current workflow step was not loaded successfully. Call get_task_workflow first, perform its instructions, then retry completion.'
1398
+ : `Taskforce refused workflow completion because these earlier changes failed: ${unresolvedMutationNames.join(', ')}. Retry the failed changes successfully before completing the step.`,
1320
1399
  }
1321
- : await mcpBridge.executeToolCall(requestedToolCall);
1400
+ : await mcpBridge.executeToolCall(activeToolCall);
1322
1401
  mcpToolResults.push(toolResult);
1323
1402
  if (isTaskforceAgentMutationToolName(toolResult.name)
1324
1403
  && toolResult.name !== 'complete_workflow_step') {
@@ -1349,6 +1428,17 @@ export function registerAgentsRoutes(deps) {
1349
1428
  throw error;
1350
1429
  }
1351
1430
  requestedToolCall = mcpBridge.parseToolCall(result.text);
1431
+ if (toolResult.name === 'complete_workflow_step'
1432
+ && toolResult.ok
1433
+ && requestedToolCall
1434
+ && mcpToolResults.length >= MAX_MCP_TOOL_ITERATIONS) {
1435
+ result = {
1436
+ ...result,
1437
+ text: 'Workflow step completed successfully.',
1438
+ };
1439
+ requestedToolCall = null;
1440
+ break;
1441
+ }
1352
1442
  continue;
1353
1443
  }
1354
1444
  const workflowStepCompleted = mcpToolResults.some((toolResult) => (toolResult.name === 'complete_workflow_step' && toolResult.ok));
@@ -1358,7 +1448,7 @@ export function registerAgentsRoutes(deps) {
1358
1448
  role: 'user',
1359
1449
  content: unresolvedFailedMutations.size > 0
1360
1450
  ? `The workflow step is not complete because these Taskforce changes failed: ${[...new Set(unresolvedFailedMutations.values())].join(', ')}. Retry those exact changes successfully before calling complete_workflow_step. If they cannot succeed, respond exactly WORKFLOW_STEP_INCOMPLETE: followed by the reason.`
1361
- : 'The workflow step is not authoritatively complete. If all required work succeeded, call complete_workflow_step now. Otherwise respond exactly WORKFLOW_STEP_INCOMPLETE: followed by the reason. Do not claim completion without a successful tool result.',
1451
+ : 'The workflow step is not authoritatively complete. Call get_task_workflow first if it has not succeeded in this response, perform the returned step instructions, then call complete_workflow_step. Otherwise respond exactly WORKFLOW_STEP_INCOMPLETE: followed by the reason. Do not claim completion without successful tool results.',
1362
1452
  });
1363
1453
  result = await gateway.generate({
1364
1454
  ...gatewayInput,
@@ -1370,7 +1460,19 @@ export function registerAgentsRoutes(deps) {
1370
1460
  break;
1371
1461
  }
1372
1462
  if (requestedToolCall) {
1373
- throw new Error('Taskforce tool call limit reached before the agent returned a final response.');
1463
+ mcpToolResults.push({
1464
+ name: requestedToolCall.name,
1465
+ arguments: requestedToolCall.arguments,
1466
+ ok: false,
1467
+ text: 'Taskforce did not execute this call because the per-message tool limit was reached.',
1468
+ });
1469
+ result = {
1470
+ ...result,
1471
+ text: workflowCompletionRequested
1472
+ ? 'WORKFLOW_STEP_INCOMPLETE: Taskforce reached the per-message tool limit before finishing the workflow step. Review Tool Activity and retry.'
1473
+ : 'Taskforce reached the per-message tool limit. Review Tool Activity and retry.',
1474
+ };
1475
+ requestedToolCall = null;
1374
1476
  }
1375
1477
  if (workflowCompletionRequested && !mcpToolResults.some((toolResult) => (toolResult.name === 'complete_workflow_step' && toolResult.ok))) {
1376
1478
  const incompletePrefix = 'WORKFLOW_STEP_INCOMPLETE:';
@@ -11,7 +11,8 @@
11
11
  * DELETE /api/taskforce/annotated-attachments/sessions/:id
12
12
  * GET /api/taskforce/annotated-attachments/sessions/:id/payload
13
13
  */
14
- import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../../runtime/appGates.js';
14
+ import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, } from '../../runtime/appGates.js';
15
+ import { resolveNodeAppGateAccess } from '../../runtime/nodeAppGates.js';
15
16
  import { parseJsonBody } from './primitives.js';
16
17
  import { resolveErrorStatusCode } from './shared.js';
17
18
  import { getImageReferenceLabel, parseImageReference } from '../../utils/imageReferences.js';
@@ -37,7 +38,7 @@ export function registerAnnotatedAttachmentRoutes(deps) {
37
38
  const ensureAnnotatedAttachmentAccess = (req, res, workspaceId) => {
38
39
  if (!ensureAppAccess(req, res))
39
40
  return false;
40
- const gate = resolveAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
41
+ const gate = resolveNodeAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
41
42
  if (gate.allowed)
42
43
  return true;
43
44
  writeJson(res, 403, {
@@ -60,6 +60,7 @@ import { buildGithubAuthUrl, exchangeGithubCode } from '../auth/providers/github
60
60
  import { hasValidSession, createClearSessionCookie, createSessionTokenCookie, resolveRequestAccess, resolveSessionCookieAttributesForRequest, } from '../auth.js';
61
61
  import { sendEmail } from '../email.js';
62
62
  import { resolveDeploymentConfig } from '../../config/deployment.js';
63
+ import { resolveNodeAppGateAccessMap } from '../../runtime/nodeAppGates.js';
63
64
  import { resolveTaskMetadataActivationCapability } from '../../sync/v3/taskMetadataActivation.js';
64
65
  import { resolveTaskCreateActivationCapability } from '../../sync/v3/taskCreateActivation.js';
65
66
  import { resolveTaskStatusActivationCapability } from '../../sync/v3/taskStatusActivation.js';
@@ -401,7 +402,7 @@ export function registerAuthRoutes(deps) {
401
402
  : 'Reset your Taskforce password';
402
403
  const text = kind === 'verify-email'
403
404
  ? `Welcome to Taskforce.\n\nYou're almost in. Enter this verification token on the verification screen to activate your account:\n\n${token}\n\nPrefer the faster route? Open the link below to verify your email automatically:\n${link}\n\nIf you didn't create a Taskforce account, you can safely ignore this email.`
404
- : `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
405
+ : `Enter this password reset token on the reset screen:\n\n${token}\n\nOr reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
405
406
  const result = await sendEmail({
406
407
  to: email,
407
408
  subject,
@@ -470,6 +471,7 @@ export function registerAuthRoutes(deps) {
470
471
  authSource: runtimeContract.authSource,
471
472
  workspaceMode: runtimeContract.workspaceMode,
472
473
  workspaceSwitchingEnabled: runtimeContract.workspaceSwitchingEnabled,
474
+ appGates: resolveNodeAppGateAccessMap(process.env),
473
475
  syncV3
474
476
  }
475
477
  }));
@@ -10,8 +10,9 @@ export declare function createTaskHttpDurableRouters(input: {
10
10
  actorRef: string;
11
11
  root: {
12
12
  enabled: boolean;
13
+ hasCommittedOperation(): boolean;
13
14
  create(candidate: unknown): import("../../core/Taskforce.js").TaskItem;
14
- update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed"): import("../../core/Taskforce.js").TaskItem;
15
+ update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed", expectedDetailRevision?: string): import("../../core/Taskforce.js").TaskItem;
15
16
  runBatch<T>(routeInput: string, request: unknown, apply: (context: {
16
17
  runChild<R>(mutation: () => R): R;
17
18
  deferPostCommitNotification(notify: () => void): void;
@@ -41,6 +42,7 @@ export declare function createTaskHttpDurableRouters(input: {
41
42
  replace(args: {
42
43
  taskId: string;
43
44
  items: readonly Partial<import("../../core/Taskforce.js").ChecklistItem>[];
45
+ expectedDetailRevision?: string;
44
46
  }): {
45
47
  state: "pending" | "committed";
46
48
  route: "local-outbox" | "cloud-acceptance";
@@ -54,6 +56,7 @@ export declare function createTaskHttpDurableRouters(input: {
54
56
  taskId: string;
55
57
  links: readonly import("../../sync/v3/workspaceSyncV3NestedTaskCodecs.js").WorkspaceSyncV3TaskAttachmentLink[];
56
58
  cardCoverAssetId?: string | null;
59
+ expectedDetailRevision?: string;
57
60
  }): {
58
61
  state: "pending" | "committed";
59
62
  route: "local-outbox" | "cloud-acceptance";
@@ -66,6 +69,7 @@ export declare function createTaskHttpDurableRouters(input: {
66
69
  replace(args: {
67
70
  taskId: string;
68
71
  items: readonly Partial<import("../../core/Taskforce.js").ChecklistItem>[];
72
+ expectedDetailRevision?: string;
69
73
  }): {
70
74
  state: "pending" | "committed";
71
75
  route: "local-outbox" | "cloud-acceptance";
@@ -79,6 +83,7 @@ export declare function createTaskHttpDurableRouters(input: {
79
83
  taskId: string;
80
84
  links: readonly import("../../sync/v3/workspaceSyncV3NestedTaskCodecs.js").WorkspaceSyncV3TaskAttachmentLink[];
81
85
  cardCoverAssetId?: string | null;
86
+ expectedDetailRevision?: string;
82
87
  }): {
83
88
  state: "pending" | "committed";
84
89
  route: "local-outbox" | "cloud-acceptance";
@@ -134,6 +139,7 @@ export declare function createTaskHttpDurableRouters(input: {
134
139
  }>, options?: {
135
140
  saveSource?: "manual" | "autosave";
136
141
  atomicItemFailures?: boolean;
142
+ expectedDetailRevision?: string;
137
143
  }): {
138
144
  preview: import("../../core/Taskforce.js").BulkUpdateFieldsPreview;
139
145
  results: import("../../core/Taskforce.js").TaskItem[];
@@ -149,8 +155,9 @@ export declare function createTaskHttpDurableRouters(input: {
149
155
  };
150
156
  createRootRouter: (operationId?: string, deferPostCommitNotification?: (notify: () => void) => void) => {
151
157
  enabled: boolean;
158
+ hasCommittedOperation(): boolean;
152
159
  create(candidate: unknown): import("../../core/Taskforce.js").TaskItem;
153
- update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed"): import("../../core/Taskforce.js").TaskItem;
160
+ update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed", expectedDetailRevision?: string): import("../../core/Taskforce.js").TaskItem;
154
161
  runBatch<T>(routeInput: string, request: unknown, apply: (context: {
155
162
  runChild<R>(mutation: () => R): R;
156
163
  deferPostCommitNotification(notify: () => void): void;