@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
@@ -2,6 +2,7 @@ import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
2
2
  export type AttachmentLinkInput = string | {
3
3
  path: string;
4
4
  fsPath?: string;
5
+ assetId?: string | null;
5
6
  caption?: string;
6
7
  displayName?: string;
7
8
  originalFilename?: string;
@@ -12,4 +13,6 @@ export interface AttachmentLinkServiceDeps {
12
13
  tenantId: string;
13
14
  normalizeWorkspaceId: (workspaceId: string) => string;
14
15
  }
16
+ export type AttachmentLinkTargetType = 'task' | 'initiative' | 'workstream';
17
+ export declare function syncCanonicalAttachmentLinksForTargetService(deps: AttachmentLinkServiceDeps, targetType: AttachmentLinkTargetType, targetId: string, workspaceId: string, attachments: AttachmentLinkInput[]): void;
15
18
  export declare function syncCanonicalTaskAttachmentLinksService(deps: AttachmentLinkServiceDeps, taskId: string, workspaceId: string, attachments: AttachmentLinkInput[]): void;
@@ -63,6 +63,25 @@ function normalizeAttachmentStorageKey(pathValue, fsPathValue) {
63
63
  }
64
64
  return null;
65
65
  }
66
+ function extractAttachmentAssetId(attachment) {
67
+ const explicitAssetId = typeof attachment === 'string'
68
+ ? ''
69
+ : String(attachment.assetId || '').trim();
70
+ if (explicitAssetId)
71
+ return explicitAssetId;
72
+ const pathValue = typeof attachment === 'string'
73
+ ? attachment.trim()
74
+ : String(attachment.path || '').trim();
75
+ const match = pathValue.match(/^\/api\/taskforce\/documents\/([^/?#]+)\/content(?:[?#].*)?$/);
76
+ if (!match)
77
+ return null;
78
+ try {
79
+ return decodeURIComponent(match[1]).trim() || null;
80
+ }
81
+ catch {
82
+ return match[1].trim() || null;
83
+ }
84
+ }
66
85
  function ensureExternalReferenceAsset(deps, storageKeyRaw, workspaceId, attachment) {
67
86
  const storageKey = String(storageKeyRaw || '').trim();
68
87
  const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
@@ -124,25 +143,31 @@ function ensureExternalReferenceAsset(deps, storageKeyRaw, workspaceId, attachme
124
143
  `).run(assetId, deps.tenantId, normalizedWorkspaceId, 'file', logicalName, logicalName, mimeType, 'external', storageKey, urlHash, Buffer.byteLength(storageKey, 'utf8'), null, null, 1, 'clean', null, null, null, timestamp, timestamp);
125
144
  return assetId;
126
145
  }
127
- export function syncCanonicalTaskAttachmentLinksService(deps, taskId, workspaceId, attachments) {
128
- const normalizedTaskId = String(taskId || '').trim();
146
+ export function syncCanonicalAttachmentLinksForTargetService(deps, targetType, targetId, workspaceId, attachments) {
147
+ const normalizedTargetId = String(targetId || '').trim();
129
148
  const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
130
- if (!normalizedTaskId)
149
+ if (!normalizedTargetId)
131
150
  return;
132
151
  const supportsTargetType = supportsWorkspaceAssetLinkTargetType(deps.db);
152
+ const safeTargetType = targetType === 'initiative' || targetType === 'workstream'
153
+ ? targetType
154
+ : 'task';
155
+ const nowIso = new Date().toISOString();
133
156
  if (supportsTargetType) {
134
157
  deps.db.prepare(`
135
158
  UPDATE workspace_asset_links
136
159
  SET deleted_at = ?, updated_at = ?
137
- WHERE tenant_id = ? AND workspace_id = ? AND target_type = 'task' AND task_id = ? AND deleted_at IS NULL
138
- `).run(new Date().toISOString(), new Date().toISOString(), deps.tenantId, normalizedWorkspaceId, normalizedTaskId);
160
+ WHERE tenant_id = ? AND workspace_id = ? AND target_type = ? AND task_id = ? AND deleted_at IS NULL
161
+ `).run(nowIso, nowIso, deps.tenantId, normalizedWorkspaceId, safeTargetType, normalizedTargetId);
139
162
  }
140
163
  else {
164
+ if (safeTargetType !== 'task')
165
+ return;
141
166
  deps.db.prepare(`
142
167
  UPDATE workspace_asset_links
143
168
  SET deleted_at = ?, updated_at = ?
144
169
  WHERE tenant_id = ? AND workspace_id = ? AND task_id = ? AND deleted_at IS NULL
145
- `).run(new Date().toISOString(), new Date().toISOString(), deps.tenantId, normalizedWorkspaceId, normalizedTaskId);
170
+ `).run(nowIso, nowIso, deps.tenantId, normalizedWorkspaceId, normalizedTargetId);
146
171
  }
147
172
  const selectAsset = deps.db.prepare(`
148
173
  SELECT asset_id, kind
@@ -150,11 +175,17 @@ export function syncCanonicalTaskAttachmentLinksService(deps, taskId, workspaceI
150
175
  WHERE tenant_id = ? AND workspace_id = ? AND storage_key = ? AND deleted_at IS NULL
151
176
  LIMIT 1
152
177
  `);
178
+ const selectAssetById = deps.db.prepare(`
179
+ SELECT asset_id, kind
180
+ FROM workspace_assets
181
+ WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND deleted_at IS NULL
182
+ LIMIT 1
183
+ `);
153
184
  const upsertLink = supportsTargetType
154
185
  ? deps.db.prepare(`
155
186
  INSERT INTO workspace_asset_links (
156
187
  tenant_id, workspace_id, asset_id, target_type, task_id, role, display_name, sort_order, created_at, updated_at, deleted_at
157
- ) VALUES (?, ?, ?, 'task', ?, ?, ?, ?, ?, ?, NULL)
188
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
158
189
  ON CONFLICT (tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
159
190
  target_type = excluded.target_type,
160
191
  display_name = excluded.display_name,
@@ -174,16 +205,27 @@ export function syncCanonicalTaskAttachmentLinksService(deps, taskId, workspaceI
174
205
  `);
175
206
  attachments.forEach((attachment, index) => {
176
207
  const storageKey = normalizeAttachmentStorageKey(typeof attachment === 'string' ? attachment : attachment.path, typeof attachment === 'string' ? null : attachment.fsPath);
177
- if (!storageKey)
208
+ const attachmentAssetId = extractAttachmentAssetId(attachment);
209
+ if (!storageKey && !attachmentAssetId)
178
210
  return;
179
- const row = selectAsset.get(deps.tenantId, normalizedWorkspaceId, storageKey);
211
+ const row = (storageKey
212
+ ? selectAsset.get(deps.tenantId, normalizedWorkspaceId, storageKey)
213
+ : null)
214
+ || (attachmentAssetId
215
+ ? selectAssetById.get(deps.tenantId, normalizedWorkspaceId, attachmentAssetId)
216
+ : null);
217
+ const isExternalStorageKey = typeof storageKey === 'string' && /^https?:\/\//i.test(storageKey);
218
+ const hasExplicitFileStorageKey = typeof storageKey === 'string'
219
+ && typeof attachment !== 'string'
220
+ && typeof attachment.fsPath === 'string'
221
+ && attachment.fsPath.trim().length > 0;
180
222
  const assetId = String(row?.asset_id || '').trim()
181
- || ((/^https?:\/\//i.test(storageKey) || (typeof attachment !== 'string' && typeof attachment.fsPath === 'string' && attachment.fsPath.trim().length > 0))
223
+ || (isExternalStorageKey || hasExplicitFileStorageKey
182
224
  ? ensureExternalReferenceAsset(deps, storageKey, normalizedWorkspaceId, attachment)
183
225
  : '');
184
226
  if (!assetId)
185
227
  return;
186
- const role = /^https?:\/\//i.test(storageKey)
228
+ const role = isExternalStorageKey
187
229
  ? 'reference'
188
230
  : String(row?.kind || '').trim() === 'image'
189
231
  ? 'image'
@@ -192,8 +234,11 @@ export function syncCanonicalTaskAttachmentLinksService(deps, taskId, workspaceI
192
234
  ? null
193
235
  : (typeof attachment === 'string'
194
236
  ? null
195
- : buildAttachmentDisplayName(attachment, /^https?:\/\//i.test(storageKey) ? null : path.basename(storageKey)));
237
+ : buildAttachmentDisplayName(attachment, isExternalStorageKey || !storageKey ? null : path.basename(storageKey)));
196
238
  const timestamp = typeof attachment === 'string' ? new Date().toISOString() : (attachment.timestamp || new Date().toISOString());
197
- upsertLink.run(deps.tenantId, normalizedWorkspaceId, assetId, normalizedTaskId, role, displayName, index, timestamp, timestamp);
239
+ upsertLink.run(deps.tenantId, normalizedWorkspaceId, assetId, safeTargetType, normalizedTargetId, role, displayName, index, timestamp, timestamp);
198
240
  });
199
241
  }
242
+ export function syncCanonicalTaskAttachmentLinksService(deps, taskId, workspaceId, attachments) {
243
+ syncCanonicalAttachmentLinksForTargetService(deps, 'task', taskId, workspaceId, attachments);
244
+ }
@@ -194,6 +194,21 @@ export function getGlobalSettingsService(deps) {
194
194
  : {}),
195
195
  ...(typeof global.site.pricingPageEnabled === 'boolean'
196
196
  ? { pricingPageEnabled: global.site.pricingPageEnabled }
197
+ : {}),
198
+ ...(typeof global.site.animatedBackgroundEnabled === 'boolean'
199
+ ? { animatedBackgroundEnabled: global.site.animatedBackgroundEnabled }
200
+ : {}),
201
+ ...(global.site.siteTheme === 'light' || global.site.siteTheme === 'dark'
202
+ ? { siteTheme: global.site.siteTheme }
203
+ : {}),
204
+ ...(typeof global.site.pricingPageTitle === 'string'
205
+ ? { pricingPageTitle: global.site.pricingPageTitle.trim() }
206
+ : {}),
207
+ ...(typeof global.site.pricingPageDescription === 'string'
208
+ ? { pricingPageDescription: global.site.pricingPageDescription.trim() }
209
+ : {}),
210
+ ...(typeof global.site.pricingPageShowPublicDescriptions === 'boolean'
211
+ ? { pricingPageShowPublicDescriptions: global.site.pricingPageShowPublicDescriptions }
197
212
  : {})
198
213
  }
199
214
  }
@@ -407,6 +422,21 @@ export function updateGlobalSettingsService(deps, settings) {
407
422
  : {}),
408
423
  ...(typeof incoming.pricingPageEnabled === 'boolean'
409
424
  ? { pricingPageEnabled: incoming.pricingPageEnabled }
425
+ : {}),
426
+ ...(typeof incoming.animatedBackgroundEnabled === 'boolean'
427
+ ? { animatedBackgroundEnabled: incoming.animatedBackgroundEnabled }
428
+ : {}),
429
+ ...(incoming.siteTheme === 'light' || incoming.siteTheme === 'dark'
430
+ ? { siteTheme: incoming.siteTheme }
431
+ : {}),
432
+ ...(typeof incoming.pricingPageTitle === 'string'
433
+ ? { pricingPageTitle: incoming.pricingPageTitle.trim() }
434
+ : {}),
435
+ ...(typeof incoming.pricingPageDescription === 'string'
436
+ ? { pricingPageDescription: incoming.pricingPageDescription.trim() }
437
+ : {}),
438
+ ...(typeof incoming.pricingPageShowPublicDescriptions === 'boolean'
439
+ ? { pricingPageShowPublicDescriptions: incoming.pricingPageShowPublicDescriptions }
410
440
  : {})
411
441
  };
412
442
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { TaskforceCore } from './Taskforce';
6
+ const roots = [];
7
+ function makeProjectRoot() {
8
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-global-settings-'));
9
+ roots.push(root);
10
+ return root;
11
+ }
12
+ afterEach(() => {
13
+ while (roots.length > 0) {
14
+ const root = roots.pop();
15
+ if (root)
16
+ fs.rmSync(root, { recursive: true, force: true });
17
+ }
18
+ });
19
+ describe('TaskforceCore global site settings', () => {
20
+ it('persists pricing page title and description', () => {
21
+ const projectRoot = makeProjectRoot();
22
+ const storagePath = '.taskforce';
23
+ const core = new TaskforceCore({ projectRoot, storagePath });
24
+ core.updateGlobalSettings({
25
+ site: {
26
+ pricingPageTitle: 'Choose Your Plan',
27
+ pricingPageDescription: 'Compare plans and pick the right fit.',
28
+ pricingPageShowPublicDescriptions: false
29
+ }
30
+ });
31
+ expect(core.getGlobalSettings().site).toMatchObject({
32
+ pricingPageTitle: 'Choose Your Plan',
33
+ pricingPageDescription: 'Compare plans and pick the right fit.',
34
+ pricingPageShowPublicDescriptions: false
35
+ });
36
+ core.close();
37
+ const reopened = new TaskforceCore({ projectRoot, storagePath });
38
+ expect(reopened.getGlobalSettings().site).toMatchObject({
39
+ pricingPageTitle: 'Choose Your Plan',
40
+ pricingPageDescription: 'Compare plans and pick the right fit.',
41
+ pricingPageShowPublicDescriptions: false
42
+ });
43
+ reopened.close();
44
+ });
45
+ });
@@ -60,6 +60,11 @@ export declare class McpTokenService {
60
60
  workspaceId: string;
61
61
  tokenId: string;
62
62
  }): McpAccessTokenRecord;
63
+ deleteMcpAccessToken(input: {
64
+ actorUserId: string;
65
+ workspaceId: string;
66
+ tokenId: string;
67
+ }): boolean;
63
68
  regenerateMcpAccessToken(input: {
64
69
  actorUserId: string;
65
70
  workspaceId: string;
@@ -111,6 +116,13 @@ export declare class McpTokenService {
111
116
  grantId: string;
112
117
  }): McpOAuthConnectorGrantRecord;
113
118
  authenticateMcpOAuthAccessToken(token: string, workspaceId?: string): McpOAuthAccessTokenAuthResult | null;
119
+ bindMcpOAuthConnectorGrantAiProfile(input: {
120
+ grantId: string;
121
+ workspaceId: string;
122
+ profileId: string;
123
+ profileToken: string;
124
+ profileName: string;
125
+ }): boolean;
114
126
  recordMcpOAuthConnectorGrantUsage(input: {
115
127
  grantId: string;
116
128
  workspaceId: string;
@@ -518,6 +518,23 @@ export class McpTokenService {
518
518
  }
519
519
  return this.rowToMcpAccessTokenRecord(updated);
520
520
  }
521
+ deleteMcpAccessToken(input) {
522
+ this.getActiveWorkspaceMemberForMcp(input.actorUserId, input.workspaceId);
523
+ const row = this.getMcpAccessTokenRow(input.tokenId, input.workspaceId);
524
+ if (!row) {
525
+ throw new TaskforceRuleError('MCP token not found', 404, 'MCP_TOKEN_NOT_FOUND');
526
+ }
527
+ this.assertCanManageOwnMcpAccessToken(input.actorUserId, row);
528
+ const status = this.resolveMcpAccessTokenStatus(row);
529
+ if (status === 'active') {
530
+ throw new TaskforceRuleError('Active MCP tokens must be revoked before deletion', 400, 'MCP_TOKEN_DELETE_ACTIVE');
531
+ }
532
+ const result = this.db.prepare(`
533
+ DELETE FROM mcp_access_tokens
534
+ WHERE tenant_id = ? AND workspace_id = ? AND id = ?
535
+ `).run(this.tenantId, input.workspaceId, row.id);
536
+ return Number(result.changes || 0) > 0;
537
+ }
521
538
  regenerateMcpAccessToken(input) {
522
539
  this.getActiveWorkspaceMemberForMcp(input.actorUserId, input.workspaceId);
523
540
  const existing = this.getMcpAccessTokenRow(input.tokenId, input.workspaceId);
@@ -872,7 +889,8 @@ export class McpTokenService {
872
889
  if (!parsed)
873
890
  return null;
874
891
  const row = this.db.prepare(`
875
- SELECT id, provider, client_id, user_id, workspace_id, scopes_json, token_hash, status, revoked_at
892
+ SELECT id, provider, client_id, user_id, workspace_id, scopes_json, token_hash, status, revoked_at,
893
+ ai_profile_id, ai_profile_token, ai_profile_name
876
894
  FROM mcp_oauth_connector_grants
877
895
  WHERE tenant_id = ?
878
896
  AND token_prefix = ?
@@ -902,8 +920,33 @@ export class McpTokenService {
902
920
  provider: this.normalizeMcpOAuthProvider(row.provider),
903
921
  scopes,
904
922
  status: this.resolveMcpOAuthGrantStatus(row),
923
+ aiProfileId: row?.ai_profile_id ? String(row.ai_profile_id) : null,
924
+ aiProfileToken: row?.ai_profile_token ? String(row.ai_profile_token) : null,
925
+ aiProfileName: row?.ai_profile_name ? String(row.ai_profile_name) : null,
905
926
  };
906
927
  }
928
+ bindMcpOAuthConnectorGrantAiProfile(input) {
929
+ const grantId = String(input.grantId || '').trim();
930
+ const workspaceId = String(input.workspaceId || '').trim();
931
+ const profileId = String(input.profileId || '').trim();
932
+ const profileToken = String(input.profileToken || '').trim();
933
+ const profileName = String(input.profileName || '').trim();
934
+ if (!grantId || !workspaceId || !profileId || !profileToken || !profileName)
935
+ return false;
936
+ const now = new Date().toISOString();
937
+ const result = this.db.prepare(`
938
+ UPDATE mcp_oauth_connector_grants
939
+ SET ai_profile_id = ?,
940
+ ai_profile_token = ?,
941
+ ai_profile_name = ?,
942
+ updated_at = ?
943
+ WHERE tenant_id = ?
944
+ AND workspace_id = ?
945
+ AND id = ?
946
+ AND status = 'active'
947
+ `).run(profileId, profileToken, profileName, now, this.tenantId, workspaceId, grantId);
948
+ return Number(result?.changes || 0) > 0;
949
+ }
907
950
  recordMcpOAuthConnectorGrantUsage(input) {
908
951
  const grantId = String(input.grantId || '').trim();
909
952
  const workspaceId = String(input.workspaceId || '').trim();
@@ -20,6 +20,14 @@ export declare class PlanEntitlementService {
20
20
  private normalizeOptionalCatalogMarketingCopy;
21
21
  private canonicalizePlanFeatureKey;
22
22
  private normalizeOptionalCatalogDisplayOrder;
23
+ private normalizeOptionalCatalogDescriptionVisible;
24
+ private isBuiltInPlanFeatureKey;
25
+ private normalizeRequiredCatalogLabel;
26
+ private slugifyCustomPlanFeatureLabel;
27
+ private featureKeyExists;
28
+ private generateUniqueCustomPlanFeatureKey;
29
+ private listCustomPlanFeatureCatalogEntries;
30
+ private getCustomPlanFeatureCatalogEntry;
23
31
  private listPlanFeatureCatalogOverrideMap;
24
32
  normalizeEntitlementState(value: unknown): EntitlementState;
25
33
  private normalizeFeatureAccess;
@@ -171,10 +179,22 @@ export declare class PlanEntitlementService {
171
179
  };
172
180
  listPlanCatalog(): PlanCatalogSnapshot[];
173
181
  listPlanFeatureCatalog(): PlanFeatureCatalogItem[];
182
+ createCustomPlanFeatureCatalogEntry(input: {
183
+ label: string;
184
+ description?: string | null;
185
+ publicLabel?: string | null;
186
+ publicDescription?: string | null;
187
+ publicDescriptionVisible?: boolean | null;
188
+ publicDisplayOrder?: number | null;
189
+ }): PlanFeatureCatalogItem;
190
+ deleteCustomPlanFeatureCatalogEntry(featureKeyRaw: unknown): void;
174
191
  updatePlanFeatureCatalogMarketingCopy(input: {
175
192
  featureKey: string;
193
+ label?: string | null;
194
+ description?: string | null;
176
195
  publicLabel?: string | null;
177
196
  publicDescription?: string | null;
197
+ publicDescriptionVisible?: boolean | null;
178
198
  publicDisplayOrder?: number | null;
179
199
  }): PlanFeatureCatalogItem;
180
200
  listPlanVersions(planId: string): PlanVersionSnapshot[];