@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
@@ -31,9 +31,10 @@
31
31
  * === SECTION: Settings Model === Line ~4625 — settingsModel object
32
32
  * === SECTION: Return Value === Line ~4817 — final return object (public API shape)
33
33
  */
34
- import { type SetStateAction } from 'react';
34
+ import { type Dispatch, type SetStateAction } from 'react';
35
35
  import type { TaskforceConfig, TaskforceTheme, Comment, AttachmentItem, CategoryConfig, TaskItem, TaxonomyDefinition, TaskAssignee, TaskStatus, DeletedTaskRecord, InitiativeItem, WorkstreamItem, ChecklistItem, PlanningBootstrapPayload, PlanningBootstrapWorkstreamTaskSummary, PlanningLoadState } from '../types';
36
36
  import { type FirstClassTaxonomyDisplayLabels } from '../shared/taxonomyState.js';
37
+ import type { PlanningIdentityColor, PlanningIdentityIcon } from '../shared/planningIdentity.js';
37
38
  import type { UiNotice } from '../utils/uiNotice';
38
39
  import type { TaskAssigneeOption } from '../utils/assignees';
39
40
  import { type WorkspaceModuleId } from './workspace/workspaceLocalState';
@@ -137,31 +138,31 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
137
138
  showTaskCardStatusLabel: boolean;
138
139
  };
139
140
  isOpen: boolean;
140
- setIsOpen: import("react").Dispatch<SetStateAction<boolean>>;
141
+ setIsOpen: Dispatch<SetStateAction<boolean>>;
141
142
  activeTab: "tasks" | "settings" | "add";
142
143
  setActiveTab: (tab: "add" | "tasks" | "settings") => void;
143
144
  currentTheme: "light" | "dawn" | "dark" | "midnight";
144
- setCurrentTheme: import("react").Dispatch<SetStateAction<"light" | "dawn" | "dark" | "midnight">>;
145
+ setCurrentTheme: Dispatch<SetStateAction<"light" | "dawn" | "dark" | "midnight">>;
145
146
  configLoaded: boolean;
146
147
  storagePath: string;
147
148
  saveSettings: () => Promise<boolean>;
148
149
  pathSaved: boolean;
149
150
  keyShortcut: string;
150
- setKeyShortcut: import("react").Dispatch<SetStateAction<string>>;
151
+ setKeyShortcut: Dispatch<SetStateAction<string>>;
151
152
  globalWeekStartsOn: WeekStartsOn;
152
153
  locale: "en-US" | "es-419" | "pt-BR";
153
154
  supportedLocales: ("en-US" | "es-419" | "pt-BR")[];
154
155
  saveLocale: (nextLocale: string) => Promise<boolean>;
155
156
  jsonBackupEnabled: boolean;
156
- setJsonBackupEnabled: import("react").Dispatch<SetStateAction<boolean>>;
157
+ setJsonBackupEnabled: Dispatch<SetStateAction<boolean>>;
157
158
  manualComplexityEnabled: boolean;
158
159
  checklistDropdownEnabled: boolean;
159
160
  showTaskCardStatusLabel: boolean;
160
- setManualComplexityEnabled: import("react").Dispatch<SetStateAction<boolean>>;
161
+ setManualComplexityEnabled: Dispatch<SetStateAction<boolean>>;
161
162
  mcpHostRoot: string;
162
- setMcpHostRoot: import("react").Dispatch<SetStateAction<string>>;
163
+ setMcpHostRoot: Dispatch<SetStateAction<string>>;
163
164
  settingsSection: "mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "integrations" | null;
164
- setSettingsSection: import("react").Dispatch<SetStateAction<"mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "integrations" | null>>;
165
+ setSettingsSection: Dispatch<SetStateAction<"mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "integrations" | null>>;
165
166
  runtimeMode: "local" | "cloud";
166
167
  workspaceMode: "single-local" | "multi-cloud";
167
168
  workspaceSwitchingEnabled: boolean;
@@ -432,7 +433,7 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
432
433
  }>;
433
434
  logout: () => Promise<void>;
434
435
  showFolderBrowser: boolean;
435
- setShowFolderBrowser: import("react").Dispatch<SetStateAction<boolean>>;
436
+ setShowFolderBrowser: Dispatch<SetStateAction<boolean>>;
436
437
  folders: string[];
437
438
  files: string[];
438
439
  currentBrowsePath: string;
@@ -441,16 +442,16 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
441
442
  type: "category";
442
443
  value: string;
443
444
  } | null;
444
- setBrowserTarget: import("react").Dispatch<SetStateAction<"storage" | {
445
+ setBrowserTarget: Dispatch<SetStateAction<"storage" | {
445
446
  type: "category";
446
447
  value: string;
447
448
  } | null>>;
448
449
  groupBy: "category" | "type" | "priority" | "status" | "complexity" | "assignee" | "schedule";
449
- setGroupBy: import("react").Dispatch<SetStateAction<"category" | "type" | "priority" | "status" | "complexity" | "assignee" | "schedule">>;
450
+ setGroupBy: Dispatch<SetStateAction<"category" | "type" | "priority" | "status" | "complexity" | "assignee" | "schedule">>;
450
451
  activeWorkspaceModule: WorkspaceModuleId;
451
- setActiveWorkspaceModule: import("react").Dispatch<SetStateAction<WorkspaceModuleId>>;
452
+ setActiveWorkspaceModule: Dispatch<SetStateAction<WorkspaceModuleId>>;
452
453
  emptyColumnMode: "show" | "collapse" | "hide";
453
- setEmptyColumnMode: import("react").Dispatch<SetStateAction<"show" | "collapse" | "hide">>;
454
+ setEmptyColumnMode: Dispatch<SetStateAction<"show" | "collapse" | "hide">>;
454
455
  zenMode: boolean;
455
456
  setZenMode: () => Promise<void>;
456
457
  handleSelectPath: (path: string) => void;
@@ -484,38 +485,38 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
484
485
  taxonomyDisplayLabels: FirstClassTaxonomyDisplayLabels;
485
486
  taxonomies: TaxonomyDefinition[];
486
487
  searchQuery: string;
487
- setSearchQuery: import("react").Dispatch<SetStateAction<string>>;
488
+ setSearchQuery: Dispatch<SetStateAction<string>>;
488
489
  filterCategories: string[];
489
- setFilterCategories: import("react").Dispatch<SetStateAction<string[]>>;
490
+ setFilterCategories: Dispatch<SetStateAction<string[]>>;
490
491
  filterTypes: string[];
491
- setFilterTypes: import("react").Dispatch<SetStateAction<string[]>>;
492
+ setFilterTypes: Dispatch<SetStateAction<string[]>>;
492
493
  filterPriorities: (string | number)[];
493
- setFilterPriorities: import("react").Dispatch<SetStateAction<(string | number)[]>>;
494
+ setFilterPriorities: Dispatch<SetStateAction<(string | number)[]>>;
494
495
  filterStatus: (string | number)[];
495
- setFilterStatus: import("react").Dispatch<SetStateAction<(string | number)[]>>;
496
+ setFilterStatus: Dispatch<SetStateAction<(string | number)[]>>;
496
497
  filterAssignees: string[];
497
498
  setFilterAssignees: (nextValues: TaskAssignee[] | ((prev: TaskAssignee[]) => TaskAssignee[])) => void;
498
499
  filterTaxonomies: Record<string, (string | number)[]>;
499
- setFilterTaxonomies: import("react").Dispatch<SetStateAction<Record<string, (string | number)[]>>>;
500
+ setFilterTaxonomies: Dispatch<SetStateAction<Record<string, (string | number)[]>>>;
500
501
  hasInitedFilters: boolean;
501
502
  sortBy: import("../utils/taxonomySorting").TaskSortKey;
502
- setSortBy: import("react").Dispatch<SetStateAction<import("../utils/taxonomySorting").TaskSortKey>>;
503
+ setSortBy: Dispatch<SetStateAction<import("../utils/taxonomySorting").TaskSortKey>>;
503
504
  sortOrder: "asc" | "desc";
504
- setSortOrder: import("react").Dispatch<SetStateAction<"asc" | "desc">>;
505
+ setSortOrder: Dispatch<SetStateAction<"asc" | "desc">>;
505
506
  toggleSortOrder: () => void;
506
507
  showArchive: boolean;
507
- setShowArchive: import("react").Dispatch<SetStateAction<boolean>>;
508
+ setShowArchive: Dispatch<SetStateAction<boolean>>;
508
509
  taskScope: TaskScope;
509
- setTaskScope: import("react").Dispatch<SetStateAction<TaskScope>>;
510
+ setTaskScope: Dispatch<SetStateAction<TaskScope>>;
510
511
  compressedCards: boolean;
511
- setCompressedCards: import("react").Dispatch<SetStateAction<boolean>>;
512
+ setCompressedCards: Dispatch<SetStateAction<boolean>>;
512
513
  clearFilters: () => void;
513
514
  filteredTasks: TaskItem[];
514
515
  searchAgnosticTasks: TaskItem[];
515
516
  filteredArchive: TaskItem[];
516
517
  groupedTasks: Record<string, TaskItem[]>;
517
518
  collapsedCategories: Record<string, boolean>;
518
- setCollapsedCategories: import("react").Dispatch<SetStateAction<Record<string, boolean>>>;
519
+ setCollapsedCategories: Dispatch<SetStateAction<Record<string, boolean>>>;
519
520
  fetchTasks: (isSilent?: boolean, options?: {
520
521
  ignoreAuthGuard?: boolean;
521
522
  highlightChangedTaskIds?: readonly string[];
@@ -539,6 +540,10 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
539
540
  }>;
540
541
  createInitiative: (payload: Partial<InitiativeItem>) => Promise<InitiativeItem>;
541
542
  updateInitiative: (id: string, payload: Partial<InitiativeItem>) => Promise<InitiativeItem>;
543
+ updatePlanningIdentity: (entityType: "initiative" | "workstream", id: string, patch: {
544
+ icon?: PlanningIdentityIcon | null;
545
+ color?: PlanningIdentityColor | null;
546
+ }) => Promise<InitiativeItem | WorkstreamItem>;
542
547
  archiveInitiative: (id: string) => Promise<InitiativeItem>;
543
548
  unarchiveInitiative: (id: string) => Promise<InitiativeItem>;
544
549
  createWorkstream: (payload: Partial<WorkstreamItem>) => Promise<WorkstreamItem>;
@@ -574,55 +579,59 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
574
579
  error: string;
575
580
  clearTaskError: () => void;
576
581
  title: string;
577
- setTitle: import("react").Dispatch<SetStateAction<string>>;
582
+ setTitle: Dispatch<SetStateAction<string>>;
578
583
  setTitleDraft: (value: string) => void;
579
584
  description: string;
580
- setDescription: import("react").Dispatch<SetStateAction<string>>;
585
+ setDescription: Dispatch<SetStateAction<string>>;
581
586
  setDescriptionDraft: (value: string) => void;
582
587
  checklistItems: ChecklistItem[];
583
- setChecklistItems: import("react").Dispatch<SetStateAction<ChecklistItem[]>>;
588
+ setChecklistItems: Dispatch<SetStateAction<ChecklistItem[]>>;
584
589
  category: string;
585
- setCategory: import("react").Dispatch<SetStateAction<string>>;
590
+ setCategory: Dispatch<SetStateAction<string>>;
586
591
  type: string;
587
- setType: import("react").Dispatch<SetStateAction<string>>;
592
+ setType: Dispatch<SetStateAction<string>>;
588
593
  priority: string | number;
589
- setPriority: import("react").Dispatch<SetStateAction<string | number>>;
594
+ setPriority: Dispatch<SetStateAction<string | number>>;
590
595
  complexity: string | number;
591
- setComplexity: import("react").Dispatch<SetStateAction<string | number>>;
596
+ setComplexity: Dispatch<SetStateAction<string | number>>;
592
597
  status: TaskStatus;
593
- setStatus: import("react").Dispatch<SetStateAction<TaskStatus>>;
598
+ setStatus: Dispatch<SetStateAction<TaskStatus>>;
594
599
  assignee: string;
595
- setAssignee: import("react").Dispatch<SetStateAction<string>>;
600
+ setAssignee: Dispatch<SetStateAction<string>>;
596
601
  scheduledDate: string;
597
- setScheduledDate: import("react").Dispatch<SetStateAction<string>>;
602
+ setScheduledDate: Dispatch<SetStateAction<string>>;
598
603
  dueDate: string;
599
- setDueDate: import("react").Dispatch<SetStateAction<string>>;
604
+ setDueDate: Dispatch<SetStateAction<string>>;
600
605
  workstreamInput: string;
601
- setWorkstreamInput: import("react").Dispatch<SetStateAction<string>>;
606
+ setWorkstreamInput: Dispatch<SetStateAction<string>>;
602
607
  formTaxonomies: Record<string, string | number | string[] | number[]>;
603
- setFormTaxonomies: import("react").Dispatch<SetStateAction<Record<string, string | number | string[] | number[]>>>;
608
+ setFormTaxonomies: Dispatch<SetStateAction<Record<string, string | number | string[] | number[]>>>;
604
609
  comments: Comment[];
605
610
  newCommentText: string;
606
- setNewCommentText: import("react").Dispatch<SetStateAction<string>>;
611
+ setNewCommentText: Dispatch<SetStateAction<string>>;
607
612
  attachments: (string | AttachmentItem)[];
608
- setAttachments: import("react").Dispatch<SetStateAction<(string | AttachmentItem)[]>>;
613
+ setAttachments: Dispatch<SetStateAction<(string | AttachmentItem)[]>>;
609
614
  attachmentsDirty: boolean;
610
- setAttachmentsDirty: import("react").Dispatch<SetStateAction<boolean>>;
615
+ setAttachmentsDirty: Dispatch<SetStateAction<boolean>>;
616
+ descriptionImageUploadPending: boolean;
617
+ descriptionImageDraftId: string;
618
+ registerDescriptionImageUpload: (upload: Promise<void>) => void;
611
619
  descriptionFocused: boolean;
612
- setDescriptionFocused: import("react").Dispatch<SetStateAction<boolean>>;
620
+ setDescriptionFocused: Dispatch<SetStateAction<boolean>>;
613
621
  showMarkdownHelp: boolean;
614
- setShowMarkdownHelp: import("react").Dispatch<SetStateAction<boolean>>;
622
+ setShowMarkdownHelp: Dispatch<SetStateAction<boolean>>;
615
623
  isCapturingScreenshot: boolean;
616
- setIsCapturingScreenshot: import("react").Dispatch<SetStateAction<boolean>>;
624
+ setIsCapturingScreenshot: Dispatch<SetStateAction<boolean>>;
617
625
  handleSubmit: (event: React.FormEvent) => Promise<void>;
618
626
  resetForm: () => void;
627
+ discardDescriptionImageDraft: () => Promise<void>;
619
628
  handleAddComment: (text: string) => Promise<void>;
620
629
  handleSetWorkstreamForCurrentTask: (workstreamIdOverride?: string | null) => Promise<void>;
621
630
  handleOpenTaskById: (taskId: string) => void;
622
631
  returnToPreviousTask: () => boolean;
623
632
  autoSaveState: import("./tasks/useTaskFormActions").TaskAutoSaveState;
624
633
  unsavedModalOpen: boolean;
625
- setUnsavedModalOpen: import("react").Dispatch<SetStateAction<boolean>>;
634
+ setUnsavedModalOpen: Dispatch<SetStateAction<boolean>>;
626
635
  pendingNavigation: (() => void) | null;
627
636
  handleNavigation: (action: () => void) => Promise<void>;
628
637
  handleClose: () => void;
@@ -648,13 +657,13 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
648
657
  confirmScheduleWarning: () => Promise<void>;
649
658
  cancelScheduleWarning: () => void;
650
659
  tasksScrollRef: import("react").RefObject<HTMLDivElement | null>;
651
- setTasksScrollPos: import("react").Dispatch<SetStateAction<number>>;
660
+ setTasksScrollPos: Dispatch<SetStateAction<number>>;
652
661
  taskforceAgentId: string;
653
- setTaskforceAgentId: import("react").Dispatch<SetStateAction<string>>;
662
+ setTaskforceAgentId: Dispatch<SetStateAction<string>>;
654
663
  taskforceAgentConversationId: string;
655
- setTaskforceAgentConversationId: import("react").Dispatch<SetStateAction<string>>;
664
+ setTaskforceAgentConversationId: Dispatch<SetStateAction<string>>;
656
665
  taskforceAgentConversationByAgentId: Record<string, string>;
657
- setTaskforceAgentConversationByAgentId: import("react").Dispatch<SetStateAction<Record<string, string>>>;
666
+ setTaskforceAgentConversationByAgentId: Dispatch<SetStateAction<Record<string, string>>>;
658
667
  uiStateReady: boolean;
659
668
  handleUpdateCategory: (updatedCat: CategoryConfig, oldLabel?: string) => Promise<void>;
660
669
  handleRemoveCategory: (catValue: string) => Promise<void>;
@@ -685,7 +694,7 @@ export declare function useTaskforce({ config, initialTaskId, initialActivityEnt
685
694
  validatePaths: (paths: string[]) => Promise<void>;
686
695
  getCategoryPaths: (cat: CategoryConfig) => string[];
687
696
  customCategories: CategoryConfig[];
688
- setCustomCategories: import("react").Dispatch<SetStateAction<CategoryConfig[]>>;
697
+ setCustomCategories: Dispatch<SetStateAction<CategoryConfig[]>>;
689
698
  projectRoot: string;
690
699
  projectName: string;
691
700
  mcpScriptPath: string;
@@ -1494,7 +1494,46 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
1494
1494
  const [activeWorkspaceModule, setActiveWorkspaceModuleState] = useState('tasks');
1495
1495
  const [descriptionFocused, setDescriptionFocused] = useState(false);
1496
1496
  const [isCapturingScreenshot, setIsCapturingScreenshot] = useState(false);
1497
- const [attachments, setAttachments] = useState([]);
1497
+ const attachmentsDraftRef = useRef([]);
1498
+ const [attachments, setAttachmentsState] = useState([]);
1499
+ const setAttachments = useCallback((next) => {
1500
+ const nextAttachments = typeof next === 'function'
1501
+ ? next(attachmentsDraftRef.current)
1502
+ : next;
1503
+ attachmentsDraftRef.current = nextAttachments;
1504
+ setAttachmentsState(nextAttachments);
1505
+ }, []);
1506
+ const getAttachmentsDraft = useCallback(() => [...attachmentsDraftRef.current], []);
1507
+ const pendingDescriptionImageUploadRef = useRef(null);
1508
+ const [descriptionImageUploadPending, setDescriptionImageUploadPending] = useState(false);
1509
+ const createDescriptionImageDraftId = useCallback(() => (`task-description-${globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`}`), []);
1510
+ const descriptionImageDraftIdRef = useRef(createDescriptionImageDraftId());
1511
+ const [descriptionImageDraftId, setDescriptionImageDraftId] = useState(descriptionImageDraftIdRef.current);
1512
+ const rotateDescriptionImageDraftId = useCallback(() => {
1513
+ const nextDraftId = createDescriptionImageDraftId();
1514
+ descriptionImageDraftIdRef.current = nextDraftId;
1515
+ setDescriptionImageDraftId(nextDraftId);
1516
+ }, [createDescriptionImageDraftId]);
1517
+ const registerDescriptionImageUpload = useCallback((upload) => {
1518
+ pendingDescriptionImageUploadRef.current = upload;
1519
+ setDescriptionImageUploadPending(true);
1520
+ void upload.then(() => {
1521
+ if (pendingDescriptionImageUploadRef.current !== upload)
1522
+ return;
1523
+ pendingDescriptionImageUploadRef.current = null;
1524
+ setDescriptionImageUploadPending(false);
1525
+ }, () => {
1526
+ if (pendingDescriptionImageUploadRef.current !== upload)
1527
+ return;
1528
+ pendingDescriptionImageUploadRef.current = null;
1529
+ setDescriptionImageUploadPending(false);
1530
+ });
1531
+ }, []);
1532
+ const awaitPendingDescriptionImageUpload = useCallback(async () => {
1533
+ const upload = pendingDescriptionImageUploadRef.current;
1534
+ if (upload)
1535
+ await upload;
1536
+ }, []);
1498
1537
  const [attachmentsDirty, setAttachmentsDirty] = useState(false);
1499
1538
  const dataVersionRef = useRef(null);
1500
1539
  const ambiguousRealtimeConfirmationTimerRef = useRef(null);
@@ -2804,6 +2843,24 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
2804
2843
  setInitiatives((current) => upsertEntityById(current, data));
2805
2844
  return data;
2806
2845
  }, [fetchPlanningEntities, runInitiativeMutation]);
2846
+ const updatePlanningIdentity = useCallback(async (entityType, id, patch) => {
2847
+ const res = await runInitiativeMutation(`/api/taskforce/${entityType}/${encodeURIComponent(id)}/identity`, {
2848
+ method: 'PATCH',
2849
+ headers: { 'Content-Type': 'application/json' },
2850
+ body: JSON.stringify(patch),
2851
+ });
2852
+ const data = await res.json().catch(() => ({}));
2853
+ if (!res.ok)
2854
+ throw new Error(data?.error || `Failed to update ${entityType} identity (${res.status})`);
2855
+ if (entityType === 'initiative') {
2856
+ setInitiatives((current) => upsertEntityById(current, data));
2857
+ }
2858
+ else {
2859
+ setWorkstreams((current) => upsertEntityById(current, data));
2860
+ }
2861
+ void fetchPlanningEntities();
2862
+ return data;
2863
+ }, [fetchPlanningEntities, runInitiativeMutation]);
2807
2864
  const refreshPlanningEntitiesInBackground = useCallback(() => {
2808
2865
  void fetchPlanningEntities();
2809
2866
  }, [fetchPlanningEntities]);
@@ -3784,6 +3841,22 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3784
3841
  durableTaskHttpRootActivationEnabled: runtimeConfigOverride.syncV3TaskCloudHttpRootActivation,
3785
3842
  workspaceId: currentWorkspaceId,
3786
3843
  });
3844
+ const promoteDescriptionImageDraft = useCallback(async () => {
3845
+ const draftId = descriptionImageDraftIdRef.current;
3846
+ const response = await fetch('/api/taskforce/context-upload/draft/promote', {
3847
+ method: 'POST',
3848
+ headers: { 'Content-Type': 'application/json' },
3849
+ body: JSON.stringify({
3850
+ workspaceId: currentWorkspaceId,
3851
+ draftUploadId: draftId,
3852
+ uploadPurpose: 'task-description-draft',
3853
+ }),
3854
+ });
3855
+ if (!response.ok) {
3856
+ throw new Error('Task was created, but its draft upload records could not be finalized.');
3857
+ }
3858
+ rotateDescriptionImageDraftId();
3859
+ }, [currentWorkspaceId, rotateDescriptionImageDraftId]);
3787
3860
  const { autoSaveState, flushAutoSave, scheduleWarningPrompt, handleSubmit, confirmScheduleWarning, cancelScheduleWarning, handleAddComment } = useTaskFormActions({
3788
3861
  activeCategories,
3789
3862
  activeTab,
@@ -3804,9 +3877,12 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3804
3877
  editingTaskId,
3805
3878
  fetchTasks,
3806
3879
  formTaxonomies,
3880
+ getAttachmentsDraft,
3807
3881
  getDescriptionDraft,
3808
3882
  getPreferredCategoryValue,
3809
3883
  getTitleDraft,
3884
+ awaitPendingDescriptionImageUpload,
3885
+ onTaskCreateAccepted: promoteDescriptionImageDraft,
3810
3886
  mergeTaskFromServer,
3811
3887
  onTaskCreated: handleEdit,
3812
3888
  workstreamInput,
@@ -3831,6 +3907,27 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3831
3907
  textDraftOccupied,
3832
3908
  type
3833
3909
  });
3910
+ const discardDescriptionImageDraft = useCallback(async () => {
3911
+ const draftId = descriptionImageDraftIdRef.current;
3912
+ rotateDescriptionImageDraftId();
3913
+ try {
3914
+ await awaitPendingDescriptionImageUpload();
3915
+ const response = await fetch('/api/taskforce/context-upload/draft/discard', {
3916
+ method: 'POST',
3917
+ headers: { 'Content-Type': 'application/json' },
3918
+ body: JSON.stringify({
3919
+ workspaceId: currentWorkspaceId,
3920
+ draftUploadId: draftId,
3921
+ uploadPurpose: 'task-description-draft',
3922
+ }),
3923
+ });
3924
+ if (!response.ok)
3925
+ throw new Error('Draft upload cleanup was rejected.');
3926
+ }
3927
+ catch (error) {
3928
+ console.warn('[Taskforce] Failed to clean discarded Description image uploads.', error);
3929
+ }
3930
+ }, [awaitPendingDescriptionImageUpload, currentWorkspaceId, rotateDescriptionImageDraftId]);
3834
3931
  useEffect(() => {
3835
3932
  flushAutoSaveRef.current = flushAutoSave;
3836
3933
  }, [flushAutoSave]);
@@ -4181,6 +4278,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
4181
4278
  fetchAssigneeOptions,
4182
4279
  createInitiative,
4183
4280
  updateInitiative,
4281
+ updatePlanningIdentity,
4184
4282
  archiveInitiative,
4185
4283
  unarchiveInitiative,
4186
4284
  createWorkstream,
@@ -4244,6 +4342,9 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
4244
4342
  setAttachments,
4245
4343
  attachmentsDirty,
4246
4344
  setAttachmentsDirty,
4345
+ descriptionImageUploadPending,
4346
+ descriptionImageDraftId,
4347
+ registerDescriptionImageUpload,
4247
4348
  descriptionFocused,
4248
4349
  setDescriptionFocused,
4249
4350
  showMarkdownHelp,
@@ -4253,6 +4354,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
4253
4354
  // Form Actions
4254
4355
  handleSubmit,
4255
4356
  resetForm,
4357
+ discardDescriptionImageDraft,
4256
4358
  handleAddComment,
4257
4359
  handleSetWorkstreamForCurrentTask,
4258
4360
  handleOpenTaskById,
@@ -35,12 +35,23 @@ export interface CanonicalAssetHelpersDeps {
35
35
  assetId: string;
36
36
  };
37
37
  }
38
+ export declare class CanonicalDocumentHeadStaleError extends Error {
39
+ readonly details: Record<string, unknown>;
40
+ readonly code = "MCP_DOCUMENT_HEAD_STALE";
41
+ readonly statusCode = 409;
42
+ constructor(details: Record<string, unknown>);
43
+ }
38
44
  export declare function createCanonicalAssetHelpers(deps: CanonicalAssetHelpersDeps): {
39
45
  normalizeAttachmentStorageKey: (pathValue: unknown, fsPathValue: unknown) => string | null;
40
46
  buildSyntheticAttachmentId: (taskId: string, attachment: TaskAttachmentRecord) => string;
41
47
  getAttachmentAssetRecord: (attachment: TaskAttachmentRecord) => WorkspaceAssetEntry | null;
42
48
  buildAttachmentDownloadUrl: (attachment: McpTaskAttachmentDescriptor) => string;
43
49
  buildDocumentDownloadUrl: (asset: WorkspaceAssetEntry | WorkspaceDocumentEntry) => string;
50
+ assertCanonicalDocumentHeadCurrent: (asset: WorkspaceAssetEntry | WorkspaceDocumentEntry) => import("../sync/engine/workspaceSyncPublishedContentResolver.js").WorkspaceSyncPublishedLegacyContent | import("../sync/engine/workspaceSyncPublishedContentResolver.js").WorkspaceSyncPublishedVersionedContent | {
51
+ source: "legacy";
52
+ physicalKey: string;
53
+ storageProvider: import("../storage/objectStorage.js").ObjectStorageProvider;
54
+ };
44
55
  readCanonicalAttachmentBuffer: (taskId: string, attachment: McpTaskAttachmentDescriptor, targetLabel?: string) => Promise<Buffer>;
45
56
  readCanonicalDocumentBuffer: (asset: WorkspaceAssetEntry | WorkspaceDocumentEntry) => Promise<Buffer>;
46
57
  toTaskAttachmentRecord: (attachment: string | TaskAttachmentRecord) => TaskAttachmentRecord;
@@ -68,6 +79,7 @@ export declare function createCanonicalAssetHelpers(deps: CanonicalAssetHelpersD
68
79
  scanEngine?: string | null;
69
80
  scanDetails?: string | null;
70
81
  createLink?: boolean;
82
+ displayName?: string;
71
83
  }) => Promise<WorkspaceAssetEntry | null>;
72
84
  ensureCanonicalTaskAttachment: (relativePathRaw: string, taskIdRaw: string, captionRaw: string | undefined, timestampRaw: string | undefined, role: WorkspaceAssetLinkRole, options?: Parameters<(relativePathRaw: string, targetTypeRaw: WorkspaceAssetLinkTargetType, targetIdRaw: string, captionRaw: string | undefined, timestampRaw: string | undefined, role: WorkspaceAssetLinkRole, options?: {
73
85
  originalFilename?: string;
@@ -80,6 +92,7 @@ export declare function createCanonicalAssetHelpers(deps: CanonicalAssetHelpersD
80
92
  scanEngine?: string | null;
81
93
  scanDetails?: string | null;
82
94
  createLink?: boolean;
95
+ displayName?: string;
83
96
  }) => Promise<WorkspaceAssetEntry | null>>[6]) => Promise<WorkspaceAssetEntry | null>;
84
97
  buildCanonicalWorkspaceAssetRelativePath: (params: {
85
98
  workspaceIdRaw: unknown;
@@ -3,6 +3,16 @@ import * as path from 'path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { verifyWorkspaceSyncPublishedContentBody, } from '../sync/engine/workspaceSyncPublishedContentResolver.js';
5
5
  import { normalizeRelativeStorageKey, resolveExistingStorageKeyFilePathInsideRoot, resolveStorageKeyWritePathInsideRoot } from '../utils/pathSafety.js';
6
+ export class CanonicalDocumentHeadStaleError extends Error {
7
+ details;
8
+ code = 'MCP_DOCUMENT_HEAD_STALE';
9
+ statusCode = 409;
10
+ constructor(details) {
11
+ super('The published document head does not match the current canonical document metadata.');
12
+ this.details = details;
13
+ this.name = 'CanonicalDocumentHeadStaleError';
14
+ }
15
+ }
6
16
  export function createCanonicalAssetHelpers(deps) {
7
17
  const normalizeAttachmentStorageKey = (pathValue, fsPathValue) => {
8
18
  const fsPathRaw = String(fsPathValue || '').trim().replace(/\\/g, '/');
@@ -50,6 +60,30 @@ export function createCanonicalAssetHelpers(deps) {
50
60
  legacyProvider: asset.storageProvider,
51
61
  });
52
62
  };
63
+ const assertCanonicalDocumentHeadCurrent = (asset) => {
64
+ const resolved = resolveCanonicalContentRead(asset, 'document');
65
+ if (resolved.source === 'deleted') {
66
+ throw new Error(`Document ${asset.assetId} not found in workspace ${deps.activeWorkspaceId}.`);
67
+ }
68
+ if (resolved.source === 'versioned' && (resolved.storageProvider !== asset.storageProvider
69
+ || resolved.contentSha256 !== asset.contentSha256
70
+ || resolved.sizeBytes !== asset.sizeBytes)) {
71
+ throw new CanonicalDocumentHeadStaleError({
72
+ assetId: asset.assetId,
73
+ currentVersion: asset.version,
74
+ currentStorageProvider: asset.storageProvider,
75
+ currentContentSha256: asset.contentSha256,
76
+ currentSizeBytes: asset.sizeBytes,
77
+ headVersionId: resolved.versionId,
78
+ headStorageProvider: resolved.storageProvider,
79
+ headContentSha256: resolved.contentSha256,
80
+ headSizeBytes: resolved.sizeBytes,
81
+ nextTool: 'update_document',
82
+ guidance: 'Retry update_document with the complete current body and expectedVersion to republish the canonical immutable head. Do not use the returned download until the repair succeeds.',
83
+ });
84
+ }
85
+ return resolved;
86
+ };
53
87
  const buildSyntheticAttachmentId = (taskId, attachment) => {
54
88
  const stableSource = [
55
89
  taskId,
@@ -157,10 +191,7 @@ export function createCanonicalAssetHelpers(deps) {
157
191
  if (asset.scanStatus === 'pending') {
158
192
  throw new Error('Document scan pending');
159
193
  }
160
- const resolved = resolveCanonicalContentRead(asset, 'document');
161
- if (resolved.source === 'deleted') {
162
- throw new Error(`Document ${asset.assetId} not found in workspace ${deps.activeWorkspaceId}.`);
163
- }
194
+ const resolved = assertCanonicalDocumentHeadCurrent(asset);
164
195
  if (resolved.storageProvider === 'r2') {
165
196
  if (!normalizeRelativeStorageKey(resolved.physicalKey)) {
166
197
  throw new Error(`Document ${asset.assetId} not found in workspace ${deps.activeWorkspaceId}.`);
@@ -324,7 +355,9 @@ export function createCanonicalAssetHelpers(deps) {
324
355
  targetType,
325
356
  targetId,
326
357
  role,
327
- displayName: kind === 'document' ? null : (captionRaw || null),
358
+ displayName: options?.displayName !== undefined
359
+ ? options.displayName
360
+ : kind === 'document' ? null : (captionRaw || null),
328
361
  updatedAt: timestamp,
329
362
  deletedAt: null
330
363
  });
@@ -338,6 +371,7 @@ export function createCanonicalAssetHelpers(deps) {
338
371
  getAttachmentAssetRecord,
339
372
  buildAttachmentDownloadUrl,
340
373
  buildDocumentDownloadUrl,
374
+ assertCanonicalDocumentHeadCurrent,
341
375
  readCanonicalAttachmentBuffer,
342
376
  readCanonicalDocumentBuffer,
343
377
  toTaskAttachmentRecord,
@@ -23,7 +23,7 @@ export function registerCollaborationTools(registerTool, executeTool) {
23
23
  });
24
24
  };
25
25
  register('list_assignees', (context) => ({
26
- description: context.describeTool('List eligible assignees for the active workspace. Returns human workspace members and AI profiles that can own tasks. Use query to match a known ID, label, or username without loading the full catalog. Calls without query or limit preserve full-catalog discovery.'),
26
+ description: context.describeTool('List eligible assignees for the active workspace. Includes members and AI profiles. Use query for a bounded ID, label, or username match.'),
27
27
  inputSchema: {
28
28
  properties: {
29
29
  query: stringProperty('Optional case-insensitive substring matched against assignee ID, label, and username. When non-empty, returns at most 25 matches unless limit is set.'),
@@ -32,7 +32,7 @@ export function registerCollaborationTools(registerTool, executeTool) {
32
32
  },
33
33
  }));
34
34
  register('get_current_profile', (context) => ({
35
- description: context.describeTool('Inspect current AI identity for this call (ambient session, connection bootstrap, or explicit profileToken). Returns identityBound (true if this invocation has a resolved identity), bindingSource, profile, taskFilter, suggestedArgs, and guidance. Stateless/scheduled agents: call with profileToken if no ambient bind; use the returned suggestedArgs (with assignee) for list_tasks and pass profileToken on writes. Call before mutating or to decide on resolve_profile.'),
35
+ description: context.describeTool('Inspect current AI identity for this call and write readiness. Stateless/scheduled agents may pass profileToken. Use before writes or to decide whether resolve_profile is needed.'),
36
36
  inputSchema: {
37
37
  properties: {
38
38
  profileToken: stringProperty('Optional AI profile token to inspect when no profile is currently bound in session state. Pass only a token previously issued to this AI identity.'),
@@ -40,24 +40,25 @@ export function registerCollaborationTools(registerTool, executeTool) {
40
40
  },
41
41
  }));
42
42
  register('resolve_profile', (context) => ({
43
- description: context.describeTool('Resolve or create this AI collaborator profile. Use a stable collaborator/product name that does not change with the active model. For stateless agents (no ambient binding): call with name + surfaceType (and profileToken to re-use); the response includes created, profileToken, profile, *plus* identityBound, bindingSource, taskFilter, suggestedArgs, nextTool, guidance. Store profileToken privately only. Use suggestedArgs for list_tasks; pass profileToken on mutating calls. Never write profileToken to repos, tasks, logs, or docs.'),
43
+ description: context.describeTool('Resolve or create this AI collaborator profile using a stable product name. Stateless agents use name + surfaceType and may reuse profileToken. If OAuth recovery returns candidates, retry with profileId. Store profileToken privately only; pass it on writes. Never write profileToken to repos, tasks, logs, or docs.'),
44
44
  inputSchema: {
45
45
  properties: {
46
- name: stringProperty('Stable display name for the AI collaborator or product identity. Do not include the active model name or version.'),
47
- icon: stringProperty('Optional icon name or emoji saved with the AI profile. Omit to leave unchanged when profileToken is provided.'),
48
- color: stringProperty('Optional signature color as a Taskforce palette token or hex color. Omit on first registration to receive a stable application-assigned product or palette default. Omit to leave unchanged when profileToken is provided; workspace user overrides remain authoritative.'),
49
- description: stringProperty('Optional short description stored on the AI profile. Omit to leave unchanged when profileToken is provided.'),
50
- role: stringProperty('Optional role label stored on the AI profile. Omit to leave unchanged when profileToken is provided.'),
51
- provider: stringProperty('Optional provider name stored as secondary diagnostic metadata on the AI profile. Omit to leave unchanged when profileToken is provided.'),
52
- model: stringProperty('Optional model name stored as secondary diagnostic metadata on the AI profile. Omit to leave unchanged when profileToken is provided.'),
46
+ name: stringProperty('Stable collaborator or product name; exclude model names and versions.'),
47
+ icon: stringProperty('Optional profile icon or emoji; omit to leave unchanged.'),
48
+ color: stringProperty('Optional Taskforce palette token or hex color; omit to use the assigned default or retain the current value.'),
49
+ description: stringProperty('Optional profile description; omit to leave unchanged.'),
50
+ role: stringProperty('Optional role label; omit to leave unchanged.'),
51
+ provider: stringProperty('Optional provider metadata; omit to leave unchanged.'),
52
+ model: stringProperty('Optional model metadata; omit to leave unchanged.'),
53
53
  surfaceType: {
54
54
  anyOf: [
55
55
  { type: 'string', enum: [...AI_PROFILE_SURFACE_TYPES] },
56
56
  { type: 'null' },
57
57
  ],
58
- description: 'Optional self-declared AI surface type. Use agent for a persistent named AI collaborator in Taskforce, cli for terminal-native coding agents, coding_tool for coding surfaces that are not specifically CLI or IDE-native, chat_app for a conversational app identity, and ide for an IDE-native copilot identity. If you are unsure or none of those fit, choose agent. Omit to leave unchanged; pass null or an empty string to clear.',
58
+ description: 'Optional surface: agent (persistent collaborator), cli (terminal coding agent), coding_tool (other coding surface), chat_app (chat app), or ide (IDE copilot). Use agent when unsure. Omit to retain; null or empty clears.',
59
59
  },
60
- profileToken: stringProperty('Previously issued Taskforce profile token for this AI identity. Keep it in private agent/client state; when provided, Taskforce resolves the existing profile instead of creating a new one, and applies any supplied presentation field updates to that profile.'),
60
+ profileId: stringProperty('Compatible profile ID from a prior selection-required response.'),
61
+ profileToken: stringProperty('Private token previously issued for this AI identity; reuses and optionally updates that profile.'),
61
62
  },
62
63
  required: ['name'],
63
64
  },
@@ -73,11 +74,12 @@ export function registerCollaborationTools(registerTool, executeTool) {
73
74
  },
74
75
  }));
75
76
  register('add_comment', (context) => ({
76
- description: context.describeTool('Add a comment to a task. Use this for agent progress notes, handoff summaries, decision logs, and audit trails. Comments are visible in the task timeline.'),
77
+ description: context.describeTool('Add a comment to a task. Pass idempotencyKey when retrying after an ambiguous timeout; reuse it only with the exact same target and text.'),
77
78
  inputSchema: {
78
79
  properties: {
79
80
  id: stringProperty('Task ID or task reference such as T-123.'),
80
81
  text: stringProperty('Comment text.'),
82
+ idempotencyKey: { type: 'string', minLength: 1, maxLength: 128, description: 'Optional opaque retry key scoped to the active workspace, actor, and tool.' },
81
83
  },
82
84
  required: ['id', 'text'],
83
85
  },