@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
@@ -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
+ }
@@ -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);
@@ -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) => {
@@ -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,7 @@ 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)
226
259
  };
227
260
  }
228
261
  export function listDeletedTasksReadOnlyService(deps, workspaceId) {
@@ -240,9 +273,7 @@ export function listDeletedTasksReadOnlyService(deps, workspaceId) {
240
273
  `;
241
274
  const rows = deps.db.prepare(listSql).all(deps.tenantId, normalizedWorkspaceId);
242
275
  return {
243
- deleted: rows
244
- .map((row) => mapDeletedTaskRowService(deps, row))
245
- .filter((row) => Boolean(row))
276
+ deleted: mapDeletedTaskRowsService(deps, rows)
246
277
  .filter((row) => row.deletedAt >= cutoff || durableDeleteOwnedTaskIds.has(row.taskId))
247
278
  };
248
279
  }
@@ -8,7 +8,7 @@ export declare class DurableTaskAttachmentLinksApplyError extends Error {
8
8
  constructor(message: string, code: 'TASK_ATTACHMENT_ASSET_NOT_READY' | 'TASK_ATTACHMENT_ASSET_INVALID' | 'TASK_ATTACHMENT_LINK_INVALID', statusCode: number, assetId?: string | undefined);
9
9
  }
10
10
  export interface DurableTaskAttachmentLinksCore {
11
- getWorkspaceAssetStore(): Pick<WorkspaceAssetStore, 'get' | 'listLinksForTask' | 'adoptReadableReferenceNumber'>;
11
+ getWorkspaceAssetStore(): Pick<WorkspaceAssetStore, 'get' | 'listLinksForTask' | 'adoptReadableReferenceNumber' | 'restoreDeleted'>;
12
12
  getWorkspaceAssetDispatchReadiness(assetId: string, workspaceId: string): {
13
13
  ready: boolean;
14
14
  retryable: boolean;
@@ -16,7 +16,7 @@ export interface DurableTaskAttachmentLinksCore {
16
16
  };
17
17
  }
18
18
  export interface DurableWorkspaceTaskAttachmentLinksCore extends DurableTaskAttachmentLinksCore {
19
- getWorkspaceAssetStore(): Pick<WorkspaceAssetStore, 'get' | 'listLinksForTask' | 'listLinksForWorkspace' | 'listAllNormalizedByWorkspace' | 'adoptReadableReferenceNumber'>;
19
+ getWorkspaceAssetStore(): Pick<WorkspaceAssetStore, 'get' | 'listLinksForTask' | 'listLinksForWorkspace' | 'listAllNormalizedByWorkspace' | 'adoptReadableReferenceNumber' | 'restoreDeleted'>;
20
20
  }
21
21
  export declare function resolveTaskCardCoverAssetIdForLinks(input: {
22
22
  currentCardCoverAssetId?: string | null;
@@ -29,6 +29,7 @@ export declare function resolveTaskAttachmentLinksForDurableSync(core: DurableTa
29
29
  requireDispatchReady?: boolean;
30
30
  adoptAuthoritativeReferenceNumber?: boolean;
31
31
  ignoreAssetMetadataSnapshots?: boolean;
32
+ restoreDeletedExternalAssetForAuthoritativeSync?: boolean;
32
33
  }): AttachmentItem[];
33
34
  export declare function normalizeTaskAttachmentDescriptorsForDurableSync(core: DurableTaskAttachmentLinksCore, workspaceId: string, attachments: readonly AttachmentItem[], options?: {
34
35
  requireDispatchReady?: boolean;
@@ -53,6 +53,12 @@ function canonicalRole(asset, requested) {
53
53
  }
54
54
  return expected;
55
55
  }
56
+ function canonicalPersistedRole(asset, persisted) {
57
+ // Historical external links predate the reference-only invariant. Repair
58
+ // projections must publish the current canonical shape, while mutation
59
+ // inputs continue through canonicalRole and remain strictly validated.
60
+ return asset.storageProvider === 'external' ? 'reference' : canonicalRole(asset, persisted);
61
+ }
56
62
  function referenceFields(asset) {
57
63
  if (!asset.referenceNumber)
58
64
  return { referenceNumber: null, referenceLabel: null };
@@ -83,9 +89,13 @@ function assertReady(core, workspaceId, asset, options) {
83
89
  }
84
90
  }
85
91
  function canonicalLink(asset, link, fallbackOrder) {
92
+ // Historical task links could persist an external asset as an attachment.
93
+ // External assets are references by contract, so repair/export must emit the
94
+ // canonical role rather than reproducing an un-installable legacy link.
95
+ const role = asset.storageProvider === 'external' ? 'reference' : link.role;
86
96
  return {
87
97
  assetId: asset.assetId,
88
- role: link.role,
98
+ role,
89
99
  order: Number.isSafeInteger(link.sortOrder) && Number(link.sortOrder) >= 0
90
100
  ? Number(link.sortOrder)
91
101
  : fallbackOrder,
@@ -107,7 +117,7 @@ export function listTaskAttachmentLinksForDurableSync(core, workspaceId, taskId)
107
117
  fail(`Task attachment asset is deleted: ${link.assetId}`, 'TASK_ATTACHMENT_ASSET_INVALID', 400);
108
118
  }
109
119
  assertReady(core, workspaceId, asset);
110
- return canonicalLink(asset, link, index);
120
+ return canonicalLink(asset, { ...link, role: canonicalPersistedRole(asset, link.role) }, index);
111
121
  })
112
122
  .sort((left, right) => left.order - right.order
113
123
  || (left.assetId < right.assetId ? -1 : left.assetId > right.assetId ? 1 : 0)
@@ -139,7 +149,7 @@ export function listWorkspaceTaskAttachmentLinksForDurableSync(core, workspaceId
139
149
  fail(`Task attachment asset is deleted: ${link.assetId}`, 'TASK_ATTACHMENT_ASSET_INVALID', 400);
140
150
  }
141
151
  assertReady(core, workspaceId, asset);
142
- return canonicalLink(asset, link, index);
152
+ return canonicalLink(asset, { ...link, role: canonicalPersistedRole(asset, link.role) }, index);
143
153
  }).sort((left, right) => left.order - right.order
144
154
  || (left.assetId < right.assetId ? -1 : left.assetId > right.assetId ? 1 : 0)
145
155
  || (left.role < right.role ? -1 : left.role > right.role ? 1 : 0))
@@ -156,6 +166,14 @@ export function resolveTaskAttachmentLinksForDurableSync(core, workspaceId, link
156
166
  if (!asset) {
157
167
  fail(`Task attachment asset is unavailable because it has not reached this runtime: ${link.assetId}`, 'TASK_ATTACHMENT_ASSET_NOT_READY', 425, link.assetId);
158
168
  }
169
+ if (asset.deletedAt
170
+ && asset.storageProvider === 'external'
171
+ && options?.restoreDeletedExternalAssetForAuthoritativeSync === true) {
172
+ // External pointers do not travel through binary content hydration.
173
+ // An active authoritative link is the lifecycle proof needed to
174
+ // revive the existing local placeholder inside the apply transaction.
175
+ asset = store.restoreDeleted(asset.assetId, workspaceId, asset.updatedAt) || asset;
176
+ }
159
177
  assertReady(core, workspaceId, asset, options);
160
178
  // Replicated apply may align independently hydrated local D-/I- numbers
161
179
  // to cloud authority. Ordinary local/cloud mutation writers must never
@@ -175,8 +193,8 @@ export function resolveTaskAttachmentLinksForDurableSync(core, workspaceId, link
175
193
  const displayName = String(link.displayName
176
194
  || (options?.ignoreAssetMetadataSnapshots === true ? '' : link.originalFilename)
177
195
  || (options?.ignoreAssetMetadataSnapshots === true ? '' : link.caption)
178
- || asset.originalFilename
179
- || asset.logicalName).trim() || null;
196
+ || (options?.ignoreAssetMetadataSnapshots === true ? '' : asset.originalFilename)
197
+ || (options?.ignoreAssetMetadataSnapshots === true ? '' : asset.logicalName)).trim() || null;
180
198
  const projected = canonicalLink(asset, { role, displayName, sortOrder: index }, index);
181
199
  if (options?.ignoreAssetMetadataSnapshots !== true
182
200
  && ((link.referenceNumber ?? null) !== (projected.referenceNumber ?? null)
@@ -187,12 +205,18 @@ export function resolveTaskAttachmentLinksForDurableSync(core, workspaceId, link
187
205
  path: canonicalAssetPath(asset),
188
206
  assetId: asset.assetId,
189
207
  linkRole: role,
190
- ...(asset.kind === 'document' && asset.storageProvider !== 'external' && projected.caption !== undefined
208
+ ...(options?.ignoreAssetMetadataSnapshots !== true
209
+ && asset.kind === 'document'
210
+ && asset.storageProvider !== 'external'
211
+ && projected.caption !== undefined
191
212
  ? { caption: projected.caption } : {}),
192
213
  ...(projected.displayName !== undefined ? { displayName: projected.displayName } : {}),
193
- ...(projected.originalFilename !== undefined ? { originalFilename: projected.originalFilename } : {}),
194
- ...(projected.referenceNumber !== undefined ? { referenceNumber: projected.referenceNumber } : {}),
195
- ...(projected.referenceLabel !== undefined ? { referenceLabel: projected.referenceLabel } : {}),
214
+ ...(options?.ignoreAssetMetadataSnapshots !== true && projected.originalFilename !== undefined
215
+ ? { originalFilename: projected.originalFilename } : {}),
216
+ ...(options?.ignoreAssetMetadataSnapshots !== true && projected.referenceNumber !== undefined
217
+ ? { referenceNumber: projected.referenceNumber } : {}),
218
+ ...(options?.ignoreAssetMetadataSnapshots !== true && projected.referenceLabel !== undefined
219
+ ? { referenceLabel: projected.referenceLabel } : {}),
196
220
  };
197
221
  });
198
222
  }
@@ -95,6 +95,17 @@ export interface AuditActivityItem {
95
95
  event: TaskEvent;
96
96
  }
97
97
  export type TaskActivityItem = CommentActivityItem | AuditActivityItem;
98
+ export type TaskActivitySummaryItem = {
99
+ id: string;
100
+ type: 'comment';
101
+ timestamp: string;
102
+ comment: Pick<Comment, 'text' | 'author' | 'actor'>;
103
+ } | {
104
+ id: string;
105
+ type: 'event';
106
+ timestamp: string;
107
+ event: Pick<TaskEvent, 'action' | 'details' | 'actor' | 'actorType' | 'actorProfile'>;
108
+ };
98
109
  export interface EntityAuditActivityItem {
99
110
  id: string;
100
111
  type: 'event';
@@ -153,6 +164,7 @@ export interface TaskItem {
153
164
  taskEvents?: TaskEvent[];
154
165
  attachments?: AttachmentItem[];
155
166
  cardCoverAssetId?: string | null;
167
+ cardCoverAttachment?: Exclude<AttachmentItem, string> | null;
156
168
  /** @deprecated Use status='done'. Safe to remove after v0.4.x migration gate. */
157
169
  completed?: boolean;
158
170
  /** @deprecated Use status='cancelled'. Safe to remove after v0.4.x migration gate. */
@@ -171,7 +183,27 @@ export interface TaskItem {
171
183
  workstreamOrder?: number | null;
172
184
  workstreamId?: string | null;
173
185
  checklistItems?: ChecklistItem[];
174
- }
186
+ /** True when this object came from a collection read and omits the full task graph. */
187
+ isSummary?: boolean;
188
+ commentCount?: number;
189
+ checklistCount?: number;
190
+ completedChecklistCount?: number;
191
+ attachmentCount?: number;
192
+ relationshipCount?: number;
193
+ /** Compact newest comment or audit event carried by lightweight collection reads. */
194
+ latestActivity?: TaskActivitySummaryItem | null;
195
+ detailRevision?: string;
196
+ }
197
+ export type TaskSummary = Omit<TaskItem, 'description' | 'comments' | 'activity' | 'taskEvents' | 'attachments' | 'checklistItems'> & {
198
+ isSummary: true;
199
+ commentCount: number;
200
+ checklistCount: number;
201
+ completedChecklistCount: number;
202
+ attachmentCount: number;
203
+ relationshipCount: number;
204
+ latestActivity: TaskActivitySummaryItem | null;
205
+ detailRevision: string;
206
+ };
175
207
  export type AttachmentItem = string | {
176
208
  path: string;
177
209
  fsPath?: string;
@@ -351,12 +383,23 @@ export interface TaskFile {
351
383
  manualComplexityEnabled?: boolean;
352
384
  checklistDropdownEnabled?: boolean;
353
385
  showTaskCardStatusLabel?: boolean;
386
+ showTaskCardDescription?: boolean;
387
+ showTaskCardAttachments?: boolean;
388
+ showTaskCardChecklistProgress?: boolean;
389
+ showTaskCardLatestActivity?: boolean;
390
+ tintTaskCardActivityBackground?: boolean;
354
391
  taxonomies?: any[];
355
392
  };
356
393
  }
357
394
  export interface ArchiveFile {
358
395
  archived: TaskItem[];
359
396
  }
397
+ export interface TaskSummaryFile {
398
+ tasks: TaskSummary[];
399
+ }
400
+ export interface ArchiveSummaryFile {
401
+ archived: TaskSummary[];
402
+ }
360
403
  export interface BulkCreateTaskError {
361
404
  index: number;
362
405
  message: string;
@@ -997,6 +1040,7 @@ export interface TaskforceSyncCapable {
997
1040
  repairLegacyBinding?: boolean;
998
1041
  }): AiProfileRecord | null;
999
1042
  resolveWorkspaceActor(workspaceId: string, actorRef: unknown): TaskActor | null;
1043
+ resolveWorkspaceActors(workspaceId: string, actorRefs: unknown[]): Map<string, TaskActor | null>;
1000
1044
  }
1001
1045
  export type TaskRelationshipInputType = 'depends_on' | 'blocks' | 'related_to' | 'supersedes';
1002
1046
  export type TaskRelationshipKind = 'dependency' | 'related' | 'supersedes';
@@ -1146,6 +1190,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1146
1190
  requireDispatchReady?: boolean;
1147
1191
  adoptAuthoritativeReferenceNumber?: boolean;
1148
1192
  ignoreAssetMetadataSnapshots?: boolean;
1193
+ restoreDeletedExternalAssetForAuthoritativeSync?: boolean;
1149
1194
  }): AttachmentItem[];
1150
1195
  private normalizeEntitlementState;
1151
1196
  private upsertCanonicalEntitlementRecord;
@@ -1445,6 +1490,16 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1445
1490
  private getSyncReconciliationServiceDeps;
1446
1491
  private getWorkspaceLifecycleServiceDeps;
1447
1492
  listTasks(workspaceId?: string): TaskFile;
1493
+ private listTaskSummaries;
1494
+ listActiveTaskSummaries(workspaceId?: string): TaskSummaryFile;
1495
+ listArchiveSummaries(workspaceId?: string): ArchiveSummaryFile;
1496
+ getTaskDetailRevision(id: string, workspaceId?: string): string | null;
1497
+ getTaskDetailSnapshot(id: string, workspaceId?: string): TaskItem | null;
1498
+ updateTaskIfDetailRevisionMatches(id: string, expectedDetailRevision: string, updates: Partial<TaskItem>, workspaceId?: string, options?: {
1499
+ actorRef?: string;
1500
+ saveSource?: 'manual' | 'autosave';
1501
+ }): TaskItem | null;
1502
+ assertTaskDetailRevisionForUpdate(id: string, expectedDetailRevision: string, workspaceId?: string): void;
1448
1503
  /**
1449
1504
  * Filter-pushdown listing used by read-only surfaces that don't need the
1450
1505
  * full task graph (MCP list_tasks, CLI summaries). Avoids the per-task
@@ -1480,7 +1535,10 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1480
1535
  title: string;
1481
1536
  status: TaskStatus;
1482
1537
  }>;
1483
- searchTaskSummaries(query: string, scope?: 'all' | 'active' | 'archive', workspaceId?: string): Array<Record<string, unknown>>;
1538
+ searchTaskSummaries(query: string, scope?: 'all' | 'active' | 'archive', workspaceId?: string, options?: {
1539
+ limit?: number;
1540
+ offset?: number;
1541
+ }): Array<Record<string, unknown>>;
1484
1542
  getDataVersion(): number;
1485
1543
  getDatabaseProvider(): DatabaseProvider;
1486
1544
  isDatabaseHealthy(): boolean;
@@ -1805,7 +1863,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
1805
1863
  sessionId: string;
1806
1864
  }): boolean;
1807
1865
  private getAiProfileServiceDeps;
1866
+ private buildUnresolvedWorkspaceActor;
1808
1867
  resolveWorkspaceActor(workspaceId: string, actorRef: unknown): TaskActor | null;
1868
+ resolveWorkspaceActors(workspaceId: string, actorRefs: unknown[]): Map<string, TaskActor | null>;
1809
1869
  listAssignableActors(workspaceId: string): TaskActor[];
1810
1870
  addAdminAuditLog(input: {
1811
1871
  action: string;