@taskforcehq/taskforce 0.3.303 → 0.3.305

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 (237) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +102 -5
  3. package/dist/TaskforceCore.js +635 -238
  4. package/dist/TaskforceCore.test.js +2243 -495
  5. package/dist/annotatedAttachments/service.d.ts +1 -1
  6. package/dist/annotatedAttachments/types.d.ts +1 -1
  7. package/dist/compat/workspaceSyncCompat.js +6 -0
  8. package/dist/components/features/AgentsModule.js +1 -1
  9. package/dist/components/features/AgentsModule.test.js +1 -1
  10. package/dist/components/features/DocumentIndex.d.ts +1 -1
  11. package/dist/components/features/DocumentIndex.js +1 -1
  12. package/dist/components/features/DocumentViewer.d.ts +1 -1
  13. package/dist/components/features/DocumentViewer.js +32 -9
  14. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  15. package/dist/components/features/DocumentWorkspace.js +12 -155
  16. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  17. package/dist/components/features/TaskSettings.js +87 -41
  18. package/dist/components/features/TaskSettings.test.js +180 -40
  19. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  20. package/dist/components/features/documentWorkspaceModel.js +57 -0
  21. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  22. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  23. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  24. package/dist/components/task/TaskActionHeader.js +3 -3
  25. package/dist/components/task/TaskActionHeader.test.js +8 -0
  26. package/dist/components/task/TaskCard.js +16 -1
  27. package/dist/components/task/TaskCard.test.js +31 -0
  28. package/dist/components/task/TaskForm.d.ts +9 -8
  29. package/dist/components/task/TaskForm.js +374 -219
  30. package/dist/components/task/TaskForm.test.js +482 -361
  31. package/dist/components/task/TaskKanban.d.ts +1 -1
  32. package/dist/components/task/TaskKanban.js +33 -161
  33. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  34. package/dist/components/task/TaskStatusActions.js +8 -3
  35. package/dist/components/task/TaskStatusActions.test.js +9 -0
  36. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  37. package/dist/components/task/taskKanbanDropRules.js +187 -0
  38. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  39. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  40. package/dist/components/ui/Modal.d.ts +1 -1
  41. package/dist/components/ui/Modal.js +1 -0
  42. package/dist/components/views/AppShellHeader.js +5 -1
  43. package/dist/components/views/AppShellHeader.test.js +4 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  45. package/dist/components/views/PlanComparisonPage.js +9 -10
  46. package/dist/components/views/PlanComparisonPage.test.js +55 -41
  47. package/dist/components/views/PlansPage.d.ts +5 -1
  48. package/dist/components/views/PlansPage.js +150 -81
  49. package/dist/components/views/PlansPage.test.js +367 -41
  50. package/dist/components/views/StandaloneLayout.js +135 -54
  51. package/dist/components/views/WidgetView.js +11 -2
  52. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  53. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  54. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  55. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  56. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  57. package/dist/config/envSchema.js +1 -1
  58. package/dist/core/AiProfileService.d.ts +6 -1
  59. package/dist/core/AiProfileService.js +161 -16
  60. package/dist/core/AiProfileService.test.js +3 -1
  61. package/dist/core/AiProfiles.test.js +200 -0
  62. package/dist/core/AuthTokenService.test.d.ts +1 -0
  63. package/dist/core/AuthTokenService.test.js +78 -0
  64. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  65. package/dist/core/EntitlementsPolicy.test.js +75 -13
  66. package/dist/core/PlanEntitlementService.d.ts +9 -1
  67. package/dist/core/PlanEntitlementService.js +144 -19
  68. package/dist/core/PlanVersionPolicy.test.js +19 -26
  69. package/dist/core/SignupMonetizationSettings.test.js +75 -15
  70. package/dist/core/SystemAdmin.test.js +212 -56
  71. package/dist/core/TaskActivityService.d.ts +4 -1
  72. package/dist/core/TaskActivityService.js +45 -0
  73. package/dist/core/TaskAuditTimeline.test.js +52 -1
  74. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  75. package/dist/core/Taskforce.d.ts +18 -11
  76. package/dist/core/Taskforce.js +93 -12
  77. package/dist/core/WorkspacePlanMode.test.js +73 -8
  78. package/dist/core/shared.d.ts +2 -0
  79. package/dist/core/shared.js +11 -0
  80. package/dist/documentReviews/service.d.ts +1 -1
  81. package/dist/documentReviews/types.d.ts +1 -1
  82. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  83. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  84. package/dist/hooks/sync/auth.d.ts +37 -0
  85. package/dist/hooks/sync/auth.js +63 -0
  86. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  87. package/dist/hooks/sync/bootstrap.js +52 -0
  88. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  89. package/dist/hooks/sync/orchestratorShared.js +209 -0
  90. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  91. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  92. package/dist/hooks/sync/realtime.d.ts +25 -0
  93. package/dist/hooks/sync/realtime.js +60 -0
  94. package/dist/hooks/sync/recovery.d.ts +69 -0
  95. package/dist/hooks/sync/recovery.js +118 -0
  96. package/dist/hooks/sync/transfers.d.ts +123 -0
  97. package/dist/hooks/sync/transfers.js +435 -0
  98. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  99. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  100. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  101. package/dist/hooks/ui/useResourceExport.js +5 -2
  102. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  103. package/dist/hooks/useRealtimeSync.js +2 -1
  104. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  105. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  106. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  107. package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
  108. package/dist/hooks/useSyncOrchestrator.js +388 -879
  109. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
  110. package/dist/hooks/useTaskMutations.d.ts +2 -1
  111. package/dist/hooks/useTaskMutations.js +57 -7
  112. package/dist/hooks/useTaskMutations.test.js +176 -0
  113. package/dist/hooks/useTaskforce.d.ts +13 -4
  114. package/dist/hooks/useTaskforce.js +302 -817
  115. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  116. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  117. package/dist/hooks/useWorkspaceSyncController.js +33 -11
  118. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  119. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  120. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  121. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  122. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  123. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  124. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  125. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  126. package/dist/localization/locales/en-US.js +2 -2
  127. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  128. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  129. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  130. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  131. package/dist/mcp/clientIntegrations.d.ts +12 -1
  132. package/dist/mcp/clientIntegrations.js +161 -6
  133. package/dist/mcp/clientIntegrations.test.js +76 -4
  134. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  135. package/dist/mcp/collaborationRegistrar.js +71 -0
  136. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  137. package/dist/mcp/documentAssetRegistrar.js +346 -0
  138. package/dist/mcp/documentHelpers.d.ts +100 -0
  139. package/dist/mcp/documentHelpers.js +161 -0
  140. package/dist/mcp/runtime.js +2455 -3925
  141. package/dist/mcp/runtime.test.js +240 -8
  142. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  143. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  144. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  145. package/dist/mcp/taskAttachmentTypes.js +1 -0
  146. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  147. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  148. package/dist/mcp/toolCatalog.d.ts +35 -0
  149. package/dist/mcp/toolCatalog.js +3 -0
  150. package/dist/plugins/vite.js +1 -1
  151. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  152. package/dist/runtime/appGateDefinitions.js +1 -1
  153. package/dist/server/index.d.ts +1 -0
  154. package/dist/server/index.js +46 -3
  155. package/dist/server/index.test.js +12 -1
  156. package/dist/server/routes/admin.d.ts +1 -5
  157. package/dist/server/routes/admin.js +151 -80
  158. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  159. package/dist/server/routes/annotatedAttachments.js +1 -1
  160. package/dist/server/routes/auth.d.ts +1 -4
  161. package/dist/server/routes/auth.js +59 -64
  162. package/dist/server/routes/authSupport.d.ts +30 -0
  163. package/dist/server/routes/authSupport.js +81 -0
  164. package/dist/server/routes/billing.d.ts +1 -1
  165. package/dist/server/routes/billing.js +122 -255
  166. package/dist/server/routes/billing.test.js +257 -86
  167. package/dist/server/routes/documentReviews.d.ts +1 -1
  168. package/dist/server/routes/documentReviews.js +1 -1
  169. package/dist/server/routes/documents.d.ts +4 -2
  170. package/dist/server/routes/documents.js +73 -7
  171. package/dist/server/routes/primitives.d.ts +11 -0
  172. package/dist/server/routes/primitives.js +73 -0
  173. package/dist/server/routes/resources.d.ts +1 -1
  174. package/dist/server/routes/resources.js +1 -1
  175. package/dist/server/routes/shared.d.ts +31 -1
  176. package/dist/server/routes/shared.js +26 -0
  177. package/dist/server/routes/sync.d.ts +1 -1
  178. package/dist/server/routes/sync.integration.test.js +20 -0
  179. package/dist/server/routes/sync.js +111 -238
  180. package/dist/server/routes/tasks.d.ts +1 -1
  181. package/dist/server/routes/tasks.js +50 -8
  182. package/dist/server/routes/workspaces.d.ts +1 -1
  183. package/dist/server/routes/workspaces.js +1 -1
  184. package/dist/server/routes.d.ts +2 -9
  185. package/dist/server/routes.js +84 -181
  186. package/dist/server/routes.test.js +912 -32
  187. package/dist/shared/taskActor.d.ts +10 -0
  188. package/dist/shared/taskActor.js +1 -0
  189. package/dist/sync/collaborationSyncPayload.js +4 -0
  190. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  191. package/dist/sync/collaborationSyncState.d.ts +3 -1
  192. package/dist/sync/collaborationSyncState.js +19 -0
  193. package/dist/sync/contentSyncState.js +28 -17
  194. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  195. package/dist/sync/syncApplyHandlers.js +370 -8
  196. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  197. package/dist/sync/syncApplyHandlers.test.js +379 -0
  198. package/dist/sync/syncService.d.ts +9 -1
  199. package/dist/sync/syncService.js +44 -2
  200. package/dist/sync/syncService.test.js +62 -0
  201. package/dist/sync/workspacePullFeed.d.ts +1 -0
  202. package/dist/sync/workspacePullFeed.js +66 -59
  203. package/dist/sync/workspacePullFeed.test.js +72 -4
  204. package/dist/sync/workspaceSyncModel.d.ts +25 -1
  205. package/dist/sync/workspaceSyncModel.js +77 -3
  206. package/dist/sync/workspaceSyncModel.test.js +153 -0
  207. package/dist/sync/workspaceSyncState.d.ts +2 -0
  208. package/dist/sync/workspaceSyncState.js +1 -0
  209. package/dist/types.d.ts +2 -10
  210. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
  211. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  212. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  213. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  214. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  215. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  216. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  217. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  218. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
  219. package/dist/ui/assets/index-C2-OXZV7.css +1 -0
  220. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  221. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
  222. package/dist/ui/index.html +4 -4
  223. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  224. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  225. package/dist/utils/taskActivity.js +4 -0
  226. package/dist/utils/taskActivity.test.js +25 -1
  227. package/package.json +10 -1
  228. package/scripts/check-cloud-mcp.mjs +83 -0
  229. package/scripts/playwright-auth.sh +9 -1
  230. package/scripts/run-billing-performance-smoke.sh +24 -0
  231. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  232. package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
  233. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  234. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  235. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  236. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  237. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -0,0 +1,418 @@
1
+ import { bindDomainTool } from './toolCatalog.js';
2
+ function stringProperty(description, enumValues) {
3
+ return enumValues ? { type: 'string', description, enum: enumValues } : { type: 'string', description };
4
+ }
5
+ function numberProperty(description) {
6
+ return { type: 'number', description };
7
+ }
8
+ function booleanProperty(description) {
9
+ return { type: 'boolean', description };
10
+ }
11
+ function arrayProperty(description, items) {
12
+ return { type: 'array', description, items };
13
+ }
14
+ export function registerTaskPlanningTools(registerTool, executeTool) {
15
+ const register = (name, buildDescriptor) => {
16
+ registerTool({
17
+ name,
18
+ buildDescriptor: (context) => {
19
+ const descriptor = buildDescriptor(context);
20
+ return {
21
+ name,
22
+ description: descriptor.description,
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: descriptor.inputSchema.properties,
26
+ ...(descriptor.inputSchema.required ? { required: descriptor.inputSchema.required } : {}),
27
+ },
28
+ };
29
+ },
30
+ handler: bindDomainTool(executeTool, name),
31
+ });
32
+ };
33
+ register('list_tasks', (context) => ({
34
+ description: context.describeTool('List active tasks with optional filters. Supports filtering by status, assignee, priority, type, and category. Use openOnly=true to exclude done/cancelled. Use format=json for programmatic use. Default sort is by created date descending. Returns a compact summary of each task — description, comments, and activity are omitted to keep responses concise. Use get_task to retrieve full details for a specific task.'),
35
+ inputSchema: {
36
+ properties: {
37
+ format: stringProperty('Toggle output format (default: markdown).', ['markdown', 'json']),
38
+ category: stringProperty('Filter by category.'),
39
+ status: stringProperty('Filter by status.', ['task', 'on-hold', 'in-progress', 'review', 'done', 'cancelled']),
40
+ openOnly: booleanProperty('When true, only include open tasks (task, on-hold, in-progress, review).'),
41
+ assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
42
+ priority: numberProperty('Filter by priority level.'),
43
+ type: stringProperty('Filter by task type.', context.typeEnum),
44
+ sort: stringProperty('Sort field (default: created).', ['priority', 'created', 'updated']),
45
+ order: stringProperty('Sort order (default: desc).', ['asc', 'desc']),
46
+ limit: numberProperty('Max tasks to return.'),
47
+ },
48
+ },
49
+ }));
50
+ register('search_tasks', (context) => ({
51
+ description: context.describeTool('Search active and archived tasks by keyword. Matches against title, description, and ID. Use scope=\'active\' or scope=\'archive\' to narrow results. Prefer this over list_tasks when looking for a specific task by name.'),
52
+ inputSchema: {
53
+ properties: {
54
+ query: stringProperty('Search term (matches title, description, internal ID, or task reference such as T-123).'),
55
+ scope: stringProperty('Search scope (default: all).', ['all', 'active', 'archive']),
56
+ },
57
+ required: ['query'],
58
+ },
59
+ }));
60
+ register('get_task', (context) => ({
61
+ description: context.describeTool('Get a single task by ID or task reference. Checks both active and archived tasks. Use this when you have a specific task ID or T-123 reference from a previous list or search result and need full details including comments and attachment metadata. This tool does not return attachment file contents. For task attachments, call get_task_attachments and then read_task_attachment. Do not fetch attachment path URLs directly when MCP attachment-read tools are available. Do not use this for image references like I-123 or document references like D-123.'),
62
+ inputSchema: {
63
+ properties: {
64
+ id: stringProperty('Task ID or task reference such as T-123.'),
65
+ },
66
+ required: ['id'],
67
+ },
68
+ }));
69
+ register('list_initiatives', (context) => ({
70
+ description: context.describeTool('List initiatives for the active workspace. Returns initiative records with attached workstream summaries and task counts so AI agents can reason about the planning model directly.'),
71
+ inputSchema: { properties: {} },
72
+ }));
73
+ register('get_initiative', (context) => ({
74
+ description: context.describeTool('Get a single initiative by ID or initiative reference such as IN-123. Returns the initiative plus its workstreams and rolled-up task counts.'),
75
+ inputSchema: {
76
+ properties: {
77
+ id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
78
+ },
79
+ required: ['id'],
80
+ },
81
+ }));
82
+ register('create_initiative', (context) => ({
83
+ description: context.describeTool('Create a new initiative. Use this for the top planning layer when a body of work should coordinate multiple workstreams.'),
84
+ inputSchema: {
85
+ properties: {
86
+ title: stringProperty('Initiative title.'),
87
+ description: stringProperty('Optional initiative description.'),
88
+ ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
89
+ order: numberProperty('Optional sort order among initiatives.'),
90
+ },
91
+ required: ['title'],
92
+ },
93
+ }));
94
+ register('update_initiative', (context) => ({
95
+ description: context.describeTool('Update an existing initiative. Only pass the fields you want to change.'),
96
+ inputSchema: {
97
+ properties: {
98
+ id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
99
+ title: stringProperty('Updated initiative title.'),
100
+ description: stringProperty('Updated initiative description.'),
101
+ ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
102
+ order: numberProperty('Optional sort order among initiatives.'),
103
+ },
104
+ required: ['id'],
105
+ },
106
+ }));
107
+ register('archive_initiative', (context) => ({
108
+ description: context.describeTool('Archive an initiative without deleting it. This hides it from open planning views while preserving its workstreams and history.'),
109
+ inputSchema: {
110
+ properties: {
111
+ id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
112
+ },
113
+ required: ['id'],
114
+ },
115
+ }));
116
+ register('unarchive_initiative', (context) => ({
117
+ description: context.describeTool('Restore an archived initiative to active planning views.'),
118
+ inputSchema: {
119
+ properties: {
120
+ id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
121
+ },
122
+ required: ['id'],
123
+ },
124
+ }));
125
+ register('list_workstreams', (context) => ({
126
+ description: context.describeTool('List workstreams for the active workspace. Returns workstream records with attached task summaries and linked initiative information when present.'),
127
+ inputSchema: { properties: {} },
128
+ }));
129
+ register('get_workstream', (context) => ({
130
+ description: context.describeTool('Get a single workstream by ID or workstream reference such as WS-123. Returns the workstream plus its tasks.'),
131
+ inputSchema: {
132
+ properties: {
133
+ id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
134
+ },
135
+ required: ['id'],
136
+ },
137
+ }));
138
+ register('create_workstream', (context) => ({
139
+ description: context.describeTool('Create a new workstream. Use initiativeId to link it to an initiative, or omit initiativeId to create a standalone workstream.'),
140
+ inputSchema: {
141
+ properties: {
142
+ title: stringProperty('Workstream title.'),
143
+ description: stringProperty('Optional workstream description.'),
144
+ ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
145
+ initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123.'),
146
+ order: numberProperty('Optional sort order among workstreams.'),
147
+ },
148
+ required: ['title'],
149
+ },
150
+ }));
151
+ register('update_workstream', (context) => ({
152
+ description: context.describeTool('Update an existing workstream. Only pass the fields you want to change.'),
153
+ inputSchema: {
154
+ properties: {
155
+ id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
156
+ title: stringProperty('Updated workstream title.'),
157
+ description: stringProperty('Updated workstream description.'),
158
+ ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
159
+ initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123. Pass an empty string to unlink.'),
160
+ order: numberProperty('Optional sort order among workstreams.'),
161
+ },
162
+ required: ['id'],
163
+ },
164
+ }));
165
+ register('archive_workstream', (context) => ({
166
+ description: context.describeTool('Archive a workstream without deleting it. This hides it from open planning views while preserving its linked tasks.'),
167
+ inputSchema: {
168
+ properties: {
169
+ id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
170
+ },
171
+ required: ['id'],
172
+ },
173
+ }));
174
+ register('unarchive_workstream', (context) => ({
175
+ description: context.describeTool('Restore an archived workstream to active planning views.'),
176
+ inputSchema: {
177
+ properties: {
178
+ id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
179
+ },
180
+ required: ['id'],
181
+ },
182
+ }));
183
+ register('set_task_workstream', (context) => ({
184
+ description: context.describeTool('Assign an existing task to a workstream. Reassigning a task removes it from any previous workstream.'),
185
+ inputSchema: {
186
+ properties: {
187
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
188
+ workstreamId: stringProperty('Workstream ID or workstream reference such as WS-123.'),
189
+ },
190
+ required: ['taskId', 'workstreamId'],
191
+ },
192
+ }));
193
+ register('clear_task_workstream', (context) => ({
194
+ description: context.describeTool('Remove a task from its current workstream, making it standalone.'),
195
+ inputSchema: {
196
+ properties: {
197
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
198
+ },
199
+ required: ['taskId'],
200
+ },
201
+ }));
202
+ register('set_workstream_initiative', (context) => ({
203
+ description: context.describeTool('Assign an existing workstream to an initiative. Reassigning a workstream removes it from any previous initiative.'),
204
+ inputSchema: {
205
+ properties: {
206
+ workstreamId: stringProperty('Workstream ID or workstream reference such as WS-123.'),
207
+ initiativeId: stringProperty('Initiative ID or initiative reference such as IN-123.'),
208
+ },
209
+ required: ['workstreamId', 'initiativeId'],
210
+ },
211
+ }));
212
+ register('clear_workstream_initiative', (context) => ({
213
+ description: context.describeTool('Remove a workstream from its current initiative, making it standalone.'),
214
+ inputSchema: {
215
+ properties: {
216
+ workstreamId: stringProperty('Workstream ID or workstream reference such as WS-123.'),
217
+ },
218
+ required: ['workstreamId'],
219
+ },
220
+ }));
221
+ register('get_task_checklist', (context) => ({
222
+ description: context.describeTool('Read the structured checklist items for one task. Returns stored checklist items, not markdown parsing.'),
223
+ inputSchema: {
224
+ properties: {
225
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
226
+ },
227
+ required: ['taskId'],
228
+ },
229
+ }));
230
+ register('replace_task_checklist', (context) => ({
231
+ description: context.describeTool('Replace the structured checklist items for one task with a new ordered list.'),
232
+ inputSchema: {
233
+ properties: {
234
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
235
+ items: arrayProperty('Ordered checklist items to persist.', {
236
+ type: 'object',
237
+ properties: {
238
+ text: { type: 'string' },
239
+ done: { type: 'boolean' },
240
+ },
241
+ required: ['text'],
242
+ }),
243
+ },
244
+ required: ['taskId', 'items'],
245
+ },
246
+ }));
247
+ register('get_schedule_board', (context) => ({
248
+ description: context.describeTool('Read the current schedule board for the active workspace. Use this before scheduling or reordering tasks when you need the existing day layout and order.'),
249
+ inputSchema: { properties: {} },
250
+ }));
251
+ register('schedule_task', (context) => ({
252
+ description: context.describeTool('Schedule a task on a specific day. Use get_schedule_board first when you need the current order before inserting the task.'),
253
+ inputSchema: {
254
+ properties: {
255
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
256
+ date: stringProperty('Target date in YYYY-MM-DD format.'),
257
+ order: numberProperty('Optional position within that day.'),
258
+ },
259
+ required: ['taskId', 'date'],
260
+ },
261
+ }));
262
+ register('unschedule_task', (context) => ({
263
+ description: context.describeTool('Remove a task from the schedule board and return it to the unscheduled pool.'),
264
+ inputSchema: {
265
+ properties: {
266
+ taskId: stringProperty('Task ID or task reference such as T-123.'),
267
+ },
268
+ required: ['taskId'],
269
+ },
270
+ }));
271
+ register('reorder_scheduled_tasks', (context) => ({
272
+ description: context.describeTool('Replace the ordered task list for one scheduled day. Use get_schedule_board first to inspect the current day contents.'),
273
+ inputSchema: {
274
+ properties: {
275
+ date: stringProperty('Target date in YYYY-MM-DD format.'),
276
+ taskIds: arrayProperty('Ordered task IDs or task references for that day.', { type: 'string' }),
277
+ },
278
+ required: ['date', 'taskIds'],
279
+ },
280
+ }));
281
+ register('bulk_carryover', (context) => ({
282
+ description: context.describeTool('Carry unfinished scheduled tasks forward from one day to another in bulk.'),
283
+ inputSchema: {
284
+ properties: {
285
+ fromDate: stringProperty('Source date in YYYY-MM-DD format.'),
286
+ toDate: stringProperty('Destination date in YYYY-MM-DD format.'),
287
+ preserveCompleted: booleanProperty('When true, leave completed tasks on the source date.'),
288
+ },
289
+ required: ['fromDate', 'toDate'],
290
+ },
291
+ }));
292
+ register('set_schedule_preferences', (context) => ({
293
+ description: context.describeTool('Update schedule board display and planning preferences for the current session.'),
294
+ inputSchema: {
295
+ properties: {
296
+ weekStartsOn: numberProperty('Week start day index, where 0 is Sunday and 1 is Monday.'),
297
+ showWeekends: booleanProperty('When true, include weekends in schedule views.'),
298
+ },
299
+ },
300
+ }));
301
+ register('create_task', (context) => ({
302
+ description: context.describeTool('Create a new task. Use get_config first so category, type, priority, approach, status, and complexity values match the active workspace taxonomy.'),
303
+ inputSchema: {
304
+ properties: {
305
+ title: stringProperty('Task title.'),
306
+ description: stringProperty('Optional task description.'),
307
+ assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
308
+ category: stringProperty('Task category value.'),
309
+ type: stringProperty('Task type value.', context.typeEnum),
310
+ priority: numberProperty(context.priorityDesc),
311
+ approach: stringProperty('Task approach value.', context.approachEnum),
312
+ status: stringProperty('Initial task status.', ['task', 'on-hold', 'in-progress', 'review', 'done', 'cancelled']),
313
+ complexity: numberProperty(context.complexityDesc),
314
+ scheduledDate: stringProperty('Optional scheduled date in YYYY-MM-DD format.'),
315
+ dueDate: stringProperty('Optional due date in YYYY-MM-DD format.'),
316
+ },
317
+ required: ['title'],
318
+ },
319
+ }));
320
+ register('start_task', (context) => ({
321
+ description: context.describeTool('preferred AI start-of-task workflow. Sets a task to in-progress and, when currently unassigned, claims it for the active AI profile. It will not steal a task from a different assignee.'),
322
+ inputSchema: {
323
+ properties: {
324
+ id: stringProperty('Task ID or task reference such as T-123.'),
325
+ },
326
+ required: ['id'],
327
+ },
328
+ }));
329
+ register('update_task', (context) => ({
330
+ description: context.describeTool('Update an existing task. Use get_config first for taxonomy-backed fields. Use start_task when beginning active work on a tracked task, and prefer finish_task for the final completion or review handoff.'),
331
+ inputSchema: {
332
+ properties: {
333
+ id: stringProperty('Task ID or task reference such as T-123.'),
334
+ title: stringProperty('Updated task title.'),
335
+ description: stringProperty('Updated task description.'),
336
+ assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
337
+ category: stringProperty('Updated category value.'),
338
+ type: stringProperty('Updated task type value.', context.typeEnum),
339
+ priority: numberProperty(context.priorityDesc),
340
+ approach: stringProperty('Updated task approach value.', context.approachEnum),
341
+ status: stringProperty('Updated task status.', ['task', 'on-hold', 'in-progress', 'review', 'done', 'cancelled']),
342
+ complexity: numberProperty(context.complexityDesc),
343
+ scheduledDate: stringProperty('Updated scheduled date in YYYY-MM-DD format. Pass an empty string to clear.'),
344
+ dueDate: stringProperty('Updated due date in YYYY-MM-DD format. Pass an empty string to clear.'),
345
+ },
346
+ required: ['id'],
347
+ },
348
+ }));
349
+ register('finish_task', (context) => ({
350
+ description: context.describeTool('default end-of-task handoff. Use outcome=\'done\' when work is complete and no explicit human action is requested. Use outcome=\'review\' when human validation or approval is needed. If you report completed work on a tracked task, either finish it with this tool or explain why it remains open. This tool never archives automatically.'),
351
+ inputSchema: {
352
+ properties: {
353
+ id: stringProperty('Task ID or task reference such as T-123.'),
354
+ outcome: stringProperty('Completion outcome.', ['review', 'done']),
355
+ comment: stringProperty('Optional completion note to add as a task comment.'),
356
+ },
357
+ required: ['id'],
358
+ },
359
+ }));
360
+ register('assign_task', (context) => ({
361
+ description: context.describeTool('Assign a task to one eligible assignee. For assigning 3 or more tasks at once, use bulk_assign_tasks instead.'),
362
+ inputSchema: {
363
+ properties: {
364
+ id: stringProperty('Task ID or task reference such as T-123.'),
365
+ assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
366
+ },
367
+ required: ['id', 'assignee'],
368
+ },
369
+ }));
370
+ register('bulk_assign_tasks', (context) => ({
371
+ description: context.describeTool('Assign multiple tasks to the same eligible assignee in one request. Preferred over repeated assign_task calls when changing 3 or more tasks at once.'),
372
+ inputSchema: {
373
+ properties: {
374
+ ids: arrayProperty('Task IDs or task references such as T-123.', { type: 'string' }),
375
+ assignee: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
376
+ },
377
+ required: ['ids', 'assignee'],
378
+ },
379
+ }));
380
+ register('archive_task', (context) => ({
381
+ description: context.describeTool('Move a task to history by archiving it. Use status=\'done\' for completed work or status=\'cancelled\' for abandoned work. active tasks may still use status=done for final lookover without being archived.'),
382
+ inputSchema: {
383
+ properties: {
384
+ id: stringProperty('Task ID or task reference such as T-123.'),
385
+ status: stringProperty('Final archive status.', ['done', 'cancelled']),
386
+ reason: stringProperty('Reason for cancellation when status is cancelled.'),
387
+ },
388
+ required: ['id', 'status'],
389
+ },
390
+ }));
391
+ register('list_archive', (context) => ({
392
+ description: context.describeTool('List archived tasks. Use format=json for programmatic use.'),
393
+ inputSchema: {
394
+ properties: {
395
+ format: stringProperty('Toggle output format (default: markdown).', ['markdown', 'json']),
396
+ },
397
+ },
398
+ }));
399
+ register('unarchive_task', (context) => ({
400
+ description: context.describeTool('Restore an archived task to the active task list.'),
401
+ inputSchema: {
402
+ properties: {
403
+ id: stringProperty('Task ID or task reference such as T-123.'),
404
+ },
405
+ required: ['id'],
406
+ },
407
+ }));
408
+ register('bulk_archive', (context) => ({
409
+ description: context.describeTool('Archive multiple tasks in one request. Use status=\'done\' for completed work or status=\'cancelled\' for abandoned work.'),
410
+ inputSchema: {
411
+ properties: {
412
+ ids: arrayProperty('Task IDs or task references such as T-123.', { type: 'string' }),
413
+ status: stringProperty('Final archive status (default: done).', ['done', 'cancelled']),
414
+ },
415
+ required: ['ids'],
416
+ },
417
+ }));
418
+ }
@@ -0,0 +1,35 @@
1
+ import type { McpToolName } from './toolRegistry.js';
2
+ export type McpToolInputSchema = {
3
+ type: 'object';
4
+ properties?: Record<string, unknown>;
5
+ required?: string[];
6
+ };
7
+ export type McpToolDescriptor = {
8
+ name: McpToolName;
9
+ description: string;
10
+ inputSchema: McpToolInputSchema;
11
+ };
12
+ export type McpToolDescriptorContext = {
13
+ describeTool: (text: string) => string;
14
+ assigneeHint: string;
15
+ priorityDesc: string;
16
+ typeEnum: string[];
17
+ approachEnum: string[];
18
+ complexityDesc: string;
19
+ };
20
+ export type McpToolHandlerResult = {
21
+ content?: Array<{
22
+ type: string;
23
+ text?: string;
24
+ }>;
25
+ isError?: boolean;
26
+ };
27
+ export type McpToolHandler = (args: Record<string, unknown>) => Promise<McpToolHandlerResult> | McpToolHandlerResult;
28
+ export type RegisteredMcpTool = {
29
+ name: McpToolName;
30
+ buildDescriptor: (context: McpToolDescriptorContext) => McpToolDescriptor;
31
+ handler: McpToolHandler;
32
+ };
33
+ export type RegisterMcpTool = (tool: RegisteredMcpTool) => void;
34
+ export type DomainToolExecutor = (name: McpToolName, args: Record<string, unknown>) => Promise<McpToolHandlerResult> | McpToolHandlerResult;
35
+ export declare function bindDomainTool(executeTool: DomainToolExecutor, name: McpToolName): McpToolHandler;
@@ -0,0 +1,3 @@
1
+ export function bindDomainTool(executeTool, name) {
2
+ return (args) => executeTool(name, args);
3
+ }
@@ -190,7 +190,7 @@ export function taskforcePlugin(options) {
190
190
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
191
191
  <link rel="icon" type="image/svg+xml" href="/src/assets/favicon/favicon.svg" />
192
192
  <link rel="icon" type="image/x-icon" href="/src/assets/favicon/favicon.ico" />
193
- <title>Taskforce Dashboard</title>
193
+ <title>Taskforce</title>
194
194
  <style>
195
195
  body { margin: 0; background: #0f0f1a; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
196
196
  #root { height: 100vh; overflow: hidden; }
@@ -12,7 +12,7 @@ export declare const APP_GATE_DEFINITIONS: {
12
12
  readonly removalMilestone: "Document Manager GA";
13
13
  };
14
14
  readonly 'workflows.workspace_access': {
15
- readonly enabled: true;
15
+ readonly enabled: false;
16
16
  readonly description: "Workflow workspace readiness gate.";
17
17
  readonly owner: "taskforce";
18
18
  readonly removalMilestone: "Workflow workspace GA";
@@ -12,7 +12,7 @@ export const APP_GATE_DEFINITIONS = {
12
12
  removalMilestone: 'Document Manager GA'
13
13
  },
14
14
  'workflows.workspace_access': {
15
- enabled: true,
15
+ enabled: false,
16
16
  description: 'Workflow workspace readiness gate.',
17
17
  owner: 'taskforce',
18
18
  removalMilestone: 'Workflow workspace GA'
@@ -28,6 +28,7 @@ export declare function shouldRewriteSetCookieForLocalDevHost(hostHeader: string
28
28
  export declare function writeJsonErrorIfPossible(res: http.ServerResponse, statusCode: number, payload: Record<string, unknown>): boolean;
29
29
  export declare function applyCloudProxyForwardedHeaders(headers: Headers, req: http.IncomingMessage): void;
30
30
  export declare function rewriteProxySetCookieForLocalHost(cookie: string): string;
31
+ export declare function shouldRewriteProxySetCookieDomain(cookie: string, hostHeader: string | undefined): boolean;
31
32
  export declare function shouldApplyAuthRateLimitForPath(pathname: string): boolean;
32
33
  export declare function resolveCloudProxyBaseUrl(env?: NodeJS.ProcessEnv): string;
33
34
  export declare function ensureBillingWebhookAuthExclusion(excludedPaths: string[]): string[];
@@ -205,6 +205,43 @@ export function rewriteProxySetCookieForLocalHost(cookie) {
205
205
  const filteredAttrs = attrs.filter((attr) => !attr.toLowerCase().startsWith('domain='));
206
206
  return [nameValue, ...filteredAttrs].join('; ');
207
207
  }
208
+ function normalizeHostHeaderHost(hostHeader) {
209
+ const raw = String(hostHeader || '').trim().toLowerCase();
210
+ if (!raw)
211
+ return '';
212
+ if (raw.startsWith('[')) {
213
+ const closing = raw.indexOf(']');
214
+ if (closing >= 0)
215
+ return raw.slice(1, closing);
216
+ }
217
+ const portSeparator = raw.lastIndexOf(':');
218
+ if (portSeparator <= 0)
219
+ return raw;
220
+ const maybePort = raw.slice(portSeparator + 1);
221
+ if (/^\d+$/.test(maybePort)) {
222
+ return raw.slice(0, portSeparator);
223
+ }
224
+ return raw;
225
+ }
226
+ function parseSetCookieDomain(cookie) {
227
+ const parts = String(cookie || '').split(';').map((part) => part.trim()).filter(Boolean);
228
+ for (const part of parts.slice(1)) {
229
+ const lower = part.toLowerCase();
230
+ if (!lower.startsWith('domain='))
231
+ continue;
232
+ return part.slice('domain='.length).trim().replace(/^\.+/, '').toLowerCase();
233
+ }
234
+ return '';
235
+ }
236
+ export function shouldRewriteProxySetCookieDomain(cookie, hostHeader) {
237
+ const requestHost = normalizeHostHeaderHost(hostHeader);
238
+ const cookieDomain = parseSetCookieDomain(cookie);
239
+ if (!requestHost || !cookieDomain)
240
+ return false;
241
+ if (requestHost === cookieDomain)
242
+ return false;
243
+ return !requestHost.endsWith(`.${cookieDomain}`);
244
+ }
208
245
  async function readRequestBody(req, maxBytes) {
209
246
  const chunks = [];
210
247
  let total = 0;
@@ -550,9 +587,15 @@ export function createStandaloneServer(options) {
550
587
  }
551
588
  const setCookies = upstream.headers.getSetCookie?.();
552
589
  if (Array.isArray(setCookies) && setCookies.length > 0) {
553
- const normalizedSetCookies = shouldRewriteSetCookieForLocalDevHost(String(req.headers.host || '').trim())
554
- ? setCookies.map((cookie) => rewriteProxySetCookieForLocalHost(cookie))
555
- : setCookies;
590
+ const requestHost = String(req.headers.host || '').trim();
591
+ const normalizedSetCookies = setCookies.map((cookie) => {
592
+ if (shouldRewriteSetCookieForLocalDevHost(requestHost)) {
593
+ return rewriteProxySetCookieForLocalHost(cookie);
594
+ }
595
+ return shouldRewriteProxySetCookieDomain(cookie, requestHost)
596
+ ? rewriteProxySetCookieForLocalHost(cookie)
597
+ : cookie;
598
+ });
556
599
  res.setHeader('Set-Cookie', normalizedSetCookies);
557
600
  }
558
601
  res.writeHead(upstream.status, outgoingHeaders);
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
- import { ensureBillingWebhookAuthExclusion, ensurePublicSiteConfigAuthExclusion, isBillingWebhookPath, resolveEffectiveRequestUserId, resolveAuthorizedWorkspaceAccess, shouldApplyAuthRateLimitForPath, shouldProxyCloudPath, writeJsonErrorIfPossible } from './index.js';
2
+ import { ensureBillingWebhookAuthExclusion, ensurePublicSiteConfigAuthExclusion, isBillingWebhookPath, rewriteProxySetCookieForLocalHost, resolveEffectiveRequestUserId, resolveAuthorizedWorkspaceAccess, shouldRewriteProxySetCookieDomain, shouldApplyAuthRateLimitForPath, shouldProxyCloudPath, writeJsonErrorIfPossible } from './index.js';
3
3
  describe('server index helper policies', () => {
4
4
  it('adds webhook path to auth exclusions without duplicates', () => {
5
5
  const base = ['/api/taskforce/health', '/api/taskforce/auth'];
@@ -92,6 +92,17 @@ describe('shouldProxyCloudPath', () => {
92
92
  expect(shouldProxyCloudPath('/api/taskforce/public/site-config-script')).toBe(true);
93
93
  });
94
94
  });
95
+ describe('proxy cookie helpers', () => {
96
+ it('rewrites mismatched cookie domains for non-loopback local hosts', () => {
97
+ const cookie = 'taskforce_session=abc; Path=/; HttpOnly; SameSite=Lax; Domain=taskforcehq.ai';
98
+ expect(shouldRewriteProxySetCookieDomain(cookie, 'taskforce.localtest.me:3847')).toBe(true);
99
+ expect(rewriteProxySetCookieForLocalHost(cookie)).not.toContain('Domain=');
100
+ });
101
+ it('preserves cookie domains that already match the request host', () => {
102
+ const cookie = 'taskforce_session=abc; Path=/; HttpOnly; SameSite=Lax; Domain=taskforcehq.ai';
103
+ expect(shouldRewriteProxySetCookieDomain(cookie, 'app.taskforcehq.ai')).toBe(false);
104
+ });
105
+ });
95
106
  describe('resolveAuthorizedWorkspaceAccess', () => {
96
107
  it('prefers an explicitly requested workspace when the user is authorized for it', () => {
97
108
  const core = {
@@ -49,7 +49,7 @@
49
49
  */
50
50
  import type { IncomingMessage, ServerResponse } from 'http';
51
51
  import type { TaskforceCore } from '../../core/Taskforce.js';
52
- import type { RouteHandler, RouteContext } from '../routes.js';
52
+ import { type RouteHandler, type RouteContext } from './primitives.js';
53
53
  import type { ResolvedObjectStorageConfig } from '../../storage/objectStorage.js';
54
54
  import type { TaskAssetStore } from '../../storage/taskAssetStore.js';
55
55
  import { ObjectStorageClient } from '../../storage/objectStorageClient.js';
@@ -99,10 +99,6 @@ export interface AdminRouteDependencies {
99
99
  }) => void;
100
100
  getCollaborationTelemetry: () => any;
101
101
  isCollaborationPolicyEnabled: () => boolean;
102
- resolveRuntimeConfigBaseUrl: (req: IncomingMessage) => string;
103
- resolvePublicAuthBaseUrl: (req: IncomingMessage) => string;
104
- parseFormBody: (req: IncomingMessage, maxBytes?: number) => Promise<Record<string, string>>;
105
- escapeHtml: (value: unknown) => string;
106
102
  normalizeMcpOauthScopeString: (raw: unknown) => string[];
107
103
  MCP_TOKEN_SCOPE_VALUES: Set<string>;
108
104
  MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS: Record<string, McpAccessTokenScope>;