@taskforcehq/taskforce 0.3.306 → 0.3.308

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 (232) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.d.ts +20 -15
  38. package/dist/components/views/PlanComparisonPage.js +221 -82
  39. package/dist/components/views/PlanComparisonPage.test.js +268 -14
  40. package/dist/components/views/PlansPage.js +16 -10
  41. package/dist/components/views/PlansPage.test.js +4 -2
  42. package/dist/components/views/StandaloneLayout.js +256 -69
  43. package/dist/components/views/WidgetView.js +10 -8
  44. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  45. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  46. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  47. package/dist/config/envSchema.js +1 -0
  48. package/dist/core/AiProfileService.d.ts +7 -0
  49. package/dist/core/AiProfileService.js +460 -35
  50. package/dist/core/AiProfiles.test.js +648 -2
  51. package/dist/core/AttachmentLinkService.d.ts +3 -0
  52. package/dist/core/AttachmentLinkService.js +58 -13
  53. package/dist/core/GlobalSettingsService.js +30 -0
  54. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  55. package/dist/core/GlobalSettingsService.test.js +45 -0
  56. package/dist/core/McpTokenService.d.ts +12 -0
  57. package/dist/core/McpTokenService.js +44 -1
  58. package/dist/core/PlanEntitlementService.d.ts +20 -0
  59. package/dist/core/PlanEntitlementService.js +215 -19
  60. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  61. package/dist/core/PlanningEntities.test.js +366 -4
  62. package/dist/core/TaskActivityService.d.ts +28 -1
  63. package/dist/core/TaskActivityService.js +227 -46
  64. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  65. package/dist/core/TaskActivitySync.test.js +90 -0
  66. package/dist/core/TaskAuditTimeline.test.js +96 -0
  67. package/dist/core/Taskforce.d.ts +204 -26
  68. package/dist/core/Taskforce.ids.test.js +58 -0
  69. package/dist/core/Taskforce.js +781 -71
  70. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  71. package/dist/core/types.d.ts +10 -0
  72. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  73. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  74. package/dist/hooks/bootstrapTimeouts.js +8 -0
  75. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  76. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  77. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  78. package/dist/hooks/sync/orchestratorShared.js +61 -1
  79. package/dist/hooks/sync/recovery.js +8 -0
  80. package/dist/hooks/sync/recovery.test.js +5 -5
  81. package/dist/hooks/sync/transfers.d.ts +2 -1
  82. package/dist/hooks/sync/transfers.js +36 -9
  83. package/dist/hooks/sync/transfers.test.js +534 -2
  84. package/dist/hooks/useFilterSort.js +13 -13
  85. package/dist/hooks/useFilterSort.test.js +87 -1
  86. package/dist/hooks/useSyncOrchestrator.js +50 -5
  87. package/dist/hooks/useTaskforce.d.ts +85 -50
  88. package/dist/hooks/useTaskforce.js +44 -14
  89. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  90. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  91. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  92. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  93. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  95. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  96. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  97. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  98. package/dist/mcp/clientIntegrations.d.ts +1 -0
  99. package/dist/mcp/clientIntegrations.js +25 -0
  100. package/dist/mcp/clientIntegrations.test.js +14 -3
  101. package/dist/mcp/clientProfileDefaults.js +3 -3
  102. package/dist/mcp/collaborationRegistrar.js +7 -7
  103. package/dist/mcp/documentAssetRegistrar.js +113 -7
  104. package/dist/mcp/runtime.d.ts +1 -0
  105. package/dist/mcp/runtime.js +969 -122
  106. package/dist/mcp/runtime.test.js +1083 -11
  107. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  108. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  109. package/dist/mcp/toolRegistry.d.ts +56 -0
  110. package/dist/mcp/toolRegistry.js +8 -0
  111. package/dist/mcp/toolRegistry.test.js +4 -0
  112. package/dist/migrations/taskSchemaMigrations.js +58 -15
  113. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  114. package/dist/server/aiProfileAvatarAssets.js +319 -0
  115. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  116. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  117. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  118. package/dist/server/index.js +4 -0
  119. package/dist/server/index.test.js +6 -1
  120. package/dist/server/routes/admin.js +328 -9
  121. package/dist/server/routes/annotatedAttachments.js +51 -0
  122. package/dist/server/routes/auth.d.ts +11 -0
  123. package/dist/server/routes/auth.js +189 -27
  124. package/dist/server/routes/authSupport.d.ts +5 -0
  125. package/dist/server/routes/authSupport.js +13 -1
  126. package/dist/server/routes/billing.d.ts +1 -0
  127. package/dist/server/routes/billing.js +116 -63
  128. package/dist/server/routes/billing.test.js +162 -11
  129. package/dist/server/routes/documents.js +10 -0
  130. package/dist/server/routes/shared.js +0 -10
  131. package/dist/server/routes/sync.integration.test.js +221 -0
  132. package/dist/server/routes/sync.js +13 -3
  133. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  134. package/dist/server/routes/tasks.js +12 -9
  135. package/dist/server/routes.js +87 -8
  136. package/dist/server/routes.test.js +972 -23
  137. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  138. package/dist/shared/aiProfileSurfaceType.js +10 -8
  139. package/dist/storage/objectStorage.d.ts +1 -0
  140. package/dist/storage/objectStorage.js +2 -1
  141. package/dist/storage/objectStorageClient.d.ts +1 -0
  142. package/dist/storage/objectStorageClient.js +41 -8
  143. package/dist/storage/objectStorageClient.test.js +26 -1
  144. package/dist/storage/objectStorageConfig.js +3 -1
  145. package/dist/storage/objectStorageConfig.test.js +13 -1
  146. package/dist/sync/collaborationSyncPayload.js +2 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -3
  149. package/dist/sync/collaborationSyncState.js +19 -12
  150. package/dist/sync/contentSyncPayload.d.ts +5 -1
  151. package/dist/sync/contentSyncPayload.js +9 -0
  152. package/dist/sync/contentSyncPayload.test.js +39 -0
  153. package/dist/sync/contentSyncState.d.ts +5 -1
  154. package/dist/sync/contentSyncState.js +231 -37
  155. package/dist/sync/planningSyncPayload.js +5 -0
  156. package/dist/sync/planningSyncPayload.test.js +24 -0
  157. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  158. package/dist/sync/syncApplyHandlers.js +89 -31
  159. package/dist/sync/syncApplyHandlers.test.js +184 -4
  160. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  161. package/dist/sync/syncResourceAdapters.js +2 -0
  162. package/dist/sync/syncService.d.ts +1 -0
  163. package/dist/sync/syncService.js +13 -1
  164. package/dist/sync/syncService.test.js +39 -0
  165. package/dist/sync/workspacePullFeed.d.ts +4 -0
  166. package/dist/sync/workspacePullFeed.js +129 -70
  167. package/dist/sync/workspacePullFeed.test.js +49 -0
  168. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  169. package/dist/sync/workspaceSyncModel.js +159 -45
  170. package/dist/sync/workspaceSyncModel.test.js +269 -3
  171. package/dist/types.d.ts +33 -2
  172. package/dist/ui/agent-logos/ChatGPT.png +0 -0
  173. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  174. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  175. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  176. package/dist/ui/agent-logos/codex.jpeg +0 -0
  177. package/dist/ui/agent-logos/cursor.png +0 -0
  178. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  179. package/dist/ui/agent-logos/windsurf.png +0 -0
  180. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
  181. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  182. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
  183. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
  184. package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
  185. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  186. package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
  187. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  188. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
  189. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  190. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  191. package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
  192. package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
  193. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
  194. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  195. package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
  196. package/dist/ui/assets/index-MAHKvFqp.js +4 -0
  197. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  198. package/dist/ui/index.html +3 -3
  199. package/dist/ui/og-image.png +0 -0
  200. package/dist/ui/product/agent manager.png +0 -0
  201. package/dist/ui/product/agent_manager_02.png +0 -0
  202. package/dist/utils/aiProfileEvents.d.ts +7 -0
  203. package/dist/utils/aiProfileEvents.js +6 -0
  204. package/dist/utils/assignees.d.ts +6 -0
  205. package/dist/utils/assignees.js +8 -1
  206. package/dist/utils/assignees.test.d.ts +1 -0
  207. package/dist/utils/assignees.test.js +25 -0
  208. package/dist/utils/avatarDisplay.d.ts +1 -0
  209. package/dist/utils/avatarDisplay.js +15 -0
  210. package/dist/utils/contextFiles.d.ts +3 -1
  211. package/dist/utils/contextFiles.js +2 -1
  212. package/dist/utils/taskActivity.d.ts +7 -1
  213. package/dist/utils/taskActivity.js +63 -1
  214. package/dist/utils/taskActivity.test.js +23 -1
  215. package/dist/utils/taskSearch.d.ts +22 -0
  216. package/dist/utils/taskSearch.js +87 -0
  217. package/dist/utils/taskSearch.test.d.ts +1 -0
  218. package/dist/utils/taskSearch.test.js +69 -0
  219. package/dist/utils/taskUtils.js +10 -3
  220. package/dist/utils/taskUtils.test.d.ts +1 -0
  221. package/dist/utils/taskUtils.test.js +19 -0
  222. package/package.json +1 -1
  223. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  224. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  225. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  226. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  227. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  228. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  229. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  230. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  231. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  232. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -60,6 +60,8 @@ import { getRouteTimingNow, logSlowBootstrapRoute, requestBootstrapTraceId, requ
60
60
  import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
61
61
  import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
62
62
  import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
63
+ // === SECTION: Module dependency interface ===
64
+ const UI_STATE_MAX_JSON_BODY_BYTES = 50 * 1024 * 1024;
63
65
  function createUiStateRequestId() {
64
66
  return `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
65
67
  }
@@ -237,8 +239,9 @@ export function registerTaskRoutes(deps) {
237
239
  const requestId = createUiStateRequestId();
238
240
  const startedAt = Date.now();
239
241
  attachUiStateDebugHeaders(res, requestId);
240
- const body = await parseJsonBody(req);
242
+ let body = {};
241
243
  try {
244
+ body = await parseJsonBody(req, UI_STATE_MAX_JSON_BODY_BYTES);
242
245
  const requestedWorkspaceId = String(body?.workspaceId || '').trim();
243
246
  let workspaceId = requestWorkspaceId(req);
244
247
  if (requestedWorkspaceId && requestRuntimeMode(req) === 'local') {
@@ -555,7 +558,7 @@ export function registerTaskRoutes(deps) {
555
558
  const body = await parseJsonBody(req);
556
559
  try {
557
560
  const workspaceId = requestWorkspaceId(req);
558
- const initiative = core.createInitiative(body || {}, workspaceId);
561
+ const initiative = core.createInitiative(body || {}, workspaceId, { actorRef: 'user' });
559
562
  res.writeHead(201, { 'Content-Type': 'application/json' });
560
563
  res.end(JSON.stringify(initiative));
561
564
  }
@@ -587,7 +590,7 @@ export function registerTaskRoutes(deps) {
587
590
  const body = await parseJsonBody(req);
588
591
  try {
589
592
  const workspaceId = requestWorkspaceId(req);
590
- const updated = core.updateInitiative(params.id, body || {}, workspaceId);
593
+ const updated = core.updateInitiative(params.id, body || {}, workspaceId, { actorRef: 'user' });
591
594
  res.writeHead(200, { 'Content-Type': 'application/json' });
592
595
  res.end(JSON.stringify(updated));
593
596
  }
@@ -603,7 +606,7 @@ export function registerTaskRoutes(deps) {
603
606
  return;
604
607
  try {
605
608
  const workspaceId = requestWorkspaceId(req);
606
- const archived = core.archiveInitiative(params.id, workspaceId);
609
+ const archived = core.archiveInitiative(params.id, workspaceId, { actorRef: 'user' });
607
610
  res.writeHead(200, { 'Content-Type': 'application/json' });
608
611
  res.end(JSON.stringify(archived));
609
612
  }
@@ -619,7 +622,7 @@ export function registerTaskRoutes(deps) {
619
622
  return;
620
623
  try {
621
624
  const workspaceId = requestWorkspaceId(req);
622
- const restored = core.unarchiveInitiative(params.id, workspaceId);
625
+ const restored = core.unarchiveInitiative(params.id, workspaceId, { actorRef: 'user' });
623
626
  res.writeHead(200, { 'Content-Type': 'application/json' });
624
627
  res.end(JSON.stringify(restored));
625
628
  }
@@ -646,7 +649,7 @@ export function registerTaskRoutes(deps) {
646
649
  const body = await parseJsonBody(req);
647
650
  try {
648
651
  const workspaceId = requestWorkspaceId(req);
649
- const workstream = core.createWorkstream(body || {}, workspaceId);
652
+ const workstream = core.createWorkstream(body || {}, workspaceId, { actorRef: 'user' });
650
653
  res.writeHead(201, { 'Content-Type': 'application/json' });
651
654
  res.end(JSON.stringify(workstream));
652
655
  }
@@ -678,7 +681,7 @@ export function registerTaskRoutes(deps) {
678
681
  const body = await parseJsonBody(req);
679
682
  try {
680
683
  const workspaceId = requestWorkspaceId(req);
681
- const updated = core.updateWorkstream(params.id, body || {}, workspaceId);
684
+ const updated = core.updateWorkstream(params.id, body || {}, workspaceId, { actorRef: 'user' });
682
685
  res.writeHead(200, { 'Content-Type': 'application/json' });
683
686
  res.end(JSON.stringify(updated));
684
687
  }
@@ -694,7 +697,7 @@ export function registerTaskRoutes(deps) {
694
697
  return;
695
698
  try {
696
699
  const workspaceId = requestWorkspaceId(req);
697
- const archived = core.archiveWorkstream(params.id, workspaceId);
700
+ const archived = core.archiveWorkstream(params.id, workspaceId, { actorRef: 'user' });
698
701
  res.writeHead(200, { 'Content-Type': 'application/json' });
699
702
  res.end(JSON.stringify(archived));
700
703
  }
@@ -710,7 +713,7 @@ export function registerTaskRoutes(deps) {
710
713
  return;
711
714
  try {
712
715
  const workspaceId = requestWorkspaceId(req);
713
- const restored = core.unarchiveWorkstream(params.id, workspaceId);
716
+ const restored = core.unarchiveWorkstream(params.id, workspaceId, { actorRef: 'user' });
714
717
  res.writeHead(200, { 'Content-Type': 'application/json' });
715
718
  res.end(JSON.stringify(restored));
716
719
  }
@@ -111,13 +111,14 @@ function resolveErrorStatusCode(error, fallback = 400) {
111
111
  }
112
112
  return fallback;
113
113
  }
114
- function writeMcpHttpError(res, statusCode, message) {
114
+ function writeMcpHttpError(res, statusCode, message, data) {
115
115
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
116
116
  res.end(JSON.stringify({
117
117
  jsonrpc: '2.0',
118
118
  error: {
119
119
  code: -32000,
120
120
  message,
121
+ ...(data ? { data } : {}),
121
122
  },
122
123
  id: null,
123
124
  }));
@@ -522,7 +523,10 @@ export function createRoutes(core, context = {}) {
522
523
  const primaryLink = canonicalAsset?.assetId
523
524
  ? workspaceAssetStore?.listLinksForAsset(canonicalAsset.assetId, inferWorkspaceIdFromStoragePath(normalizedStorageKey)).find((entry) => !entry.deletedAt && (!opts?.taskId || entry.taskId === opts.taskId))
524
525
  : null;
526
+ const isExplicitTaskAttachment = typeof opts?.taskId === 'string' && opts.taskId.trim().length > 0;
525
527
  const timestamp = String(opts?.timestamp
528
+ || primaryLink?.updatedAt
529
+ || (isExplicitTaskAttachment ? new Date().toISOString() : null)
526
530
  || canonicalAsset?.updatedAt
527
531
  || canonicalAsset?.createdAt
528
532
  || new Date().toISOString()).trim() || new Date().toISOString();
@@ -567,6 +571,45 @@ export function createRoutes(core, context = {}) {
567
571
  taskId
568
572
  };
569
573
  };
574
+ const taskHasAttachmentForAsset = (task, asset) => {
575
+ if (!task || !Array.isArray(task.attachments))
576
+ return false;
577
+ const canonicalDocumentPath = `/api/taskforce/documents/${encodeURIComponent(asset.assetId)}/content`;
578
+ const contextPath = `/api/taskforce/context/${encodeURIComponent(asset.storageKey)}`;
579
+ return task.attachments.some((attachment) => {
580
+ if (typeof attachment === 'string') {
581
+ return attachment === canonicalDocumentPath
582
+ || attachment === contextPath
583
+ || attachment === asset.storageKey;
584
+ }
585
+ if (!attachment || typeof attachment !== 'object')
586
+ return false;
587
+ return attachment.assetId === asset.assetId
588
+ || attachment.fsPath === asset.storageKey
589
+ || attachment.path === canonicalDocumentPath
590
+ || attachment.path === contextPath;
591
+ });
592
+ };
593
+ const recordTaskAttachmentActivity = (input) => {
594
+ const afterTask = core.getTask(input.taskId, input.workspaceId);
595
+ if (!taskHasAttachmentForAsset(afterTask, input.asset))
596
+ return;
597
+ core.recordTaskEvent({
598
+ taskId: input.taskId,
599
+ workspaceId: input.workspaceId,
600
+ action: 'task-attachments-changed',
601
+ actor: 'user',
602
+ createdAt: input.createdAt || new Date().toISOString(),
603
+ details: {
604
+ changes: {
605
+ attachments: {
606
+ from: input.beforeTask.attachments || [],
607
+ to: afterTask?.attachments || [],
608
+ },
609
+ },
610
+ },
611
+ });
612
+ };
570
613
  const sanitizeDownloadFilename = (nameRaw, fallback = 'download') => {
571
614
  const normalized = String(nameRaw || '').trim()
572
615
  .replace(/[\\/:*?"<>|]/g, '-')
@@ -660,6 +703,8 @@ export function createRoutes(core, context = {}) {
660
703
  purgeAfter: null
661
704
  });
662
705
  const taskId = String(params.taskId || inferTaskIdFromStoragePath(params.relativeToStorage) || '').trim();
706
+ const taskBeforeLink = taskId ? core.getTask(taskId, workspaceId) : null;
707
+ const wasAttachedBefore = taskHasAttachmentForAsset(taskBeforeLink, asset);
663
708
  if (taskId) {
664
709
  workspaceAssetStore.upsertLink({
665
710
  workspaceId,
@@ -669,6 +714,15 @@ export function createRoutes(core, context = {}) {
669
714
  displayName: null,
670
715
  deletedAt: null
671
716
  });
717
+ if (taskBeforeLink && !wasAttachedBefore) {
718
+ recordTaskAttachmentActivity({
719
+ taskId,
720
+ workspaceId,
721
+ asset,
722
+ beforeTask: taskBeforeLink,
723
+ createdAt: asset.updatedAt,
724
+ });
725
+ }
672
726
  }
673
727
  core.notifyDocumentMetadataMutation(workspaceId, [params.relativeToStorage], 'upsert');
674
728
  }
@@ -1146,6 +1200,8 @@ export function createRoutes(core, context = {}) {
1146
1200
  authenticatedViaOAuth: oauthAuth || null,
1147
1201
  userId: null,
1148
1202
  ...bootstrap,
1203
+ aiProfileToken: bootstrap.aiProfileToken || oauthAuth?.aiProfileToken || null,
1204
+ aiProfileName: bootstrap.aiProfileName || oauthAuth?.aiProfileName || null,
1149
1205
  };
1150
1206
  }
1151
1207
  core.addMcpAuditLog({
@@ -1385,6 +1441,7 @@ export function createRoutes(core, context = {}) {
1385
1441
  return;
1386
1442
  }
1387
1443
  const authScopes = auth.authenticatedViaToken?.scopes || auth.authenticatedViaOAuth?.scopes || [];
1444
+ const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID();
1388
1445
  const deniedToolName = findDeniedCloudMcpToolName(body, authScopes);
1389
1446
  if (deniedToolName) {
1390
1447
  core.addMcpAuditLog({
@@ -1394,7 +1451,7 @@ export function createRoutes(core, context = {}) {
1394
1451
  tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
1395
1452
  toolName: deniedToolName,
1396
1453
  details: {
1397
- requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
1454
+ requestId,
1398
1455
  ip: req.socket?.remoteAddress || null,
1399
1456
  userAgent: requestHeader(req, 'user-agent', '') || null,
1400
1457
  outcome: 'deny',
@@ -1408,11 +1465,13 @@ export function createRoutes(core, context = {}) {
1408
1465
  if (!applyCloudMcpWriteRateLimit(req, res, auth, body)) {
1409
1466
  return;
1410
1467
  }
1468
+ let runtime = null;
1411
1469
  try {
1412
- const runtime = createTaskforceMcpServer({
1470
+ runtime = createTaskforceMcpServer({
1413
1471
  projectRoot: core.getPaths().projectRoot,
1414
1472
  tenantId: core.getTenantId(),
1415
1473
  workspaceId: auth.workspaceId,
1474
+ core: core,
1416
1475
  serverName: 'taskforce-cloud',
1417
1476
  logStartup: false,
1418
1477
  runtimeMode: 'cloud',
@@ -1420,7 +1479,7 @@ export function createRoutes(core, context = {}) {
1420
1479
  req,
1421
1480
  auth,
1422
1481
  requestHeader,
1423
- requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
1482
+ requestId,
1424
1483
  authScopes,
1425
1484
  }),
1426
1485
  });
@@ -1435,13 +1494,29 @@ export function createRoutes(core, context = {}) {
1435
1494
  }
1436
1495
  await transport.handleRequest(req, res, body);
1437
1496
  recordCloudMcpCredentialUsage(req, auth.authenticatedViaToken, auth.authenticatedViaOAuth);
1438
- await transport.close().catch(() => undefined);
1439
- await runtime.server.close().catch(() => undefined);
1440
1497
  }
1441
1498
  catch (error) {
1442
- console.error('[Taskforce MCP] Cloud transport request failed:', error);
1499
+ console.error('[Taskforce MCP] Cloud transport request failed:', {
1500
+ requestId,
1501
+ kind: 'tool_transport_failure',
1502
+ message: error instanceof Error ? error.message : String(error),
1503
+ });
1443
1504
  if (!res.headersSent) {
1444
- writeMcpHttpError(res, 500, 'Internal server error');
1505
+ writeMcpHttpError(res, 500, 'MCP transport failure.', {
1506
+ kind: 'tool_transport_failure',
1507
+ retryable: true,
1508
+ requestId,
1509
+ });
1510
+ }
1511
+ }
1512
+ finally {
1513
+ if (runtime) {
1514
+ try {
1515
+ await runtime.server.close();
1516
+ }
1517
+ catch (closeError) {
1518
+ console.warn('[Taskforce MCP] Cloud transport cleanup failed:', closeError);
1519
+ }
1445
1520
  }
1446
1521
  }
1447
1522
  };
@@ -1531,6 +1606,10 @@ export function createRoutes(core, context = {}) {
1531
1606
  resolveAdminRequestActor,
1532
1607
  resolveEffectiveObjectStorageConfig,
1533
1608
  getObjectStorageClient,
1609
+ workspaceAssetStore,
1610
+ ensureWithinDir,
1611
+ markAssetDeleted,
1612
+ appendStorageTelemetry,
1534
1613
  });
1535
1614
  registerWorkspaceRoutes({
1536
1615
  addRoute,