@taskforcehq/taskforce 0.3.168

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 (492) hide show
  1. package/README.md +241 -0
  2. package/dist/Taskforce.d.ts +3 -0
  3. package/dist/Taskforce.js +3 -0
  4. package/dist/Taskforce.module.css +7587 -0
  5. package/dist/TaskforceCore.CategoryFallback.test.d.ts +1 -0
  6. package/dist/TaskforceCore.CategoryFallback.test.js +98 -0
  7. package/dist/TaskforceCore.d.ts +12 -0
  8. package/dist/TaskforceCore.js +729 -0
  9. package/dist/TaskforceCore.test.d.ts +1 -0
  10. package/dist/TaskforceCore.test.js +2132 -0
  11. package/dist/TaskforceScreenshot.d.ts +11 -0
  12. package/dist/TaskforceScreenshot.js +113 -0
  13. package/dist/TaskforceWidget.d.ts +6 -0
  14. package/dist/TaskforceWidget.js +101 -0
  15. package/dist/TaskforceWidget.test.d.ts +1 -0
  16. package/dist/TaskforceWidget.test.js +92 -0
  17. package/dist/UnsavedChangesRelaxed.test.d.ts +1 -0
  18. package/dist/UnsavedChangesRelaxed.test.js +16 -0
  19. package/dist/__tests__/localizationCatalogs.test.d.ts +1 -0
  20. package/dist/__tests__/localizationCatalogs.test.js +37 -0
  21. package/dist/__tests__/planParser.test.d.ts +1 -0
  22. package/dist/__tests__/planParser.test.js +183 -0
  23. package/dist/__tests__/releaseVersion.test.d.ts +1 -0
  24. package/dist/__tests__/releaseVersion.test.js +32 -0
  25. package/dist/__tests__/structuredCommentValidation.test.d.ts +1 -0
  26. package/dist/__tests__/structuredCommentValidation.test.js +31 -0
  27. package/dist/__tests__/structuredDocValidation.test.d.ts +1 -0
  28. package/dist/__tests__/structuredDocValidation.test.js +30 -0
  29. package/dist/__tests__/templateEngine.test.d.ts +1 -0
  30. package/dist/__tests__/templateEngine.test.js +150 -0
  31. package/dist/__tests__/templates.test.d.ts +1 -0
  32. package/dist/__tests__/templates.test.js +45 -0
  33. package/dist/__tests__/workflowDocumentFixtures.test.d.ts +1 -0
  34. package/dist/__tests__/workflowDocumentFixtures.test.js +78 -0
  35. package/dist/__tests__/workflowExportMatrix.test.d.ts +1 -0
  36. package/dist/__tests__/workflowExportMatrix.test.js +113 -0
  37. package/dist/__tests__/workflowExportSnapshots.test.d.ts +1 -0
  38. package/dist/__tests__/workflowExportSnapshots.test.js +28 -0
  39. package/dist/__tests__/workflowGeneration.test.d.ts +1 -0
  40. package/dist/__tests__/workflowGeneration.test.js +12 -0
  41. package/dist/__tests__/workflowSimulation.test.d.ts +1 -0
  42. package/dist/__tests__/workflowSimulation.test.js +113 -0
  43. package/dist/__tests__/workflowV2Policy.test.d.ts +1 -0
  44. package/dist/__tests__/workflowV2Policy.test.js +80 -0
  45. package/dist/api.d.ts +30 -0
  46. package/dist/api.js +85 -0
  47. package/dist/cli.d.ts +2 -0
  48. package/dist/cli.js +217 -0
  49. package/dist/compat/priorityCompat.d.ts +2 -0
  50. package/dist/compat/priorityCompat.js +24 -0
  51. package/dist/components/features/DocumentGenerateModal.d.ts +9 -0
  52. package/dist/components/features/DocumentGenerateModal.js +114 -0
  53. package/dist/components/features/DocumentIndex.d.ts +19 -0
  54. package/dist/components/features/DocumentIndex.js +51 -0
  55. package/dist/components/features/DocumentViewer.d.ts +9 -0
  56. package/dist/components/features/DocumentViewer.js +253 -0
  57. package/dist/components/features/DocumentWorkspace.d.ts +21 -0
  58. package/dist/components/features/DocumentWorkspace.js +88 -0
  59. package/dist/components/features/TaskSettings.d.ts +209 -0
  60. package/dist/components/features/TaskSettings.js +500 -0
  61. package/dist/components/features/TaskSettings.test.d.ts +1 -0
  62. package/dist/components/features/TaskSettings.test.js +308 -0
  63. package/dist/components/features/settings/CategoryManager.d.ts +19 -0
  64. package/dist/components/features/settings/CategoryManager.js +113 -0
  65. package/dist/components/features/settings/TaxonomyEditor.d.ts +8 -0
  66. package/dist/components/features/settings/TaxonomyEditor.js +98 -0
  67. package/dist/components/features/settings/TaxonomyManager 2.d.ts +52 -0
  68. package/dist/components/features/settings/TaxonomyManager 2.js +75 -0
  69. package/dist/components/features/settings/TaxonomyManager.d.ts +54 -0
  70. package/dist/components/features/settings/TaxonomyManager.js +77 -0
  71. package/dist/components/features/settings/TypeManager.d.ts +10 -0
  72. package/dist/components/features/settings/TypeManager.js +50 -0
  73. package/dist/components/features/settings/WorkflowEditorPrototype.d.ts +15 -0
  74. package/dist/components/features/settings/WorkflowEditorPrototype.js +245 -0
  75. package/dist/components/task/TaskActionHeader.d.ts +20 -0
  76. package/dist/components/task/TaskActionHeader.js +13 -0
  77. package/dist/components/task/TaskCard.d.ts +47 -0
  78. package/dist/components/task/TaskCard.js +326 -0
  79. package/dist/components/task/TaskCard.test.d.ts +1 -0
  80. package/dist/components/task/TaskCard.test.js +95 -0
  81. package/dist/components/task/TaskContextUpload.d.ts +12 -0
  82. package/dist/components/task/TaskContextUpload.js +393 -0
  83. package/dist/components/task/TaskContextUpload.test.d.ts +1 -0
  84. package/dist/components/task/TaskContextUpload.test.js +189 -0
  85. package/dist/components/task/TaskForm.d.ts +105 -0
  86. package/dist/components/task/TaskForm.js +241 -0
  87. package/dist/components/task/TaskForm.test.d.ts +1 -0
  88. package/dist/components/task/TaskForm.test.js +288 -0
  89. package/dist/components/task/TaskKanban 2.d.ts +76 -0
  90. package/dist/components/task/TaskKanban 2.js +1004 -0
  91. package/dist/components/task/TaskKanban.d.ts +76 -0
  92. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +1 -0
  93. package/dist/components/task/TaskKanban.empty-column-drop.test.js +66 -0
  94. package/dist/components/task/TaskKanban.js +1026 -0
  95. package/dist/components/task/TaskKanban.test.d.ts +1 -0
  96. package/dist/components/task/TaskKanban.test.js +46 -0
  97. package/dist/components/task/TaskList.d.ts +74 -0
  98. package/dist/components/task/TaskList.js +38 -0
  99. package/dist/components/task/TaskList.test.d.ts +1 -0
  100. package/dist/components/task/TaskList.test.js +270 -0
  101. package/dist/components/task/TaskScreenshotSection.d.ts +15 -0
  102. package/dist/components/task/TaskScreenshotSection.js +22 -0
  103. package/dist/components/ui/LevelSelector.d.ts +13 -0
  104. package/dist/components/ui/LevelSelector.js +25 -0
  105. package/dist/components/ui/LevelSelector.test.d.ts +1 -0
  106. package/dist/components/ui/LevelSelector.test.js +55 -0
  107. package/dist/components/ui/Markdown.d.ts +6 -0
  108. package/dist/components/ui/Markdown.js +94 -0
  109. package/dist/components/ui/Markdown.test.d.ts +1 -0
  110. package/dist/components/ui/Markdown.test.js +38 -0
  111. package/dist/components/ui/Modal.d.ts +16 -0
  112. package/dist/components/ui/Modal.js +49 -0
  113. package/dist/components/ui/MultiSelectFilter.d.ts +10 -0
  114. package/dist/components/ui/MultiSelectFilter.js +71 -0
  115. package/dist/components/ui/MultiSelectFilter.test.d.ts +1 -0
  116. package/dist/components/ui/MultiSelectFilter.test.js +74 -0
  117. package/dist/components/ui/TaxonomyDropdown.d.ts +17 -0
  118. package/dist/components/ui/TaxonomyDropdown.js +145 -0
  119. package/dist/components/ui/TaxonomyDropdown.test.d.ts +1 -0
  120. package/dist/components/ui/TaxonomyDropdown.test.js +130 -0
  121. package/dist/components/views/StandaloneLayout 2.d.ts +8 -0
  122. package/dist/components/views/StandaloneLayout 2.js +1660 -0
  123. package/dist/components/views/StandaloneLayout.d.ts +8 -0
  124. package/dist/components/views/StandaloneLayout.js +2117 -0
  125. package/dist/components/views/WidgetView.d.ts +8 -0
  126. package/dist/components/views/WidgetView.js +221 -0
  127. package/dist/config/envSchema.d.ts +28 -0
  128. package/dist/config/envSchema.js +374 -0
  129. package/dist/config/envSchema.test.d.ts +1 -0
  130. package/dist/config/envSchema.test.js +97 -0
  131. package/dist/config/surfaceBase.d.ts +2 -0
  132. package/dist/config/surfaceBase.js +8 -0
  133. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +1 -0
  134. package/dist/core/BulkUpdateFieldsPreview.test.js +62 -0
  135. package/dist/core/CompletedAt.test.d.ts +1 -0
  136. package/dist/core/CompletedAt.test.js +44 -0
  137. package/dist/core/CreatedBy.test.d.ts +1 -0
  138. package/dist/core/CreatedBy.test.js +60 -0
  139. package/dist/core/EntitlementsPolicy.test.d.ts +1 -0
  140. package/dist/core/EntitlementsPolicy.test.js +145 -0
  141. package/dist/core/InitiativeTemplates.test.d.ts +1 -0
  142. package/dist/core/InitiativeTemplates.test.js +50 -0
  143. package/dist/core/ParentLifecycle.test.d.ts +1 -0
  144. package/dist/core/ParentLifecycle.test.js +98 -0
  145. package/dist/core/ScheduleFields.test.d.ts +1 -0
  146. package/dist/core/ScheduleFields.test.js +197 -0
  147. package/dist/core/SetupState.test.d.ts +1 -0
  148. package/dist/core/SetupState.test.js +113 -0
  149. package/dist/core/SystemAdmin.test.d.ts +1 -0
  150. package/dist/core/SystemAdmin.test.js +295 -0
  151. package/dist/core/TaskTaxonomyValidation.test.d.ts +1 -0
  152. package/dist/core/TaskTaxonomyValidation.test.js +64 -0
  153. package/dist/core/Taskforce.d.ts +1063 -0
  154. package/dist/core/Taskforce.ids.test.d.ts +1 -0
  155. package/dist/core/Taskforce.ids.test.js +168 -0
  156. package/dist/core/Taskforce.js +5981 -0
  157. package/dist/core/Taskforce.realtimeMutation.test.d.ts +1 -0
  158. package/dist/core/Taskforce.realtimeMutation.test.js +76 -0
  159. package/dist/core/WorkspaceDelete.test.d.ts +1 -0
  160. package/dist/core/WorkspaceDelete.test.js +140 -0
  161. package/dist/core/WorkspacePermissions.test.d.ts +1 -0
  162. package/dist/core/WorkspacePermissions.test.js +151 -0
  163. package/dist/core/WorkspacePlanMode.test.d.ts +1 -0
  164. package/dist/core/WorkspacePlanMode.test.js +104 -0
  165. package/dist/hooks/authGuardPolicy.d.ts +26 -0
  166. package/dist/hooks/authGuardPolicy.js +32 -0
  167. package/dist/hooks/authGuardPolicy.test.d.ts +1 -0
  168. package/dist/hooks/authGuardPolicy.test.js +76 -0
  169. package/dist/hooks/useDraggable.d.ts +12 -0
  170. package/dist/hooks/useDraggable.js +65 -0
  171. package/dist/hooks/useRealtimeSync.d.ts +24 -0
  172. package/dist/hooks/useRealtimeSync.js +275 -0
  173. package/dist/hooks/useRealtimeSync.test.d.ts +1 -0
  174. package/dist/hooks/useRealtimeSync.test.js +284 -0
  175. package/dist/hooks/useSyncOrchestrator.d.ts +164 -0
  176. package/dist/hooks/useSyncOrchestrator.js +1427 -0
  177. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +1 -0
  178. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +367 -0
  179. package/dist/hooks/useTaskforce.d.ts +727 -0
  180. package/dist/hooks/useTaskforce.js +4396 -0
  181. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +1 -0
  182. package/dist/hooks/useTaskforce.priority-filters.test.js +33 -0
  183. package/dist/hooks/useWorkspaceSyncController.d.ts +80 -0
  184. package/dist/hooks/useWorkspaceSyncController.js +598 -0
  185. package/dist/hooks/useWorkspaceSyncController.test.d.ts +1 -0
  186. package/dist/hooks/useWorkspaceSyncController.test.js +236 -0
  187. package/dist/index.d.ts +12 -0
  188. package/dist/index.js +11 -0
  189. package/dist/localization/index.d.ts +8 -0
  190. package/dist/localization/index.js +81 -0
  191. package/dist/localization/locales/en-US.d.ts +151 -0
  192. package/dist/localization/locales/en-US.js +150 -0
  193. package/dist/localization/locales/es-419.d.ts +2 -0
  194. package/dist/localization/locales/es-419.js +150 -0
  195. package/dist/localization/locales/pt-BR.d.ts +2 -0
  196. package/dist/localization/locales/pt-BR.js +150 -0
  197. package/dist/main.d.ts +1 -0
  198. package/dist/main.js +33 -0
  199. package/dist/mcp/optionResolution.d.ts +14 -0
  200. package/dist/mcp/optionResolution.js +126 -0
  201. package/dist/mcp/optionResolution.test.d.ts +1 -0
  202. package/dist/mcp/optionResolution.test.js +39 -0
  203. package/dist/mcp/server.d.ts +1 -0
  204. package/dist/mcp/server.js +1793 -0
  205. package/dist/mcp/structuredCommentValidation.d.ts +6 -0
  206. package/dist/mcp/structuredCommentValidation.js +56 -0
  207. package/dist/mcp/structuredDocValidation.d.ts +6 -0
  208. package/dist/mcp/structuredDocValidation.js +75 -0
  209. package/dist/migrations/billingSchemaParity.test.d.ts +1 -0
  210. package/dist/migrations/billingSchemaParity.test.js +46 -0
  211. package/dist/migrations/clientMigrations.d.ts +9 -0
  212. package/dist/migrations/clientMigrations.js +74 -0
  213. package/dist/migrations/flags.d.ts +6 -0
  214. package/dist/migrations/flags.js +8 -0
  215. package/dist/migrations/taskMigrations.d.ts +71 -0
  216. package/dist/migrations/taskMigrations.js +236 -0
  217. package/dist/migrations/taskMigrations.test.d.ts +1 -0
  218. package/dist/migrations/taskMigrations.test.js +34 -0
  219. package/dist/migrations/taskSchemaMigrations.d.ts +11 -0
  220. package/dist/migrations/taskSchemaMigrations.js +453 -0
  221. package/dist/plugins/index.d.ts +10 -0
  222. package/dist/plugins/index.js +10 -0
  223. package/dist/plugins/vite.d.ts +19 -0
  224. package/dist/plugins/vite.js +271 -0
  225. package/dist/public/android-chrome-192x192.png +0 -0
  226. package/dist/public/android-chrome-512x512.png +0 -0
  227. package/dist/public/apple-touch-icon.png +0 -0
  228. package/dist/public/favicon-16x16.png +0 -0
  229. package/dist/public/favicon-32x32.png +0 -0
  230. package/dist/public/favicon.ico +0 -0
  231. package/dist/public/favicon.png +0 -0
  232. package/dist/public/site.webmanifest +1 -0
  233. package/dist/public/taskforce.png +0 -0
  234. package/dist/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  235. package/dist/resources/templates/environments/aider.yaml +14 -0
  236. package/dist/resources/templates/environments/antigravity.yaml +10 -0
  237. package/dist/resources/templates/environments/claude.yaml +10 -0
  238. package/dist/resources/templates/environments/cline.yaml +10 -0
  239. package/dist/resources/templates/environments/codex.yaml +13 -0
  240. package/dist/resources/templates/environments/copilot.yaml +10 -0
  241. package/dist/resources/templates/environments/cursor.yaml +9 -0
  242. package/dist/resources/templates/environments/gemini.yaml +9 -0
  243. package/dist/resources/templates/environments/windsurf.yaml +10 -0
  244. package/dist/resources/templates/workflow-sources/README.md +9 -0
  245. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  246. package/dist/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  247. package/dist/resources/templates/workflows/collaborate.yaml +104 -0
  248. package/dist/resources/templates/workflows/do.yaml +82 -0
  249. package/dist/resources/templates/workflows/evaluate.yaml +111 -0
  250. package/dist/resources/templates/workflows/execute.yaml +142 -0
  251. package/dist/resources/templates/workflows/plan.yaml +112 -0
  252. package/dist/resources/templates/workflows/review.yaml +109 -0
  253. package/dist/runtime/capabilities.d.ts +8 -0
  254. package/dist/runtime/capabilities.js +16 -0
  255. package/dist/security/virusScan.d.ts +16 -0
  256. package/dist/security/virusScan.js +83 -0
  257. package/dist/server/auth.d.ts +51 -0
  258. package/dist/server/auth.js +266 -0
  259. package/dist/server/auth.test.d.ts +1 -0
  260. package/dist/server/auth.test.js +111 -0
  261. package/dist/server/buildInfo.d.ts +7 -0
  262. package/dist/server/buildInfo.js +100 -0
  263. package/dist/server/buildInfo.test.d.ts +1 -0
  264. package/dist/server/buildInfo.test.js +29 -0
  265. package/dist/server/cors.d.ts +7 -0
  266. package/dist/server/cors.js +76 -0
  267. package/dist/server/cors.test.d.ts +1 -0
  268. package/dist/server/cors.test.js +71 -0
  269. package/dist/server/email.d.ts +28 -0
  270. package/dist/server/email.js +138 -0
  271. package/dist/server/email.test.d.ts +1 -0
  272. package/dist/server/email.test.js +113 -0
  273. package/dist/server/index.cookieProxy.test.d.ts +1 -0
  274. package/dist/server/index.cookieProxy.test.js +19 -0
  275. package/dist/server/index.d.ts +17 -0
  276. package/dist/server/index.js +757 -0
  277. package/dist/server/index.rateLimit.test.d.ts +1 -0
  278. package/dist/server/index.rateLimit.test.js +13 -0
  279. package/dist/server/index.test.d.ts +1 -0
  280. package/dist/server/index.test.js +19 -0
  281. package/dist/server/rateLimit.d.ts +29 -0
  282. package/dist/server/rateLimit.js +99 -0
  283. package/dist/server/rateLimit.test.d.ts +1 -0
  284. package/dist/server/rateLimit.test.js +107 -0
  285. package/dist/server/realtimeSync.auth.test.d.ts +1 -0
  286. package/dist/server/realtimeSync.auth.test.js +62 -0
  287. package/dist/server/realtimeSync.replay.test.d.ts +1 -0
  288. package/dist/server/realtimeSync.replay.test.js +192 -0
  289. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +1 -0
  290. package/dist/server/realtimeSync.workspaceIsolation.test.js +101 -0
  291. package/dist/server/realtimeSyncWs.d.ts +30 -0
  292. package/dist/server/realtimeSyncWs.js +257 -0
  293. package/dist/server/routes.billing.d.ts +15 -0
  294. package/dist/server/routes.billing.js +991 -0
  295. package/dist/server/routes.billing.test.d.ts +1 -0
  296. package/dist/server/routes.billing.test.js +387 -0
  297. package/dist/server/routes.d.ts +72 -0
  298. package/dist/server/routes.js +4820 -0
  299. package/dist/server/routes.sync.d.ts +17 -0
  300. package/dist/server/routes.sync.integration.test.d.ts +1 -0
  301. package/dist/server/routes.sync.integration.test.js +470 -0
  302. package/dist/server/routes.sync.js +1141 -0
  303. package/dist/server/routes.test.d.ts +1 -0
  304. package/dist/server/routes.test.js +4945 -0
  305. package/dist/server/runtimeMode.d.ts +4 -0
  306. package/dist/server/runtimeMode.js +25 -0
  307. package/dist/server/runtimeMode.test.d.ts +1 -0
  308. package/dist/server/runtimeMode.test.js +26 -0
  309. package/dist/server/securityHeaders.d.ts +3 -0
  310. package/dist/server/securityHeaders.js +25 -0
  311. package/dist/server/securityHeaders.test.d.ts +1 -0
  312. package/dist/server/securityHeaders.test.js +32 -0
  313. package/dist/server/smtpTransport.d.ts +21 -0
  314. package/dist/server/smtpTransport.js +11 -0
  315. package/dist/server/start.d.ts +1 -0
  316. package/dist/server/start.js +17 -0
  317. package/dist/services/formatTransformers.d.ts +29 -0
  318. package/dist/services/formatTransformers.js +56 -0
  319. package/dist/services/templateEngine.d.ts +46 -0
  320. package/dist/services/templateEngine.js +148 -0
  321. package/dist/shared/runtimeContract.d.ts +12 -0
  322. package/dist/shared/runtimeContract.js +23 -0
  323. package/dist/shared/runtimeContract.test.d.ts +1 -0
  324. package/dist/shared/runtimeContract.test.js +23 -0
  325. package/dist/soak/preflight.cli.d.ts +1 -0
  326. package/dist/soak/preflight.cli.js +28 -0
  327. package/dist/soak/preflight.d.ts +26 -0
  328. package/dist/soak/preflight.js +113 -0
  329. package/dist/soak/preflight.test.d.ts +1 -0
  330. package/dist/soak/preflight.test.js +39 -0
  331. package/dist/soak/syncSoakHarness.d.ts +6 -0
  332. package/dist/soak/syncSoakHarness.js +10 -0
  333. package/dist/storage/createDatabaseAdapter.d.ts +2 -0
  334. package/dist/storage/createDatabaseAdapter.js +9 -0
  335. package/dist/storage/databaseAdapter.d.ts +22 -0
  336. package/dist/storage/databaseAdapter.js +1 -0
  337. package/dist/storage/documentIntegrity.d.ts +28 -0
  338. package/dist/storage/documentIntegrity.js +103 -0
  339. package/dist/storage/documentPurge.d.ts +14 -0
  340. package/dist/storage/documentPurge.js +46 -0
  341. package/dist/storage/documentRegistry.d.ts +39 -0
  342. package/dist/storage/documentRegistry.js +115 -0
  343. package/dist/storage/integritySchedule.d.ts +17 -0
  344. package/dist/storage/integritySchedule.js +62 -0
  345. package/dist/storage/objectStorage.d.ts +20 -0
  346. package/dist/storage/objectStorage.js +15 -0
  347. package/dist/storage/objectStorageClient.d.ts +18 -0
  348. package/dist/storage/objectStorageClient.js +301 -0
  349. package/dist/storage/objectStorageConfig.d.ts +2 -0
  350. package/dist/storage/objectStorageConfig.js +45 -0
  351. package/dist/storage/postgresAdapter.d.ts +20 -0
  352. package/dist/storage/postgresAdapter.js +204 -0
  353. package/dist/storage/postgresWorker.d.ts +1 -0
  354. package/dist/storage/postgresWorker.js +110 -0
  355. package/dist/storage/providerConfig.d.ts +6 -0
  356. package/dist/storage/providerConfig.js +11 -0
  357. package/dist/storage/sqliteAdapter.d.ts +14 -0
  358. package/dist/storage/sqliteAdapter.js +28 -0
  359. package/dist/storage/taskAssetStore.d.ts +56 -0
  360. package/dist/storage/taskAssetStore.js +297 -0
  361. package/dist/sync/cloudSyncApi.d.ts +47 -0
  362. package/dist/sync/cloudSyncApi.js +138 -0
  363. package/dist/sync/cloudSyncApi.test.d.ts +1 -0
  364. package/dist/sync/cloudSyncApi.test.js +54 -0
  365. package/dist/sync/encryptionService.d.ts +11 -0
  366. package/dist/sync/encryptionService.js +61 -0
  367. package/dist/sync/encryptionService.test.d.ts +1 -0
  368. package/dist/sync/encryptionService.test.js +52 -0
  369. package/dist/sync/realtimeFeature.d.ts +6 -0
  370. package/dist/sync/realtimeFeature.js +20 -0
  371. package/dist/sync/realtimeFeature.test.d.ts +1 -0
  372. package/dist/sync/realtimeFeature.test.js +30 -0
  373. package/dist/sync/realtimeReconcile.d.ts +30 -0
  374. package/dist/sync/realtimeReconcile.js +90 -0
  375. package/dist/sync/realtimeReconcile.test.d.ts +1 -0
  376. package/dist/sync/realtimeReconcile.test.js +43 -0
  377. package/dist/sync/syncService.d.ts +96 -0
  378. package/dist/sync/syncService.js +423 -0
  379. package/dist/sync/syncService.test.d.ts +1 -0
  380. package/dist/sync/syncService.test.js +484 -0
  381. package/dist/sync/workspaceSyncModel.d.ts +61 -0
  382. package/dist/sync/workspaceSyncModel.js +136 -0
  383. package/dist/sync/workspaceSyncModel.test.d.ts +1 -0
  384. package/dist/sync/workspaceSyncModel.test.js +73 -0
  385. package/dist/test/setup.d.ts +1 -0
  386. package/dist/test/setup.js +15 -0
  387. package/dist/types.d.ts +157 -0
  388. package/dist/types.js +41 -0
  389. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  390. package/dist/ui/assets/index-0W5vyCpn.css +1 -0
  391. package/dist/ui/assets/index-MCZuNNfn.js +23 -0
  392. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  393. package/dist/ui/assets/taskforce-BxLPokNB.png +0 -0
  394. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +5 -0
  395. package/dist/ui/assets/vendor-icons-ZFG3SGwo.js +1 -0
  396. package/dist/ui/assets/vendor-markdown-CKYkQHfj.js +29 -0
  397. package/dist/ui/assets/vendor-react-DdIDsh7f.js +9 -0
  398. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +3 -0
  399. package/dist/ui/branding/logos/logo_black_01.png +0 -0
  400. package/dist/ui/branding/logos/logo_black_02.png +0 -0
  401. package/dist/ui/branding/logos/logo_white_01.png +0 -0
  402. package/dist/ui/branding/logos/logo_white_02.png +0 -0
  403. package/dist/ui/favicon/android-chrome-192x192.png +0 -0
  404. package/dist/ui/favicon/android-chrome-512x512.png +0 -0
  405. package/dist/ui/favicon/apple-touch-icon.png +0 -0
  406. package/dist/ui/favicon/favicon-16x16.png +0 -0
  407. package/dist/ui/favicon/favicon-32x32.png +0 -0
  408. package/dist/ui/favicon/favicon.ico +0 -0
  409. package/dist/ui/favicon/favicon.png +0 -0
  410. package/dist/ui/favicon/site.webmanifest +1 -0
  411. package/dist/ui/fonts/RussoOne-Regular.ttf +0 -0
  412. package/dist/ui/images/taskforce.png +0 -0
  413. package/dist/ui/index.html +19 -0
  414. package/dist/utils/checklistMarkdown.d.ts +9 -0
  415. package/dist/utils/checklistMarkdown.js +46 -0
  416. package/dist/utils/constants.d.ts +21 -0
  417. package/dist/utils/constants.icons.test.d.ts +1 -0
  418. package/dist/utils/constants.icons.test.js +40 -0
  419. package/dist/utils/constants.js +100 -0
  420. package/dist/utils/contextFiles.d.ts +8 -0
  421. package/dist/utils/contextFiles.js +54 -0
  422. package/dist/utils/formatting.d.ts +5 -0
  423. package/dist/utils/formatting.js +30 -0
  424. package/dist/utils/planParser.d.ts +22 -0
  425. package/dist/utils/planParser.js +151 -0
  426. package/dist/utils/taskDragZones.d.ts +8 -0
  427. package/dist/utils/taskDragZones.js +14 -0
  428. package/dist/utils/taskDragZones.test.d.ts +1 -0
  429. package/dist/utils/taskDragZones.test.js +14 -0
  430. package/dist/utils/taskHierarchy.d.ts +4 -0
  431. package/dist/utils/taskHierarchy.js +69 -0
  432. package/dist/utils/taskHierarchy.test.d.ts +1 -0
  433. package/dist/utils/taskHierarchy.test.js +38 -0
  434. package/dist/utils/taskParenting.d.ts +3 -0
  435. package/dist/utils/taskParenting.js +24 -0
  436. package/dist/utils/taskParenting.test.d.ts +1 -0
  437. package/dist/utils/taskParenting.test.js +21 -0
  438. package/dist/utils/taskRelations.d.ts +6 -0
  439. package/dist/utils/taskRelations.js +24 -0
  440. package/dist/utils/taskRelations.test.d.ts +1 -0
  441. package/dist/utils/taskRelations.test.js +34 -0
  442. package/dist/utils/taskUtils.d.ts +2 -0
  443. package/dist/utils/taskUtils.js +20 -0
  444. package/dist/utils/taskforceApiClient.d.ts +7 -0
  445. package/dist/utils/taskforceApiClient.js +42 -0
  446. package/dist/utils/workspaceIdentity.d.ts +8 -0
  447. package/dist/utils/workspaceIdentity.js +47 -0
  448. package/dist/utils/workspaceIdentity.test.d.ts +1 -0
  449. package/dist/utils/workspaceIdentity.test.js +32 -0
  450. package/dist/utils/workspaceLifecycle.d.ts +26 -0
  451. package/dist/utils/workspaceLifecycle.js +69 -0
  452. package/dist/utils/workspaceLifecycle.test.d.ts +1 -0
  453. package/dist/utils/workspaceLifecycle.test.js +64 -0
  454. package/package.json +133 -0
  455. package/scripts/check-compat-boundaries.js +48 -0
  456. package/scripts/check-env.ts +24 -0
  457. package/scripts/check-localization-strings.mjs +49 -0
  458. package/scripts/check-runtime-boundaries.mjs +44 -0
  459. package/scripts/debug-global.mjs +34 -0
  460. package/scripts/debug-server.mjs +36 -0
  461. package/scripts/export-sqlite-to-postgres.mjs +125 -0
  462. package/scripts/generate-env-example.ts +8 -0
  463. package/scripts/generate-workflow-templates.mjs +134 -0
  464. package/scripts/mcp-stdio-adapter.mjs +160 -0
  465. package/scripts/postinstall.js +60 -0
  466. package/scripts/release-version-lib.mjs +88 -0
  467. package/scripts/release-version.mjs +72 -0
  468. package/scripts/run-dev-proxy.sh +49 -0
  469. package/scripts/run-e2e-realtime.sh +16 -0
  470. package/scripts/run-smoke.sh +23 -0
  471. package/scripts/run-soak.sh +29 -0
  472. package/scripts/test-global.js +143 -0
  473. package/scripts/update-homebrew-formula.sh +38 -0
  474. package/src/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  475. package/src/resources/templates/environments/aider.yaml +14 -0
  476. package/src/resources/templates/environments/antigravity.yaml +10 -0
  477. package/src/resources/templates/environments/claude.yaml +10 -0
  478. package/src/resources/templates/environments/cline.yaml +10 -0
  479. package/src/resources/templates/environments/codex.yaml +13 -0
  480. package/src/resources/templates/environments/copilot.yaml +10 -0
  481. package/src/resources/templates/environments/cursor.yaml +9 -0
  482. package/src/resources/templates/environments/gemini.yaml +9 -0
  483. package/src/resources/templates/environments/windsurf.yaml +10 -0
  484. package/src/resources/templates/workflow-sources/README.md +9 -0
  485. package/src/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  486. package/src/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  487. package/src/resources/templates/workflows/collaborate.yaml +104 -0
  488. package/src/resources/templates/workflows/do.yaml +82 -0
  489. package/src/resources/templates/workflows/evaluate.yaml +111 -0
  490. package/src/resources/templates/workflows/execute.yaml +142 -0
  491. package/src/resources/templates/workflows/plan.yaml +112 -0
  492. package/src/resources/templates/workflows/review.yaml +109 -0
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Taskforce Standalone Server
3
+ *
4
+ * Lightweight HTTP server with WebSocket support for live updates.
5
+ * Serves pre-built static UI and provides API endpoints.
6
+ */
7
+ import * as http from 'http';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { WebSocketServer } from 'ws';
12
+ import chokidar from 'chokidar';
13
+ import { TaskforceCore } from '../core/Taskforce.js';
14
+ import { createRoutes, matchRoute } from './routes.js';
15
+ import { applyCorsHeaders, getAllowedOriginsFromEnv } from './cors.js';
16
+ import { applyPrefixedRateLimitHeaders, applyRateLimitHeaders, InMemoryRateLimiter, resolveRateLimitKey } from './rateLimit.js';
17
+ import { isRequestAuthorized, resolveAuthConfigFromEnv, resolveRequestAccess } from './auth.js';
18
+ import { assertCloudDatabaseProvider, resolveRuntimeModeFromEnv } from './runtimeMode.js';
19
+ import { applySecurityHeaders } from './securityHeaders.js';
20
+ import { resolveDatabaseConfigFromEnv } from '../storage/providerConfig.js';
21
+ import { resolveObjectStorageConfigFromEnv } from '../storage/objectStorageConfig.js';
22
+ import { purgeExpiredDocuments } from '../storage/documentPurge.js';
23
+ import { scanDocumentIntegrity } from '../storage/documentIntegrity.js';
24
+ import { resolveStorageIntegrityScheduleSettings, shouldRunStorageIntegritySchedule } from '../storage/integritySchedule.js';
25
+ import { TaskAssetStore } from '../storage/taskAssetStore.js';
26
+ import { DocumentRegistry } from '../storage/documentRegistry.js';
27
+ import { assertEnvironment } from '../config/envSchema.js';
28
+ import { createRealtimeSyncWsManager } from './realtimeSyncWs.js';
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = path.dirname(__filename);
31
+ const MIME_TYPES = {
32
+ '.html': 'text/html',
33
+ '.js': 'application/javascript',
34
+ '.css': 'text/css',
35
+ '.json': 'application/json',
36
+ '.png': 'image/png',
37
+ '.jpg': 'image/jpeg',
38
+ '.svg': 'image/svg+xml',
39
+ '.woff2': 'font/woff2',
40
+ '.ttf': 'font/ttf',
41
+ };
42
+ function resolveStaticUiPath() {
43
+ const candidates = [
44
+ // Current output layout
45
+ path.resolve(__dirname, '../../dist/ui'),
46
+ // Legacy/alternative layouts from older package builds
47
+ path.resolve(__dirname, '../../ui'),
48
+ path.resolve(__dirname, '../../dist/public'),
49
+ path.resolve(__dirname, '../../public')
50
+ ];
51
+ for (const candidate of candidates) {
52
+ const indexPath = path.join(candidate, 'index.html');
53
+ if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
54
+ return { uiPath: candidate, candidates };
55
+ }
56
+ }
57
+ return { uiPath: null, candidates };
58
+ }
59
+ function readPositiveInteger(value, fallback) {
60
+ const parsed = Number.parseInt(String(value || '').trim(), 10);
61
+ if (!Number.isFinite(parsed) || parsed <= 0)
62
+ return fallback;
63
+ return parsed;
64
+ }
65
+ const STORAGE_INTEGRITY_HISTORY_LIMIT = 50;
66
+ const HOP_BY_HOP_HEADERS = new Set([
67
+ 'connection',
68
+ 'keep-alive',
69
+ 'proxy-authenticate',
70
+ 'proxy-authorization',
71
+ 'te',
72
+ 'trailer',
73
+ 'transfer-encoding',
74
+ 'upgrade',
75
+ ]);
76
+ const CORS_RESPONSE_HEADERS = new Set([
77
+ 'access-control-allow-origin',
78
+ 'access-control-allow-credentials',
79
+ 'access-control-allow-methods',
80
+ 'access-control-allow-headers',
81
+ 'access-control-expose-headers',
82
+ 'access-control-max-age',
83
+ 'vary'
84
+ ]);
85
+ function shouldProxyCloudPath(pathname) {
86
+ if (!pathname)
87
+ return false;
88
+ if (pathname === '/api/taskforce/auth/runtime-config')
89
+ return false;
90
+ return pathname.startsWith('/api/taskforce/auth/') || pathname.startsWith('/api/taskforce/sync/');
91
+ }
92
+ export function shouldRewriteSetCookieForLocalDevHost(hostHeader) {
93
+ const host = String(hostHeader || '').trim().toLowerCase();
94
+ if (!host)
95
+ return false;
96
+ const hostname = host.replace(/:\d+$/, '');
97
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
98
+ }
99
+ export function rewriteProxySetCookieForLocalHost(cookie) {
100
+ const parts = String(cookie || '').split(';').map((part) => part.trim()).filter(Boolean);
101
+ if (parts.length === 0)
102
+ return cookie;
103
+ const [nameValue, ...attrs] = parts;
104
+ const filteredAttrs = attrs.filter((attr) => !attr.toLowerCase().startsWith('domain='));
105
+ return [nameValue, ...filteredAttrs].join('; ');
106
+ }
107
+ async function readRequestBody(req, maxBytes) {
108
+ const chunks = [];
109
+ let total = 0;
110
+ for await (const chunk of req) {
111
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
112
+ total += buffer.length;
113
+ if (total > maxBytes) {
114
+ throw new Error(`PAYLOAD_TOO_LARGE:${maxBytes}`);
115
+ }
116
+ chunks.push(buffer);
117
+ }
118
+ return Buffer.concat(chunks);
119
+ }
120
+ export function shouldApplyAuthRateLimitForPath(pathname) {
121
+ const normalizedPath = String(pathname || '').trim();
122
+ return normalizedPath.startsWith('/api/taskforce/auth/')
123
+ && normalizedPath !== '/api/taskforce/auth/session';
124
+ }
125
+ const BILLING_WEBHOOK_PATH = '/api/taskforce/billing/webhook';
126
+ export function ensureBillingWebhookAuthExclusion(excludedPaths) {
127
+ if (excludedPaths.includes(BILLING_WEBHOOK_PATH))
128
+ return excludedPaths;
129
+ return [...excludedPaths, BILLING_WEBHOOK_PATH];
130
+ }
131
+ export function isBillingWebhookPath(pathname) {
132
+ return String(pathname || '').trim() === BILLING_WEBHOOK_PATH;
133
+ }
134
+ export function createStandaloneServer(options) {
135
+ assertEnvironment(process.env, { target: 'server', strictUnknown: true });
136
+ const db = resolveDatabaseConfigFromEnv();
137
+ const runtimeMode = resolveRuntimeModeFromEnv(process.env, db.provider);
138
+ assertCloudDatabaseProvider(runtimeMode, db.provider, process.env);
139
+ const core = new TaskforceCore({
140
+ projectRoot: options.projectRoot,
141
+ tenantId: process.env.TASKFORCE_TENANT_ID || 'default',
142
+ database: {
143
+ provider: db.provider,
144
+ connectionString: db.connectionString
145
+ }
146
+ });
147
+ const { basePath } = core.getPaths();
148
+ const taskAssetStore = new TaskAssetStore(core.getDatabaseAdapter(), core.getTenantId(), basePath, new DocumentRegistry(basePath));
149
+ const authConfig = resolveAuthConfigFromEnv(runtimeMode, process.env);
150
+ authConfig.excludedPaths = ensureBillingWebhookAuthExclusion(authConfig.excludedPaths);
151
+ const objectStorage = resolveObjectStorageConfigFromEnv(process.env);
152
+ const resolveEffectiveObjectStorage = () => {
153
+ if (objectStorage.provider !== 'r2' || !objectStorage.r2)
154
+ return objectStorage;
155
+ const globalSettings = core.getGlobalSettings();
156
+ const storageSettings = globalSettings.objectStorage && typeof globalSettings.objectStorage === 'object'
157
+ ? globalSettings.objectStorage
158
+ : {};
159
+ const keyPrefixRaw = typeof storageSettings.keyPrefix === 'string'
160
+ ? storageSettings.keyPrefix.trim().replace(/^\/+|\/+$/g, '')
161
+ : '';
162
+ const uploadTtlRaw = Number(storageSettings.signedUploadTtlSeconds);
163
+ const downloadTtlRaw = Number(storageSettings.signedDownloadTtlSeconds);
164
+ return {
165
+ provider: 'r2',
166
+ r2: {
167
+ ...objectStorage.r2,
168
+ keyPrefix: keyPrefixRaw || objectStorage.r2.keyPrefix,
169
+ signedUploadTtlSeconds: Number.isFinite(uploadTtlRaw) && uploadTtlRaw > 0
170
+ ? Math.floor(uploadTtlRaw)
171
+ : objectStorage.r2.signedUploadTtlSeconds,
172
+ signedDownloadTtlSeconds: Number.isFinite(downloadTtlRaw) && downloadTtlRaw > 0
173
+ ? Math.floor(downloadTtlRaw)
174
+ : objectStorage.r2.signedDownloadTtlSeconds
175
+ }
176
+ };
177
+ };
178
+ const routes = createRoutes(core, {
179
+ authConfig,
180
+ objectStorage: resolveEffectiveObjectStorage()
181
+ });
182
+ const allowedOrigins = getAllowedOriginsFromEnv(process.env.TASKFORCE_ALLOWED_ORIGINS);
183
+ const cloudProxyBaseUrl = String(process.env.TASKFORCE_CLOUD_PROXY_BASE_URL || '').trim().replace(/\/+$/, '');
184
+ const cloudProxyEnabled = runtimeMode === 'local' && Boolean(cloudProxyBaseUrl);
185
+ const cloudProxyClientId = String(process.env.TASKFORCE_CF_ACCESS_CLIENT_ID || process.env.CF_ACCESS_CLIENT_ID || '').trim();
186
+ const cloudProxyClientSecret = String(process.env.TASKFORCE_CF_ACCESS_CLIENT_SECRET || process.env.CF_ACCESS_CLIENT_SECRET || '').trim();
187
+ const rateLimitEnabled = typeof process.env.TASKFORCE_API_RATE_LIMIT_ENABLED === 'string'
188
+ ? process.env.TASKFORCE_API_RATE_LIMIT_ENABLED !== 'false'
189
+ : (runtimeMode === 'cloud');
190
+ const rateLimitLimit = Number.parseInt(process.env.TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS || '', 10) || 120;
191
+ const rateLimitWindowMs = Number.parseInt(process.env.TASKFORCE_API_RATE_LIMIT_WINDOW_MS || '', 10) || 60_000;
192
+ const webhookRateLimitMaxRequests = Number.parseInt(process.env.TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS || '', 10) || 600;
193
+ const webhookRateLimitWindowMs = Number.parseInt(process.env.TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS || '', 10) || 60_000;
194
+ const maxPayloadBytes = Number.parseInt(process.env.TASKFORCE_MAX_PAYLOAD_BYTES || '', 10) || 1_048_576;
195
+ const rateLimiter = new InMemoryRateLimiter(rateLimitLimit, rateLimitWindowMs);
196
+ const webhookRateLimiter = new InMemoryRateLimiter(webhookRateLimitMaxRequests, webhookRateLimitWindowMs);
197
+ const resolveAuthRateLimitSettings = () => {
198
+ const auth = core.getGlobalSettings().auth || {};
199
+ const maxRequestsRaw = Number(auth.authRateLimitMaxRequests);
200
+ const windowMsRaw = Number(auth.authRateLimitWindowMs);
201
+ return {
202
+ enabled: auth.authRateLimitEnabled !== false,
203
+ maxRequests: Number.isFinite(maxRequestsRaw) && maxRequestsRaw >= 1 ? Math.floor(maxRequestsRaw) : 10,
204
+ windowMs: Number.isFinite(windowMsRaw) && windowMsRaw >= 1000 ? Math.floor(windowMsRaw) : 15 * 60_000
205
+ };
206
+ };
207
+ let authRateLimitSettings = resolveAuthRateLimitSettings();
208
+ let authRateLimiter = new InMemoryRateLimiter(authRateLimitSettings.maxRequests, authRateLimitSettings.windowMs);
209
+ const isCollaborationPolicyEnabled = () => {
210
+ const rawDefault = String(process.env.TASKFORCE_COLLAB_POLICY_V1 || 'true').trim().toLowerCase();
211
+ const envDefaultEnabled = !(rawDefault === '0' || rawDefault === 'false' || rawDefault === 'off' || rawDefault === 'no');
212
+ const settings = core.getGlobalSettings();
213
+ const override = settings?.collaborationPolicy && typeof settings.collaborationPolicy === 'object'
214
+ ? settings.collaborationPolicy
215
+ : null;
216
+ if (typeof override?.enabled === 'boolean')
217
+ return override.enabled;
218
+ return envDefaultEnabled;
219
+ };
220
+ const getCollaborationTelemetryState = () => {
221
+ const settings = core.getGlobalSettings();
222
+ const raw = settings?.collaborationTelemetry || {};
223
+ const counters = raw?.counters && typeof raw.counters === 'object'
224
+ ? raw.counters
225
+ : {};
226
+ const recent = Array.isArray(raw?.recent)
227
+ ? raw.recent
228
+ .filter((item) => item && typeof item === 'object')
229
+ .slice(0, 100)
230
+ .map((item) => ({
231
+ ts: String(item.ts || ''),
232
+ scope: item.scope === 'auth' || item.scope === 'admin' || item.scope === 'member-mutation'
233
+ ? item.scope
234
+ : 'auth',
235
+ method: String(item.method || 'GET'),
236
+ path: String(item.path || ''),
237
+ statusCode: Number(item.statusCode) || 0
238
+ }))
239
+ : [];
240
+ return {
241
+ counters,
242
+ updatedAt: typeof raw?.updatedAt === 'string' ? raw.updatedAt : null,
243
+ recent
244
+ };
245
+ };
246
+ const appendCollaborationTelemetry = (entry, counterKeys) => {
247
+ const current = getCollaborationTelemetryState();
248
+ const counters = { ...current.counters };
249
+ for (const key of counterKeys) {
250
+ counters[key] = Number(counters[key] || 0) + 1;
251
+ }
252
+ core.updateGlobalSettings({
253
+ collaborationTelemetry: {
254
+ counters,
255
+ updatedAt: entry.ts,
256
+ recent: [entry, ...current.recent].slice(0, 100)
257
+ }
258
+ });
259
+ };
260
+ const recordCollaborationRouteTelemetry = (pathname, methodRaw, statusCodeRaw) => {
261
+ const method = String(methodRaw || 'GET').toUpperCase();
262
+ const statusCode = Number(statusCodeRaw);
263
+ const isAuth = pathname.startsWith('/api/taskforce/auth/');
264
+ const isAdmin = pathname.startsWith('/api/taskforce/admin/');
265
+ const isMutation = method === 'POST' || method === 'PATCH' || method === 'DELETE';
266
+ const isMemberMutation = isMutation && pathname.startsWith('/api/taskforce/') && !isAuth && !isAdmin;
267
+ const scope = isAuth
268
+ ? 'auth'
269
+ : (isAdmin ? 'admin' : (isMemberMutation ? 'member-mutation' : null));
270
+ if (!scope)
271
+ return;
272
+ const counterKeys = [`${scope}.requests.total`, `status.${statusCode}`];
273
+ if (statusCode === 401 || statusCode === 403) {
274
+ counterKeys.push(`${scope}.status.${statusCode}`);
275
+ }
276
+ if (pathname === '/api/taskforce/auth/invite/accept' && statusCode >= 400) {
277
+ counterKeys.push('auth.invite_accept.failures');
278
+ }
279
+ if (counterKeys.length <= 2 && statusCode !== 401 && statusCode !== 403 && pathname !== '/api/taskforce/auth/invite/accept') {
280
+ return;
281
+ }
282
+ appendCollaborationTelemetry({
283
+ ts: new Date().toISOString(),
284
+ scope,
285
+ method,
286
+ path: pathname,
287
+ statusCode
288
+ }, counterKeys);
289
+ };
290
+ // Resolve static files directory at runtime
291
+ // Standalone UI source now lives in apps/app and builds to dist/ui.
292
+ // When built: dist/server/index.js -> dist/ui/
293
+ // When source: src/server/index.ts -> dist/ui/
294
+ const uiResolution = resolveStaticUiPath();
295
+ const distUiPath = uiResolution.uiPath;
296
+ if (distUiPath) {
297
+ console.log(`[Taskforce] Serving static files from: ${distUiPath}`);
298
+ }
299
+ else {
300
+ console.warn('[Taskforce] UI bundle not found. Checked:');
301
+ for (const candidate of uiResolution.candidates) {
302
+ console.warn(` - ${candidate}`);
303
+ }
304
+ }
305
+ console.log(`[Taskforce] Watching for changes in: ${basePath}`);
306
+ console.log(`[Taskforce] Runtime mode: ${runtimeMode}`);
307
+ if (rateLimitEnabled) {
308
+ console.log(`[Taskforce] API rate limit enabled: ${rateLimitLimit} req/${Math.ceil(rateLimitWindowMs / 1000)}s`);
309
+ console.log(`[Taskforce] Webhook rate limit enabled: ${webhookRateLimitMaxRequests} req/${Math.ceil(webhookRateLimitWindowMs / 1000)}s`);
310
+ }
311
+ else {
312
+ console.log('[Taskforce] API rate limit disabled');
313
+ }
314
+ if (authRateLimitSettings.enabled) {
315
+ console.log(`[Taskforce] Auth rate limit enabled: ${authRateLimitSettings.maxRequests} req/${Math.ceil(authRateLimitSettings.windowMs / 1000)}s`);
316
+ }
317
+ else {
318
+ console.log('[Taskforce] Auth rate limit disabled');
319
+ }
320
+ if (authConfig.enabled && authConfig.requiredForApi) {
321
+ console.log('[Taskforce] API auth enabled');
322
+ }
323
+ else {
324
+ console.log('[Taskforce] API auth disabled');
325
+ }
326
+ console.log(`[Taskforce] Auth session cookie domain: ${authConfig.sessionCookieDomain || '(host-only)'}`);
327
+ console.log(`[Taskforce] Object storage provider: ${objectStorage.provider}`);
328
+ if (objectStorage.provider === 'r2' && objectStorage.r2) {
329
+ console.log(`[Taskforce] Object storage bucket: ${objectStorage.r2.bucket}`);
330
+ }
331
+ console.log(`[Taskforce] Max API payload size: ${maxPayloadBytes} bytes`);
332
+ if (cloudProxyEnabled) {
333
+ console.log(`[Taskforce] Cloud proxy enabled: ${cloudProxyBaseUrl}`);
334
+ }
335
+ const purgeEnabled = process.env.TASKFORCE_DOCUMENT_PURGE_ENABLED !== 'false';
336
+ const purgeIntervalMinutes = readPositiveInteger(process.env.TASKFORCE_DOCUMENT_PURGE_INTERVAL_MINUTES, 15);
337
+ if (purgeEnabled) {
338
+ console.log(`[Taskforce] Document purge enabled: every ${purgeIntervalMinutes} minute(s)`);
339
+ }
340
+ else {
341
+ console.log('[Taskforce] Document purge disabled');
342
+ }
343
+ const integritySchedulePollMinutes = readPositiveInteger(process.env.TASKFORCE_STORAGE_INTEGRITY_SCHEDULE_POLL_MINUTES, 1);
344
+ console.log(`[Taskforce] Storage integrity scheduler poll: every ${integritySchedulePollMinutes} minute(s)`);
345
+ const server = http.createServer(async (req, res) => {
346
+ const url = new URL(req.url || '/', `http://localhost`);
347
+ applySecurityHeaders(res, runtimeMode);
348
+ // Handle CORS preflight
349
+ if (req.method === 'OPTIONS') {
350
+ const cors = applyCorsHeaders(req, res, allowedOrigins);
351
+ if (!cors.allowed) {
352
+ console.warn(`[Taskforce] CORS preflight rejected origin="${String(req.headers.origin || '')}" path="${url.pathname}"`);
353
+ res.writeHead(403, { 'Content-Type': 'application/json' });
354
+ res.end(JSON.stringify({ error: 'CORS origin not allowed' }));
355
+ return;
356
+ }
357
+ res.writeHead(200);
358
+ res.end();
359
+ return;
360
+ }
361
+ if (cloudProxyEnabled && shouldProxyCloudPath(url.pathname)) {
362
+ const cors = applyCorsHeaders(req, res, allowedOrigins);
363
+ if (!cors.allowed) {
364
+ res.writeHead(403, { 'Content-Type': 'application/json' });
365
+ res.end(JSON.stringify({ error: 'CORS origin not allowed' }));
366
+ return;
367
+ }
368
+ try {
369
+ const method = String(req.method || 'GET').toUpperCase();
370
+ const upstreamUrl = `${cloudProxyBaseUrl}${url.pathname}${url.search || ''}`;
371
+ const headers = new Headers();
372
+ for (const [key, value] of Object.entries(req.headers)) {
373
+ const name = key.toLowerCase();
374
+ if (!value)
375
+ continue;
376
+ if (name === 'host' || name === 'content-length' || HOP_BY_HOP_HEADERS.has(name))
377
+ continue;
378
+ const serialized = Array.isArray(value) ? value.join(', ') : String(value);
379
+ if (!serialized.trim())
380
+ continue;
381
+ headers.set(key, serialized);
382
+ }
383
+ if (cloudProxyClientId && cloudProxyClientSecret) {
384
+ headers.set('CF-Access-Client-Id', cloudProxyClientId);
385
+ headers.set('CF-Access-Client-Secret', cloudProxyClientSecret);
386
+ }
387
+ const hasBody = method !== 'GET' && method !== 'HEAD';
388
+ const body = hasBody ? await readRequestBody(req, maxPayloadBytes) : undefined;
389
+ const upstream = await fetch(upstreamUrl, {
390
+ method,
391
+ headers,
392
+ body: body && body.length > 0 ? new Uint8Array(body) : undefined,
393
+ redirect: 'manual'
394
+ });
395
+ const outgoingHeaders = {};
396
+ for (const [key, value] of upstream.headers.entries()) {
397
+ const name = key.toLowerCase();
398
+ if (HOP_BY_HOP_HEADERS.has(name))
399
+ continue;
400
+ if (CORS_RESPONSE_HEADERS.has(name))
401
+ continue;
402
+ if (name === 'set-cookie')
403
+ continue;
404
+ outgoingHeaders[key] = value;
405
+ }
406
+ const setCookies = upstream.headers.getSetCookie?.();
407
+ if (Array.isArray(setCookies) && setCookies.length > 0) {
408
+ const normalizedSetCookies = shouldRewriteSetCookieForLocalDevHost(String(req.headers.host || '').trim())
409
+ ? setCookies.map((cookie) => rewriteProxySetCookieForLocalHost(cookie))
410
+ : setCookies;
411
+ res.setHeader('Set-Cookie', normalizedSetCookies);
412
+ }
413
+ res.writeHead(upstream.status, outgoingHeaders);
414
+ const responseBuffer = Buffer.from(await upstream.arrayBuffer());
415
+ res.end(responseBuffer);
416
+ }
417
+ catch (error) {
418
+ const message = String(error?.message || '');
419
+ if (message.startsWith('PAYLOAD_TOO_LARGE:')) {
420
+ const max = Number(message.split(':')[1] || maxPayloadBytes);
421
+ res.writeHead(413, { 'Content-Type': 'application/json' });
422
+ res.end(JSON.stringify({ error: `Payload too large (max ${max} bytes)` }));
423
+ return;
424
+ }
425
+ res.writeHead(502, { 'Content-Type': 'application/json' });
426
+ res.end(JSON.stringify({ error: 'Cloud proxy request failed.' }));
427
+ }
428
+ return;
429
+ }
430
+ // 1. Try API routes first (with dynamic param matching)
431
+ const match = matchRoute(req.method || 'GET', url.pathname, routes);
432
+ if (match) {
433
+ let observedStatusCode = null;
434
+ const originalWriteHead = res.writeHead.bind(res);
435
+ res.writeHead = ((statusCode, ...args) => {
436
+ observedStatusCode = Number(statusCode);
437
+ return originalWriteHead(statusCode, ...args);
438
+ });
439
+ try {
440
+ req.headers['x-taskforce-runtime-mode'] = runtimeMode;
441
+ req.headers['x-taskforce-auth-required'] = String(authConfig.enabled && authConfig.requiredForApi);
442
+ res.req = req;
443
+ // Apply CORS headers + reject disallowed origins.
444
+ const cors = applyCorsHeaders(req, res, allowedOrigins);
445
+ if (!cors.allowed) {
446
+ console.warn(`[Taskforce] CORS request rejected origin="${String(req.headers.origin || '')}" path="${url.pathname}" method="${req.method || 'GET'}"`);
447
+ res.writeHead(403, { 'Content-Type': 'application/json' });
448
+ res.end(JSON.stringify({ error: 'CORS origin not allowed' }));
449
+ return;
450
+ }
451
+ // Enforce request payload guard before route handlers parse bodies.
452
+ const contentLength = req.headers['content-length'];
453
+ if (typeof contentLength === 'string') {
454
+ const contentLengthNumber = Number.parseInt(contentLength, 10);
455
+ if (Number.isFinite(contentLengthNumber) && contentLengthNumber > maxPayloadBytes) {
456
+ req.resume();
457
+ res.writeHead(413, { 'Content-Type': 'application/json' });
458
+ res.end(JSON.stringify({ error: `Payload too large (max ${maxPayloadBytes} bytes)` }));
459
+ return;
460
+ }
461
+ }
462
+ const isAuthApiPath = url.pathname.startsWith('/api/taskforce/auth/');
463
+ const shouldApplyAuthRateLimit = shouldApplyAuthRateLimitForPath(url.pathname);
464
+ const accessForRateLimit = resolveRequestAccess(req, authConfig);
465
+ const isBillingWebhook = isBillingWebhookPath(url.pathname);
466
+ if (rateLimitEnabled && !isAuthApiPath) {
467
+ const key = resolveRateLimitKey({
468
+ req,
469
+ pathname: url.pathname,
470
+ method: req.method,
471
+ authenticated: accessForRateLimit.authenticated,
472
+ sessionId: accessForRateLimit.sessionId,
473
+ userId: accessForRateLimit.userId
474
+ });
475
+ const rateLimitResult = isBillingWebhook
476
+ ? webhookRateLimiter.check(key)
477
+ : rateLimiter.check(key);
478
+ if (isBillingWebhook) {
479
+ applyPrefixedRateLimitHeaders(res, rateLimitResult, 'X-Webhook-RateLimit');
480
+ }
481
+ else {
482
+ applyRateLimitHeaders(res, rateLimitResult);
483
+ }
484
+ if (!rateLimitResult.allowed) {
485
+ res.writeHead(429, { 'Content-Type': 'application/json' });
486
+ res.end(JSON.stringify({ error: 'Rate limit exceeded. Retry later.' }));
487
+ return;
488
+ }
489
+ }
490
+ if (isAuthApiPath) {
491
+ const latestAuthRateSettings = resolveAuthRateLimitSettings();
492
+ if (latestAuthRateSettings.enabled !== authRateLimitSettings.enabled
493
+ || latestAuthRateSettings.maxRequests !== authRateLimitSettings.maxRequests
494
+ || latestAuthRateSettings.windowMs !== authRateLimitSettings.windowMs) {
495
+ authRateLimitSettings = latestAuthRateSettings;
496
+ authRateLimiter = new InMemoryRateLimiter(authRateLimitSettings.maxRequests, authRateLimitSettings.windowMs);
497
+ if (authRateLimitSettings.enabled) {
498
+ console.log(`[Taskforce] Auth rate limit updated: ${authRateLimitSettings.maxRequests} req/${Math.ceil(authRateLimitSettings.windowMs / 1000)}s`);
499
+ }
500
+ else {
501
+ console.log('[Taskforce] Auth rate limit updated: disabled');
502
+ }
503
+ }
504
+ }
505
+ if (authRateLimitSettings.enabled && shouldApplyAuthRateLimit) {
506
+ const key = resolveRateLimitKey({
507
+ req,
508
+ pathname: url.pathname,
509
+ method: req.method,
510
+ authenticated: accessForRateLimit.authenticated,
511
+ sessionId: accessForRateLimit.sessionId,
512
+ userId: accessForRateLimit.userId
513
+ });
514
+ const authRateResult = authRateLimiter.check(key);
515
+ applyPrefixedRateLimitHeaders(res, authRateResult, 'X-Auth-RateLimit');
516
+ if (!authRateResult.allowed) {
517
+ res.writeHead(429, { 'Content-Type': 'application/json' });
518
+ res.end(JSON.stringify({ error: 'Too many auth attempts. Retry later.' }));
519
+ return;
520
+ }
521
+ }
522
+ const auth = isRequestAuthorized(req, authConfig);
523
+ if (!auth.allowed) {
524
+ res.writeHead(401, { 'Content-Type': 'application/json' });
525
+ res.end(JSON.stringify({ error: auth.reason || 'Unauthorized' }));
526
+ return;
527
+ }
528
+ const pathname = url.pathname;
529
+ const isMutation = (req.method === 'POST' || req.method === 'PATCH' || req.method === 'DELETE')
530
+ && pathname.startsWith('/api/taskforce/')
531
+ && !pathname.startsWith('/api/taskforce/auth/')
532
+ && pathname !== '/api/taskforce/health';
533
+ let access = accessForRateLimit;
534
+ const isAuthRoute = pathname.startsWith('/api/taskforce/auth/');
535
+ if (access.authenticated) {
536
+ const resolved = core.resolveUserAccess(access.userId, access.workspaceId, access.role);
537
+ if (resolved) {
538
+ const systemRole = core.getSystemRole(resolved.userId);
539
+ access = {
540
+ ...access,
541
+ role: resolved.role,
542
+ workspaceId: resolved.workspaceId,
543
+ userId: resolved.userId,
544
+ systemRole
545
+ };
546
+ if (resolved.disabled && !isAuthRoute) {
547
+ res.writeHead(403, { 'Content-Type': 'application/json' });
548
+ res.end(JSON.stringify({ error: 'Forbidden: user account is disabled.' }));
549
+ return;
550
+ }
551
+ }
552
+ }
553
+ req.headers['x-taskforce-role'] = access.role;
554
+ req.headers['x-taskforce-system-role'] = access.systemRole;
555
+ req.headers['x-taskforce-workspace-id'] = access.workspaceId;
556
+ req.headers['x-taskforce-user-id'] = access.userId;
557
+ if (isCollaborationPolicyEnabled() && isMutation && access.role === 'read-only') {
558
+ res.writeHead(403, { 'Content-Type': 'application/json' });
559
+ res.end(JSON.stringify({ error: 'Forbidden: read-only role cannot mutate data.' }));
560
+ return;
561
+ }
562
+ await match.handler(req, res, match.params);
563
+ }
564
+ catch (error) {
565
+ console.error('[Taskforce] API error:', error);
566
+ const statusCode = Number.isFinite(Number(error?.statusCode))
567
+ ? Math.max(400, Math.min(599, Number(error.statusCode)))
568
+ : 500;
569
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
570
+ res.end(JSON.stringify({ error: error.message }));
571
+ }
572
+ finally {
573
+ res.writeHead = originalWriteHead;
574
+ const finalStatus = Number.isFinite(Number(observedStatusCode)) ? Number(observedStatusCode) : 200;
575
+ recordCollaborationRouteTelemetry(url.pathname, req.method || 'GET', finalStatus);
576
+ }
577
+ return;
578
+ }
579
+ // 2. Serve static files from dist/ui/
580
+ let filePath = url.pathname.replace(/^\/taskforce/, '');
581
+ if (filePath === '' || filePath === '/')
582
+ filePath = '/index.html';
583
+ const fullPath = distUiPath ? path.join(distUiPath, filePath) : '';
584
+ if (distUiPath && fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
585
+ const ext = path.extname(fullPath);
586
+ res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' });
587
+ fs.createReadStream(fullPath).pipe(res);
588
+ return;
589
+ }
590
+ // 3. SPA fallback: serve index.html for client-side routing
591
+ const indexPath = distUiPath ? path.join(distUiPath, 'index.html') : '';
592
+ if (!url.pathname.startsWith('/api/') && distUiPath && fs.existsSync(indexPath)) {
593
+ res.writeHead(200, { 'Content-Type': 'text/html' });
594
+ fs.createReadStream(indexPath).pipe(res);
595
+ return;
596
+ }
597
+ // 4. 404 fallback (or missing UI diagnostics)
598
+ if (!url.pathname.startsWith('/api/') && !distUiPath) {
599
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
600
+ res.end(`Taskforce UI bundle not found.\nChecked:\n${uiResolution.candidates.map((p) => `- ${p}`).join('\n')}`);
601
+ return;
602
+ }
603
+ res.writeHead(404);
604
+ res.end('Not Found');
605
+ });
606
+ // WebSocket server at /Taskforce-ws path
607
+ const wss = new WebSocketServer({ server, path: '/taskforce-ws' });
608
+ const realtimeWsManager = createRealtimeSyncWsManager(core, authConfig);
609
+ const offRealtimeMutation = core.onRealtimeMutation((event) => {
610
+ realtimeWsManager.broadcastMutation(event);
611
+ });
612
+ // Prevent WebSocketServer from crashing the process on server errors (like EADDRINUSE)
613
+ wss.on('error', (err) => {
614
+ // Only log if it's not a bind error (which we handle in CLI)
615
+ if (err.code !== 'EADDRINUSE') {
616
+ console.error('[Taskforce] WebSocket error:', err);
617
+ }
618
+ });
619
+ server.once('listening', () => {
620
+ let purgeTimer = null;
621
+ let integrityScheduleTimer = null;
622
+ const appendDocumentPurgeHistory = (summary) => {
623
+ const settings = core.getGlobalSettings();
624
+ const existing = Array.isArray(settings?.documentPurgeHistory)
625
+ ? settings.documentPurgeHistory
626
+ : [];
627
+ const history = [{
628
+ id: `purge-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
629
+ ranAt: new Date().toISOString(),
630
+ actorUserId: 'system',
631
+ actorRole: 'system',
632
+ workspaceId: 'system',
633
+ summary
634
+ }, ...existing].slice(0, STORAGE_INTEGRITY_HISTORY_LIMIT);
635
+ core.updateGlobalSettings({
636
+ documentPurgeHistory: history
637
+ });
638
+ };
639
+ const runDocumentPurge = async () => {
640
+ try {
641
+ const { basePath, taskDataDir } = core.getPaths();
642
+ const summary = await purgeExpiredDocuments({
643
+ basePath,
644
+ taskDataDir,
645
+ objectStorage: resolveEffectiveObjectStorage(),
646
+ assetStore: taskAssetStore
647
+ });
648
+ appendDocumentPurgeHistory(summary);
649
+ if (summary.total > 0) {
650
+ console.log(`[Taskforce] Document purge run: total=${summary.total} purged=${summary.purged} failed=${summary.failed}`);
651
+ }
652
+ }
653
+ catch (error) {
654
+ console.warn(`[Taskforce] Document purge failed: ${String(error?.message || error)}`);
655
+ }
656
+ };
657
+ if (purgeEnabled) {
658
+ void runDocumentPurge();
659
+ purgeTimer = setInterval(() => {
660
+ void runDocumentPurge();
661
+ }, purgeIntervalMinutes * 60_000);
662
+ }
663
+ const appendStorageIntegrityHistory = (item) => {
664
+ const settings = core.getGlobalSettings();
665
+ const existing = Array.isArray(settings?.storageIntegrityHistory)
666
+ ? settings.storageIntegrityHistory
667
+ : [];
668
+ const history = [item, ...existing].slice(0, STORAGE_INTEGRITY_HISTORY_LIMIT);
669
+ core.updateGlobalSettings({
670
+ storageIntegrityHistory: history
671
+ });
672
+ };
673
+ const runScheduledIntegrityScanIfDue = async () => {
674
+ try {
675
+ const globalSettings = core.getGlobalSettings();
676
+ const schedule = resolveStorageIntegrityScheduleSettings(globalSettings);
677
+ const now = new Date();
678
+ if (!shouldRunStorageIntegritySchedule(schedule, now))
679
+ return;
680
+ const { basePath } = core.getPaths();
681
+ const result = await scanDocumentIntegrity({
682
+ basePath,
683
+ objectStorage: resolveEffectiveObjectStorage(),
684
+ verifyHash: schedule.verifyHash,
685
+ includeDeleted: schedule.includeDeleted,
686
+ requireMirror: schedule.requireMirror,
687
+ limit: schedule.limit,
688
+ assetStore: taskAssetStore
689
+ });
690
+ core.updateGlobalSettings({
691
+ storageIntegritySchedule: {
692
+ ...schedule,
693
+ lastRunAt: now.toISOString()
694
+ }
695
+ });
696
+ appendStorageIntegrityHistory({
697
+ id: `integrity-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
698
+ ranAt: now.toISOString(),
699
+ actorUserId: 'system',
700
+ actorRole: 'system',
701
+ workspaceId: 'system',
702
+ options: {
703
+ verifyHash: schedule.verifyHash,
704
+ includeDeleted: schedule.includeDeleted,
705
+ requireMirror: schedule.requireMirror,
706
+ limit: schedule.limit
707
+ },
708
+ summary: result.summary
709
+ });
710
+ console.log(`[Taskforce] Scheduled integrity scan: checked=${result.summary.totalChecked} issues=${result.summary.totalIssues}`);
711
+ }
712
+ catch (error) {
713
+ console.warn(`[Taskforce] Scheduled integrity scan failed: ${String(error?.message || error)}`);
714
+ }
715
+ };
716
+ integrityScheduleTimer = setInterval(() => {
717
+ void runScheduledIntegrityScanIfDue();
718
+ }, integritySchedulePollMinutes * 60_000);
719
+ void runScheduledIntegrityScanIfDue();
720
+ wss.on('connection', (ws, req) => {
721
+ realtimeWsManager.handleConnection(ws, req);
722
+ });
723
+ // Use chokidar for cross-platform file watching (works on Linux)
724
+ const watcher = chokidar.watch(basePath, {
725
+ persistent: true,
726
+ ignoreInitial: true,
727
+ awaitWriteFinish: { stabilityThreshold: 100 }
728
+ });
729
+ watcher.on('change', (filepath) => {
730
+ if (filepath.endsWith('.json') || filepath.endsWith('.db') || filepath.endsWith('.db-wal')) {
731
+ console.log(`[Taskforce] File changed: ${path.basename(filepath)}`);
732
+ realtimeWsManager.broadcastTaskforceUpdate();
733
+ }
734
+ });
735
+ // Graceful shutdown
736
+ const shutdown = () => {
737
+ console.log('\n[Taskforce] Shutting down...');
738
+ if (purgeTimer) {
739
+ clearInterval(purgeTimer);
740
+ purgeTimer = null;
741
+ }
742
+ if (integrityScheduleTimer) {
743
+ clearInterval(integrityScheduleTimer);
744
+ integrityScheduleTimer = null;
745
+ }
746
+ watcher.close();
747
+ offRealtimeMutation();
748
+ wss.clients.forEach(client => client.close());
749
+ wss.close();
750
+ server.close();
751
+ process.exit(0);
752
+ };
753
+ process.on('SIGTERM', shutdown);
754
+ process.on('SIGINT', shutdown);
755
+ });
756
+ return server;
757
+ }