@taskforcehq/taskforce 0.3.327 → 0.3.328

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 (187) hide show
  1. package/dist/Taskforce.module.css +24 -53
  2. package/dist/components/activity/EntityActivityTimeline.js +15 -5
  3. package/dist/components/context/AttachmentPreviewHost.js +1 -1
  4. package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
  5. package/dist/components/context/ContextAttachmentManager.js +116 -20
  6. package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
  7. package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
  8. package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
  9. package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
  10. package/dist/components/features/planning/PlanningModule.js +26 -9
  11. package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
  12. package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
  13. package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
  14. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  15. package/dist/components/task/TaskActionHeader.js +2 -2
  16. package/dist/components/task/TaskCard.js +37 -4
  17. package/dist/components/task/TaskContextUpload.d.ts +3 -0
  18. package/dist/components/task/TaskContextUpload.js +2 -2
  19. package/dist/components/task/TaskForm.d.ts +4 -0
  20. package/dist/components/task/TaskForm.js +32 -7
  21. package/dist/components/task/TaskImageReviews.js +16 -16
  22. package/dist/components/task/TaskStatusActions.js +11 -2
  23. package/dist/components/task/TaskWorkstreamPicker.js +2 -1
  24. package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
  25. package/dist/components/ui/DocumentTypeIcon.js +8 -0
  26. package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
  27. package/dist/components/ui/TaxonomyDropdown.js +30 -3
  28. package/dist/components/views/StandaloneLayout.js +62 -9
  29. package/dist/components/views/WidgetView.js +8 -5
  30. package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
  31. package/dist/components/views/panels/PlanningDrawer.js +48 -44
  32. package/dist/config/envSchema.js +1 -0
  33. package/dist/config/localServiceCloudTarget.d.ts +10 -0
  34. package/dist/config/localServiceCloudTarget.js +76 -0
  35. package/dist/core/AiProfileService.d.ts +1 -0
  36. package/dist/core/AiProfileService.js +2 -1
  37. package/dist/core/AttachmentLinkService.js +7 -4
  38. package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
  39. package/dist/core/DeletedTaskLifecycleService.js +21 -0
  40. package/dist/core/McpTokenService.d.ts +4 -0
  41. package/dist/core/McpTokenService.js +52 -0
  42. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
  43. package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
  44. package/dist/core/Taskforce.d.ts +63 -1
  45. package/dist/core/Taskforce.js +558 -73
  46. package/dist/core/types.d.ts +2 -1
  47. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
  48. package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
  49. package/dist/documentReviews/service.d.ts +4 -0
  50. package/dist/documentReviews/service.js +141 -46
  51. package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
  52. package/dist/hooks/tasks/useTaskFormActions.js +33 -7
  53. package/dist/hooks/useTaskforce.d.ts +59 -50
  54. package/dist/hooks/useTaskforce.js +103 -1
  55. package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
  56. package/dist/mcp/canonicalAssetHelpers.js +39 -5
  57. package/dist/mcp/collaborationRegistrar.js +15 -13
  58. package/dist/mcp/documentAssetRegistrar.js +16 -9
  59. package/dist/mcp/localServiceProxy.js +4 -1
  60. package/dist/mcp/runtime.d.ts +16 -0
  61. package/dist/mcp/runtime.js +566 -171
  62. package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
  63. package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
  64. package/dist/mcp/taskPlanningRegistrar.js +42 -34
  65. package/dist/mcp/toolRegistry.js +2 -2
  66. package/dist/migrations/taskSchemaMigrations.js +35 -0
  67. package/dist/runtime/appGateDefinitions.d.ts +2 -2
  68. package/dist/runtime/appGateDefinitions.js +2 -2
  69. package/dist/server/appAccess.d.ts +12 -0
  70. package/dist/server/appAccess.js +21 -0
  71. package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
  72. package/dist/server/authorizedWorkspaceAccess.js +40 -0
  73. package/dist/server/cloudCollectionRead.d.ts +25 -0
  74. package/dist/server/cloudCollectionRead.js +120 -0
  75. package/dist/server/cloudCollectionReadPool.d.ts +52 -0
  76. package/dist/server/cloudCollectionReadPool.js +140 -0
  77. package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
  78. package/dist/server/cloudCollectionReadWorker.js +36 -0
  79. package/dist/server/index.d.ts +3 -11
  80. package/dist/server/index.js +53 -40
  81. package/dist/server/localServiceLease.d.ts +3 -0
  82. package/dist/server/localServiceLease.js +9 -1
  83. package/dist/server/routes/documents.d.ts +5 -2
  84. package/dist/server/routes/documents.js +160 -13
  85. package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
  86. package/dist/server/routes/durableTaskHttpRouters.js +2 -2
  87. package/dist/server/routes/localService.js +2 -0
  88. package/dist/server/routes/primitives.d.ts +2 -0
  89. package/dist/server/routes/shared.d.ts +24 -0
  90. package/dist/server/routes/shared.js +114 -18
  91. package/dist/server/routes/sync.js +22 -0
  92. package/dist/server/routes/syncV3FeedRoutes.js +4 -2
  93. package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
  94. package/dist/server/routes/syncV3OperationRoutes.js +17 -2
  95. package/dist/server/routes/tasks.js +199 -89
  96. package/dist/server/routes.js +35 -7
  97. package/dist/service/localServiceCli.js +36 -25
  98. package/dist/service/localServiceClientLifecycle.js +67 -6
  99. package/dist/shared/planningIdentity.d.ts +20 -0
  100. package/dist/shared/planningIdentity.js +79 -0
  101. package/dist/storage/postgresAdapter.js +4 -0
  102. package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
  103. package/dist/storage/postgresRequestDiagnostics.js +24 -0
  104. package/dist/storage/workspaceAssetStore.d.ts +32 -0
  105. package/dist/storage/workspaceAssetStore.js +70 -0
  106. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
  107. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
  108. package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
  109. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
  110. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
  111. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
  112. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
  113. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
  114. package/dist/sync/syncApplyHandlers.d.ts +3 -1
  115. package/dist/sync/syncApplyHandlers.js +29 -0
  116. package/dist/sync/syncService.js +9 -0
  117. package/dist/sync/taskSyncPayload.d.ts +1 -0
  118. package/dist/sync/taskSyncPayload.js +6 -0
  119. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
  120. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
  121. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
  122. package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
  123. package/dist/sync/v3/localOutboxDispatcher.js +5 -1
  124. package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
  125. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
  126. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
  127. package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
  128. package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
  129. package/dist/sync/v3/syncDurabilityStore.js +171 -33
  130. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
  131. package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
  132. package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
  133. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
  134. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
  135. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
  136. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
  137. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
  138. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
  139. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
  140. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
  141. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
  142. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
  143. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
  144. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
  145. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
  146. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
  147. package/dist/sync/workspacePullFeed.d.ts +6 -1
  148. package/dist/sync/workspacePullFeed.js +172 -161
  149. package/dist/sync/workspaceSyncModel.d.ts +11 -0
  150. package/dist/sync/workspaceSyncModel.js +27 -2
  151. package/dist/types.d.ts +8 -0
  152. package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
  153. package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
  154. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
  155. package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
  156. package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
  157. package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
  158. package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
  159. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
  160. package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
  161. package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
  162. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
  163. package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
  164. package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
  165. package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
  166. package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
  167. package/dist/ui/assets/index-BUplSsv_.js +7 -0
  168. package/dist/ui/assets/index-gz2EXuoK.css +1 -0
  169. package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
  170. package/dist/ui/index.html +3 -3
  171. package/dist/utils/constants.d.ts +2 -2
  172. package/dist/utils/constants.js +1 -1
  173. package/dist/utils/contextAttachmentUpload.d.ts +2 -0
  174. package/dist/utils/contextAttachmentUpload.js +7 -0
  175. package/dist/utils/contextFiles.d.ts +1 -1
  176. package/dist/utils/contextFiles.js +4 -4
  177. package/package.json +6 -1
  178. package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
  179. package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
  180. package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
  181. package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
  182. package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
  183. package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
  184. package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
  185. package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
  186. package/dist/ui/assets/index-BzJlVnNS.css +0 -1
  187. package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
@@ -8,6 +8,8 @@
8
8
  * POST /api/taskforce/context-upload/init
9
9
  * POST /api/taskforce/context-upload/finalize
10
10
  * POST /api/taskforce/context-upload
11
+ * POST /api/taskforce/context-upload/draft/promote
12
+ * POST /api/taskforce/context-upload/draft/discard
11
13
  * GET /api/taskforce/context/:filename
12
14
  * GET /api/taskforce/documents/:assetId/content
13
15
  * GET /api/taskforce/context-link
@@ -33,7 +35,7 @@ import { parsePlan } from '../../utils/planParser.js';
33
35
  import { getCanonicalDocumentName, getReadableDocumentName, isGeneratedDocumentName } from '../../utils/documentNames.js';
34
36
  import { createWorkspaceAssetAccessGuard } from './assetAccess.js';
35
37
  import { parseJsonBody } from './primitives.js';
36
- import { requestHeader, requestRuntimeMode, resolveErrorStatusCode } from './shared.js';
38
+ import { requestHeader, requestHumanActorRef, requestRuntimeMode, requestUserId, resolveErrorStatusCode, } from './shared.js';
37
39
  import { resolveDeploymentConfig } from '../../config/deployment.js';
38
40
  import { isAiProfileAvatarStorageKey } from '../../utils/aiProfileAvatarPaths.js';
39
41
  import { createTaskHttpDurableRouters } from './durableTaskHttpRouters.js';
@@ -110,7 +112,7 @@ async function proxyCloudUserProfileAvatar(req, res, relativePath, contentTypeFo
110
112
  }
111
113
  }
112
114
  export function registerDocumentRoutes(deps) {
113
- const { addRoute, core, context, requestWorkspaceId, ensureAdminRole, resolveEffectiveObjectStorageConfig, getObjectStorageClient, resolveStorageWorkspaceId, ensureContextUploadAllowed, parseStorageFolderSegments, buildCanonicalWorkspaceAssetRelativePath, inferAssetKindFromPath, appendStorageTelemetry, scanAndRecordDocument, buildTaskAttachmentPayload, workspaceAssetStore, taskAssetStore, normalizeDocumentPathInput, getAssetMetadata, getCanonicalAssetMetadata, documentRetentionDays, appendDocumentPurgeHistory, markAssetDeleted, isAssetDeleted, clearAssetDeleted, ensureWithinDir, sanitizeDownloadFilename, contentTypeForExt, inferInlineDocumentFromPath, resolveCanonicalDocumentRead, inferWorkspaceIdFromStoragePath, inferTaskIdFromStoragePath, } = deps;
115
+ const { addRoute, core, context, requestWorkspaceId, ensureAdminRole, resolveEffectiveObjectStorageConfig, getObjectStorageClient, resolveStorageWorkspaceId, ensureContextUploadAllowed, parseStorageFolderSegments, buildCanonicalWorkspaceAssetRelativePath, inferAssetKindFromPath, appendStorageTelemetry, scanAndRecordDocument, buildTaskAttachmentPayload, workspaceAssetStore, taskAssetStore, normalizeDocumentPathInput, getAssetMetadata, getCanonicalAssetMetadata, documentRetentionDays, appendDocumentPurgeHistory, markAssetDeleted, isAssetDeleted, clearAssetDeleted, ensureWithinDir, sanitizeDownloadFilename, contentTypeForExt, inferInlineDocumentFromPath, resolveCanonicalContentRead, inferWorkspaceIdFromStoragePath, inferTaskIdFromStoragePath, } = deps;
114
116
  const isTaskAttachmentDurable = (req, workspaceId) => (createTaskHttpDurableRouters({ core, context, req, workspaceId }).attachmentLinks.enabled);
115
117
  const durableUploadIdentity = (req, workspaceId, taskId) => {
116
118
  const normalizedTaskId = String(taskId || '').trim();
@@ -172,6 +174,69 @@ export function registerDocumentRoutes(deps) {
172
174
  return String(url.searchParams.get('workspaceId') || '').trim() || requestWorkspaceId(req);
173
175
  };
174
176
  const { ensureWorkspaceAssetAccess, isCloudAssetRequest } = createWorkspaceAssetAccessGuard({ core, context });
177
+ const readTaskDescriptionDraftUpload = (body) => {
178
+ const draftId = String(body?.draftUploadId || '').trim();
179
+ const purpose = String(body?.uploadPurpose || '').trim();
180
+ if (!draftId && !purpose)
181
+ return null;
182
+ if (purpose !== 'task-description-draft' || !/^[a-zA-Z0-9-]{16,160}$/.test(draftId)) {
183
+ throw Object.assign(new Error('Invalid task Description draft upload identity.'), { statusCode: 400 });
184
+ }
185
+ return { draftId, purpose };
186
+ };
187
+ const recordTaskDescriptionDraftUpload = (input) => {
188
+ const draft = readTaskDescriptionDraftUpload(input.body);
189
+ if (!draft || !workspaceAssetStore)
190
+ return;
191
+ workspaceAssetStore.recordUploadDraft({
192
+ workspaceId: input.workspaceId,
193
+ draftId: draft.draftId,
194
+ assetId: input.assetId,
195
+ storageKey: input.storageKey,
196
+ purpose: draft.purpose,
197
+ createdByUserId: String(requestUserId(input.req) || '').trim() || 'anonymous',
198
+ });
199
+ };
200
+ const retireOrDeleteTaskDescriptionDraftUploads = async (input) => {
201
+ if (!workspaceAssetStore)
202
+ return { total: 0, promoted: 0, discarded: 0 };
203
+ const records = workspaceAssetStore.listUploadDrafts(input);
204
+ const now = new Date();
205
+ const deletedAt = now.toISOString();
206
+ const purgeAfter = new Date(now.getTime() + documentRetentionDays * 86_400_000).toISOString();
207
+ let promoted = 0;
208
+ let discarded = 0;
209
+ for (const record of records) {
210
+ const links = workspaceAssetStore.listLinksForAsset(record.assetId, record.workspaceId);
211
+ if (links.length > 0) {
212
+ workspaceAssetStore.retireUploadDraft({
213
+ workspaceId: record.workspaceId,
214
+ draftId: record.draftId,
215
+ assetId: record.assetId,
216
+ retiredAt: deletedAt,
217
+ });
218
+ promoted += 1;
219
+ continue;
220
+ }
221
+ const asset = workspaceAssetStore.get(record.assetId, record.workspaceId);
222
+ if (asset && !asset.deletedAt) {
223
+ workspaceAssetStore.assertAssetDeletionAllowed(record.assetId, record.workspaceId);
224
+ workspaceAssetStore.markDeleted(record.assetId, record.workspaceId, deletedAt, purgeAfter);
225
+ core.notifyDocumentMetadataMutation(record.workspaceId, [record.storageKey], 'delete');
226
+ }
227
+ else if (!asset) {
228
+ await getObjectStorageClient()?.deleteObject(record.storageKey);
229
+ }
230
+ workspaceAssetStore.retireUploadDraft({
231
+ workspaceId: record.workspaceId,
232
+ draftId: record.draftId,
233
+ assetId: record.assetId,
234
+ retiredAt: deletedAt,
235
+ });
236
+ discarded += 1;
237
+ }
238
+ return { total: records.length, promoted, discarded };
239
+ };
175
240
  const removeDocumentAttachmentFromTaskAttachments = (attachmentsRaw, asset) => {
176
241
  const canonicalDocumentPath = `/api/taskforce/documents/${encodeURIComponent(asset.assetId)}/content`;
177
242
  const legacyContextPath = `/api/taskforce/context/${encodeURIComponent(asset.storageKey)}`;
@@ -260,6 +325,13 @@ export function registerDocumentRoutes(deps) {
260
325
  const relativeToStorage = canonicalAsset.relativePath;
261
326
  const relativePath = path.relative(projectRoot, path.join(basePath, relativeToStorage));
262
327
  if (objectStorageClient && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
328
+ recordTaskDescriptionDraftUpload({
329
+ req,
330
+ body,
331
+ workspaceId,
332
+ assetId: canonicalAsset.assetId,
333
+ storageKey: relativeToStorage,
334
+ });
263
335
  const uploadUrl = objectStorageClient.createSignedPutUrl(relativeToStorage, resolvedObjectStorage.r2.signedUploadTtlSeconds, String(mimeType || 'application/octet-stream'));
264
336
  appendStorageTelemetry('upload.initiated', {
265
337
  path: relativeToStorage,
@@ -345,8 +417,19 @@ export function registerDocumentRoutes(deps) {
345
417
  provider: 'r2',
346
418
  workspaceId: inferWorkspaceIdFromStoragePath(relativeToStorage),
347
419
  taskId: durableTaskAttachment ? null : taskId,
348
- originalName: typeof body?.originalName === 'string' ? body.originalName.trim() : null
420
+ originalName: typeof body?.originalName === 'string' ? body.originalName.trim() : null,
421
+ actorRef: requestHumanActorRef(req),
349
422
  });
423
+ const finalizedAsset = workspaceAssetStore?.getByStorageKey(relativeToStorage, workspaceId);
424
+ if (finalizedAsset) {
425
+ recordTaskDescriptionDraftUpload({
426
+ req,
427
+ body,
428
+ workspaceId,
429
+ assetId: finalizedAsset.assetId,
430
+ storageKey: relativeToStorage,
431
+ });
432
+ }
350
433
  if (durableTaskAttachment && taskId) {
351
434
  attachUploadedAssetThroughDurableBoundary({
352
435
  req,
@@ -456,7 +539,8 @@ export function registerDocumentRoutes(deps) {
456
539
  provider: 'r2',
457
540
  workspaceId,
458
541
  taskId: durableTaskAttachment ? null : normalizedTaskId,
459
- originalName: safeOriginal || actualFilename
542
+ originalName: safeOriginal || actualFilename,
543
+ actorRef: requestHumanActorRef(req),
460
544
  });
461
545
  appendStorageTelemetry('upload.completed', {
462
546
  path: relativeToStorage,
@@ -476,7 +560,8 @@ export function registerDocumentRoutes(deps) {
476
560
  provider: 'local',
477
561
  workspaceId,
478
562
  taskId: durableTaskAttachment ? null : normalizedTaskId,
479
- originalName: safeOriginal || actualFilename
563
+ originalName: safeOriginal || actualFilename,
564
+ actorRef: requestHumanActorRef(req),
480
565
  });
481
566
  appendStorageTelemetry('upload.completed', {
482
567
  path: relativeToStorage,
@@ -494,6 +579,16 @@ export function registerDocumentRoutes(deps) {
494
579
  caption: safeOriginal || actualFilename,
495
580
  });
496
581
  }
582
+ const uploadedAsset = workspaceAssetStore?.getByStorageKey(relativeToStorage, workspaceId);
583
+ if (uploadedAsset) {
584
+ recordTaskDescriptionDraftUpload({
585
+ req,
586
+ body,
587
+ workspaceId,
588
+ assetId: uploadedAsset.assetId,
589
+ storageKey: relativeToStorage,
590
+ });
591
+ }
497
592
  res.writeHead(200, { 'Content-Type': 'application/json' });
498
593
  const attachment = buildTaskAttachmentPayload(relativeToStorage, {
499
594
  caption: safeOriginal || actualFilename,
@@ -508,6 +603,40 @@ export function registerDocumentRoutes(deps) {
508
603
  })
509
604
  }));
510
605
  });
606
+ addRoute('POST', '/api/taskforce/context-upload/draft/promote', async (req, res) => {
607
+ const body = await parseJsonBody(req);
608
+ const workspaceId = resolveStorageWorkspaceId(req, body);
609
+ const draft = readTaskDescriptionDraftUpload(body);
610
+ if (!workspaceId || !draft) {
611
+ res.writeHead(400, { 'Content-Type': 'application/json' });
612
+ res.end(JSON.stringify({ error: 'workspaceId and draft upload identity are required.' }));
613
+ return;
614
+ }
615
+ const summary = await retireOrDeleteTaskDescriptionDraftUploads({
616
+ workspaceId,
617
+ draftId: draft.draftId,
618
+ createdByUserId: String(requestUserId(req) || '').trim() || 'anonymous',
619
+ });
620
+ res.writeHead(200, { 'Content-Type': 'application/json' });
621
+ res.end(JSON.stringify({ success: true, ...summary }));
622
+ });
623
+ addRoute('POST', '/api/taskforce/context-upload/draft/discard', async (req, res) => {
624
+ const body = await parseJsonBody(req);
625
+ const workspaceId = resolveStorageWorkspaceId(req, body);
626
+ const draft = readTaskDescriptionDraftUpload(body);
627
+ if (!workspaceId || !draft) {
628
+ res.writeHead(400, { 'Content-Type': 'application/json' });
629
+ res.end(JSON.stringify({ error: 'workspaceId and draft upload identity are required.' }));
630
+ return;
631
+ }
632
+ const summary = await retireOrDeleteTaskDescriptionDraftUploads({
633
+ workspaceId,
634
+ draftId: draft.draftId,
635
+ createdByUserId: String(requestUserId(req) || '').trim() || 'anonymous',
636
+ });
637
+ res.writeHead(200, { 'Content-Type': 'application/json' });
638
+ res.end(JSON.stringify({ success: true, ...summary }));
639
+ });
511
640
  addRoute('GET', '/api/taskforce/context/:filename', async (req, res, params) => {
512
641
  const resolvedObjectStorage = resolveEffectiveObjectStorageConfig();
513
642
  const objectStorageClient = getObjectStorageClient();
@@ -556,8 +685,8 @@ export function registerDocumentRoutes(deps) {
556
685
  ? `attachment; filename="${resolvedDownloadName.replace(/"/g, '')}"`
557
686
  : 'inline';
558
687
  const shouldProxyInlineDocument = !forceDownload && (canonicalAsset?.kind === 'document' || inferInlineDocumentFromPath(normalizedRelative));
559
- if (canonicalAsset?.kind === 'document' && !canonicalAsset.deletedAt) {
560
- const response = await resolveCanonicalDocumentRead({
688
+ if (canonicalAsset && !canonicalAsset.deletedAt && (canonicalAsset.kind === 'document' || !filePath)) {
689
+ const response = await resolveCanonicalContentRead({
561
690
  asset: canonicalAsset,
562
691
  forceDownload,
563
692
  requestedFilename
@@ -567,6 +696,11 @@ export function registerDocumentRoutes(deps) {
567
696
  res.end('Context file not found');
568
697
  return;
569
698
  }
699
+ if (response.statusCode === 409) {
700
+ res.writeHead(409, response.headers || { 'Content-Type': 'application/json' });
701
+ res.end(response.body);
702
+ return;
703
+ }
570
704
  if (forceDownload && response.statusCode === 302 && response.headers?.Location) {
571
705
  res.writeHead(302, { Location: response.headers.Location });
572
706
  res.end();
@@ -659,7 +793,7 @@ export function registerDocumentRoutes(deps) {
659
793
  res.end('Document scan pending');
660
794
  return;
661
795
  }
662
- const response = await resolveCanonicalDocumentRead({
796
+ const response = await resolveCanonicalContentRead({
663
797
  asset,
664
798
  forceDownload,
665
799
  requestedFilename
@@ -669,6 +803,11 @@ export function registerDocumentRoutes(deps) {
669
803
  res.end(JSON.stringify({ error: 'Document content not found' }));
670
804
  return;
671
805
  }
806
+ if (response.statusCode === 409) {
807
+ res.writeHead(409, response.headers || { 'Content-Type': 'application/json' });
808
+ res.end(response.body);
809
+ return;
810
+ }
672
811
  if (forceDownload && response.statusCode === 302 && response.headers?.Location) {
673
812
  res.writeHead(302, { Location: response.headers.Location });
674
813
  res.end();
@@ -791,6 +930,12 @@ export function registerDocumentRoutes(deps) {
791
930
  res.end(JSON.stringify({ error: 'Forbidden: admin role required.' }));
792
931
  return;
793
932
  }
933
+ const workspaceId = requestWorkspaceId(req);
934
+ const draftExpiry = new Date(Date.now() - 30 * 86_400_000).toISOString();
935
+ const draftSummary = await retireOrDeleteTaskDescriptionDraftUploads({
936
+ workspaceId,
937
+ createdBefore: draftExpiry,
938
+ });
794
939
  const { basePath } = core.getPaths();
795
940
  const summary = await purgeExpiredDocuments({
796
941
  basePath,
@@ -804,7 +949,7 @@ export function registerDocumentRoutes(deps) {
804
949
  provider: resolveEffectiveObjectStorageConfig().provider
805
950
  });
806
951
  res.writeHead(200, { 'Content-Type': 'application/json' });
807
- res.end(JSON.stringify({ success: true, ...summary }));
952
+ res.end(JSON.stringify({ success: true, ...summary, draftUploads: draftSummary }));
808
953
  });
809
954
  addRoute('GET', '/api/taskforce/documents', async (req, res) => {
810
955
  const workspaceId = requestWorkspaceIdWithQuery(req);
@@ -1059,7 +1204,7 @@ export function registerDocumentRoutes(deps) {
1059
1204
  core.updateTask(targetId, {
1060
1205
  attachments: [...(Array.isArray(task.attachments) ? task.attachments : []), payload],
1061
1206
  }, workspaceId, {
1062
- actorRef: 'user',
1207
+ actorRef: requestHumanActorRef(req),
1063
1208
  saveSource: 'manual',
1064
1209
  });
1065
1210
  }
@@ -1124,7 +1269,7 @@ export function registerDocumentRoutes(deps) {
1124
1269
  else {
1125
1270
  const updatedAttachments = removeDocumentAttachmentFromTaskAttachments(task.attachments, asset);
1126
1271
  core.updateTask(taskId, { attachments: updatedAttachments }, workspaceId, {
1127
- actorRef: 'user',
1272
+ actorRef: requestHumanActorRef(req),
1128
1273
  saveSource: 'manual'
1129
1274
  });
1130
1275
  }
@@ -1280,7 +1425,8 @@ export function registerDocumentRoutes(deps) {
1280
1425
  contentType: 'text/markdown; charset=utf-8',
1281
1426
  provider: 'r2',
1282
1427
  workspaceId: assetWorkspaceId,
1283
- originalName: metadata.logicalName || metadata.originalFilename || path.basename(normalizedStoragePath)
1428
+ originalName: metadata.logicalName || metadata.originalFilename || path.basename(normalizedStoragePath),
1429
+ actorRef: requestHumanActorRef(req),
1284
1430
  });
1285
1431
  clearAssetDeleted(normalizedStoragePath);
1286
1432
  const version = workspaceAssetStore
@@ -1336,7 +1482,8 @@ export function registerDocumentRoutes(deps) {
1336
1482
  contentType: 'text/markdown; charset=utf-8',
1337
1483
  provider: 'local',
1338
1484
  workspaceId: assetWorkspaceId,
1339
- originalName: path.basename(normalizedStoragePath)
1485
+ originalName: path.basename(normalizedStoragePath),
1486
+ actorRef: requestHumanActorRef(req),
1340
1487
  });
1341
1488
  clearAssetDeleted(normalizedStoragePath);
1342
1489
  version = workspaceAssetStore
@@ -53,6 +53,7 @@ export declare function createTaskHttpDurableRouters(input: {
53
53
  replace(args: {
54
54
  taskId: string;
55
55
  links: readonly import("../../sync/v3/workspaceSyncV3NestedTaskCodecs.js").WorkspaceSyncV3TaskAttachmentLink[];
56
+ cardCoverAssetId?: string | null;
56
57
  }): {
57
58
  state: "pending" | "committed";
58
59
  route: "local-outbox" | "cloud-acceptance";
@@ -77,6 +78,7 @@ export declare function createTaskHttpDurableRouters(input: {
77
78
  replace(args: {
78
79
  taskId: string;
79
80
  links: readonly import("../../sync/v3/workspaceSyncV3NestedTaskCodecs.js").WorkspaceSyncV3TaskAttachmentLink[];
81
+ cardCoverAssetId?: string | null;
80
82
  }): {
81
83
  state: "pending" | "committed";
82
84
  route: "local-outbox" | "cloud-acceptance";
@@ -4,12 +4,12 @@ import { createDurableTaskHttpRootMutationRouter } from '../../sync/v3/durableTa
4
4
  import { createDurableTaskCommentMutationRouter } from '../../sync/v3/durableTaskCommentMutationRouter.js';
5
5
  import { createDurableTaskChecklistMutationRouter } from '../../sync/v3/durableTaskChecklistMutationRouter.js';
6
6
  import { createDurableTaskAttachmentLinksMutationRouter } from '../../sync/v3/durableTaskAttachmentLinksMutationRouter.js';
7
- import { requestHeader, requestRuntimeMode, requestUserId } from './shared.js';
7
+ import { requestHeader, requestHumanActorRef, requestRuntimeMode } from './shared.js';
8
8
  export function createTaskHttpDurableRouters(input) {
9
9
  const { core, context, req, workspaceId } = input;
10
10
  const runtimeMode = context.authConfig?.runtimeMode === 'cloud' ? 'cloud' : requestRuntimeMode(req);
11
11
  const parentOperationId = requestHeader(req, 'x-taskforce-operation-id', '').trim();
12
- const actorRef = runtimeMode === 'cloud' ? (String(requestUserId(req) || '').trim() || 'user') : 'user';
12
+ const actorRef = requestHumanActorRef(req);
13
13
  const createRootRouter = (operationId = parentOperationId, deferPostCommitNotification) => createDurableTaskHttpRootMutationRouter({
14
14
  core,
15
15
  runtimeMode,
@@ -185,6 +185,7 @@ export function registerLocalServiceRoutes(deps) {
185
185
  endpoint: context.endpoint?.() || null,
186
186
  projectIdentity: context.projectIdentity,
187
187
  dataIdentity: context.ownershipKey,
188
+ cloudTarget: context.cloudTarget,
188
189
  transports: ['streamable-http'],
189
190
  health: {
190
191
  lifecycle: context.sessions.state(),
@@ -279,6 +280,7 @@ export function registerLocalServiceRoutes(deps) {
279
280
  generation: context.generation,
280
281
  protocolVersion: context.protocolVersion,
281
282
  ownerPid: context.ownerPid,
283
+ cloudTarget: context.cloudTarget,
282
284
  sessions: context.sessions.list(),
283
285
  ...(context.diagnostics?.() || {}),
284
286
  });
@@ -4,6 +4,7 @@ import type { ResolvedObjectStorageConfig } from '../../storage/objectStorage.js
4
4
  import type { LocalSyncCoordinatorRouteServices } from '../../sync/coordinator/localSyncCoordinatorRouteServices.js';
5
5
  import type { WorkspaceSyncServerDataServices } from '../../sync/engine/workspaceSyncServerDataServices.js';
6
6
  import type { LocalServiceSessionManager } from '../localServiceSessions.js';
7
+ import type { LocalServiceCloudTarget } from '../../config/localServiceCloudTarget.js';
7
8
  export interface LocalServiceRouteContext {
8
9
  generation: string;
9
10
  protocolVersion: string;
@@ -11,6 +12,7 @@ export interface LocalServiceRouteContext {
11
12
  ownerPid: number;
12
13
  bootstrapCapability: string;
13
14
  ownershipKey: string;
15
+ cloudTarget: LocalServiceCloudTarget;
14
16
  projectIdentity: string;
15
17
  operationClientId: string;
16
18
  sessions: LocalServiceSessionManager;
@@ -11,6 +11,7 @@ import type { McpAccessTokenAuthResult, McpOAuthAccessTokenAuthResult, McpAccess
11
11
  import { type RouteContext } from './primitives.js';
12
12
  import { type AiProfileBootstrapConfig } from '../../mcp/aiProfileBootstrap.js';
13
13
  import type { AiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
14
+ import { type RequestPerformanceDiagnostics } from '../../storage/postgresRequestDiagnostics.js';
14
15
  export declare function resolveErrorStatusCode(error: unknown, fallback?: number): number;
15
16
  export declare function buildCloudMcpProfileConnectionId(input: {
16
17
  oauth?: McpOAuthAccessTokenAuthResult | null;
@@ -19,6 +20,7 @@ export declare function buildCloudMcpProfileConnectionId(input: {
19
20
  }): string | null;
20
21
  export declare const BOOTSTRAP_ROUTE_SLOW_THRESHOLD_MS = 1500;
21
22
  export declare const BOOTSTRAP_ROUTE_SEVERE_THRESHOLD_MS = 5000;
23
+ export declare const TASKFORCE_PERFORMANCE_TRACE_HEADER = "x-taskforce-performance-trace";
22
24
  export type ServerTimingMetric = {
23
25
  name: string;
24
26
  durationMs: number;
@@ -31,6 +33,17 @@ export declare function serializeJsonWithTiming(payload: unknown, startedAt?: nu
31
33
  byteLength: number;
32
34
  durationMs: number;
33
35
  };
36
+ export declare function runWithPerformanceTrace<T>(req: IncomingMessage, callback: () => T): T;
37
+ export declare function currentPerformanceTraceDiagnostics(): RequestPerformanceDiagnostics | null;
38
+ export declare function performanceTraceServerTimingMetrics(diagnostics: RequestPerformanceDiagnostics | null): ServerTimingMetric[];
39
+ export declare function setPerformanceTraceHeaders(res: ServerResponse, diagnostics: RequestPerformanceDiagnostics | null, responseBytes: number): void;
40
+ export declare function respondWithPerformanceTraceJson(input: {
41
+ req: IncomingMessage;
42
+ res: ServerResponse;
43
+ route: string;
44
+ workspaceId: string;
45
+ load: () => unknown;
46
+ }): void;
34
47
  export declare function requestBootstrapTraceId(req: IncomingMessage): string;
35
48
  export declare function logSlowBootstrapRoute(input: {
36
49
  route: string;
@@ -47,6 +60,7 @@ export declare function buildCloudMcpRuntimeAuth(input: {
47
60
  authenticatedViaToken: McpAccessTokenAuthResult | null;
48
61
  authenticatedViaOAuth: McpOAuthAccessTokenAuthResult | null;
49
62
  userId: string | null;
63
+ aiProfileIdentityAsserted?: boolean;
50
64
  } & Required<AiProfileBootstrapConfig>;
51
65
  requestHeader: (req: IncomingMessage, name: string, fallback?: string) => string;
52
66
  requestId?: string | null;
@@ -57,6 +71,7 @@ export declare function buildCloudMcpRuntimeAuth(input: {
57
71
  tokenId?: string | null;
58
72
  aiProfileToken?: AiProfileBootstrapConfig['aiProfileToken'];
59
73
  aiProfileId?: string | null;
74
+ aiProfileIdentityAsserted?: boolean;
60
75
  aiProfileName?: AiProfileBootstrapConfig['aiProfileName'];
61
76
  aiProfileIcon?: AiProfileBootstrapConfig['aiProfileIcon'];
62
77
  aiProfileColor?: AiProfileBootstrapConfig['aiProfileColor'];
@@ -79,6 +94,7 @@ export type CloudMcpRequestAuth = {
79
94
  authenticatedViaOAuth: McpOAuthAccessTokenAuthResult | null;
80
95
  userId: string | null;
81
96
  aiProfileId?: string | null;
97
+ aiProfileIdentityAsserted?: boolean;
82
98
  } & Required<AiProfileBootstrapConfig>;
83
99
  export declare function makeAuthenticateCloudMcpRequest(core: TaskforceCore, deps: {
84
100
  resolveAuthenticatedUserId: (req: IncomingMessage) => string;
@@ -101,6 +117,14 @@ export declare function requestRuntimeMode(req: IncomingMessage): 'local' | 'clo
101
117
  export declare function requestRole(req: IncomingMessage): string;
102
118
  export declare function requestSystemRole(req: IncomingMessage): 'system_admin' | 'user';
103
119
  export declare function requestUserId(req: IncomingMessage): string;
120
+ /**
121
+ * Resolve the human actor recorded by ordinary HTTP mutations. The standalone
122
+ * server has already replaced this header with authenticated cloud identity
123
+ * when available; local signed-in clients provide the same account id through
124
+ * the request-scoped mutation header. Anonymous sessions retain the legacy
125
+ * generic actor for compatibility.
126
+ */
127
+ export declare function requestHumanActorRef(req: IncomingMessage): string;
104
128
  export declare function makeRequestWorkspaceId(core: TaskforceCore, context: RouteContext): (req: IncomingMessage) => string;
105
129
  export declare function makeResolveAuthenticatedUserId(core: TaskforceCore, context: RouteContext, requestWorkspaceId: (req: IncomingMessage) => string): (req: IncomingMessage) => string;
106
130
  export declare function makeResolveAuthenticatedMutationUserId(core: TaskforceCore, context: RouteContext, requestWorkspaceId: (req: IncomingMessage) => string): (req: IncomingMessage) => string;