@taskforcehq/taskforce 0.3.328 → 0.3.329

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 (264) hide show
  1. package/dist/Taskforce.module.css +233 -34
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  8. package/dist/components/features/TaskSettings.d.ts +12 -1
  9. package/dist/components/features/TaskSettings.js +147 -40
  10. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  11. package/dist/components/features/planning/PlanningModule.js +2 -5
  12. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  13. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  14. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  15. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  16. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  17. package/dist/components/task/TaskCard.d.ts +5 -0
  18. package/dist/components/task/TaskCard.js +65 -10
  19. package/dist/components/task/TaskImageReviews.js +14 -5
  20. package/dist/components/task/TaskKanban.d.ts +6 -1
  21. package/dist/components/task/TaskKanban.js +152 -6
  22. package/dist/components/task/TaskList.d.ts +5 -0
  23. package/dist/components/task/TaskList.js +3 -3
  24. package/dist/components/ui/PasswordInput.d.ts +6 -0
  25. package/dist/components/ui/PasswordInput.js +11 -0
  26. package/dist/components/ui/TopNoticeLayer.js +1 -1
  27. package/dist/components/views/AppShellHeader.js +32 -11
  28. package/dist/components/views/PlanComparisonPage.js +12 -0
  29. package/dist/components/views/StandaloneLayout.js +7 -31
  30. package/dist/components/views/WidgetView.js +3 -3
  31. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  32. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  33. package/dist/config/envSchema.js +12 -0
  34. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  35. package/dist/config/localServiceRuntimeContract.js +135 -0
  36. package/dist/core/AiProfileService.js +46 -8
  37. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  38. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  39. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  40. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  41. package/dist/core/Taskforce.d.ts +62 -2
  42. package/dist/core/Taskforce.js +635 -32
  43. package/dist/core/types.d.ts +6 -0
  44. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  45. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  46. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  47. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  48. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  49. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  50. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  51. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  52. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  53. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  54. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  55. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  56. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  57. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  58. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  59. package/dist/hooks/ui/useSettingsModel.js +13 -2
  60. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  61. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  62. package/dist/hooks/useFilterSort.d.ts +7 -3
  63. package/dist/hooks/useFilterSort.js +15 -5
  64. package/dist/hooks/useTaskData.d.ts +10 -1
  65. package/dist/hooks/useTaskData.js +216 -7
  66. package/dist/hooks/useTaskMutations.d.ts +10 -2
  67. package/dist/hooks/useTaskMutations.js +223 -43
  68. package/dist/hooks/useTaskforce.d.ts +37 -9
  69. package/dist/hooks/useTaskforce.js +349 -39
  70. package/dist/hooks/useUiNotice.d.ts +4 -0
  71. package/dist/hooks/useUiNotice.js +4 -0
  72. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  73. package/dist/mcp/documentAssetRegistrar.js +44 -35
  74. package/dist/mcp/documentHelpers.d.ts +1 -0
  75. package/dist/mcp/documentHelpers.js +7 -3
  76. package/dist/mcp/runtime.js +172 -42
  77. package/dist/mcp/taskPlanningRegistrar.js +41 -38
  78. package/dist/mcp/toolCallCompatibility.js +38 -1
  79. package/dist/mcp/toolCatalog.d.ts +1 -0
  80. package/dist/migrations/taskSchemaMigrations.js +12 -0
  81. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  82. package/dist/runtime/appGateDefinitions.js +6 -6
  83. package/dist/runtime/appGates.d.ts +12 -13
  84. package/dist/runtime/appGates.js +29 -13
  85. package/dist/runtime/nodeAppGates.d.ts +8 -0
  86. package/dist/runtime/nodeAppGates.js +27 -0
  87. package/dist/server/cloudCollectionRead.d.ts +1 -1
  88. package/dist/server/cloudCollectionRead.js +2 -2
  89. package/dist/server/index.d.ts +3 -0
  90. package/dist/server/index.js +15 -3
  91. package/dist/server/localServiceLease.d.ts +3 -0
  92. package/dist/server/localServiceLease.js +9 -1
  93. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  94. package/dist/server/rateLimit.d.ts +1 -0
  95. package/dist/server/rateLimit.js +4 -0
  96. package/dist/server/realtimeSyncWs.js +36 -12
  97. package/dist/server/routes/admin.js +1 -0
  98. package/dist/server/routes/agentRoles.js +3 -2
  99. package/dist/server/routes/agentSkills.js +3 -2
  100. package/dist/server/routes/agents.js +118 -16
  101. package/dist/server/routes/annotatedAttachments.js +3 -2
  102. package/dist/server/routes/auth.js +3 -1
  103. package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
  104. package/dist/server/routes/localService.js +2 -0
  105. package/dist/server/routes/primitives.d.ts +2 -0
  106. package/dist/server/routes/syncAuxRoutes.js +5 -2
  107. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  108. package/dist/server/routes/syncV3FeedRoutes.js +18 -16
  109. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  110. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  111. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  112. package/dist/server/routes/syncV3OperationRoutes.js +10 -0
  113. package/dist/server/routes/tasks.js +103 -11
  114. package/dist/server/routes/workspaces.js +43 -1
  115. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  116. package/dist/server/workspaceDeletionAdmission.js +50 -0
  117. package/dist/service/localServiceCli.js +16 -0
  118. package/dist/service/localServiceClientLifecycle.js +36 -10
  119. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  120. package/dist/services/codexAppServerModelGateway.js +438 -0
  121. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  122. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  123. package/dist/shared/taskforceAgentDefinition.js +7 -1
  124. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  125. package/dist/shared/taskforceAgentModels.js +30 -1
  126. package/dist/storage/postgresAdapter.d.ts +20 -3
  127. package/dist/storage/postgresAdapter.js +106 -14
  128. package/dist/storage/postgresWorker.js +13 -4
  129. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  130. package/dist/storage/postgresWorkerProtocol.js +30 -0
  131. package/dist/sync/cloudSyncApi.d.ts +4 -1
  132. package/dist/sync/cloudSyncApi.js +7 -2
  133. package/dist/sync/contentSyncState.d.ts +1 -1
  134. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  135. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
  136. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  137. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  138. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  139. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  140. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  141. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  142. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  143. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  144. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
  145. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  146. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  147. package/dist/sync/syncService.d.ts +2 -0
  148. package/dist/sync/syncService.js +4 -1
  149. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  150. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  151. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
  152. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  153. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  154. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  155. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  156. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  157. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  158. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  159. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  160. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  161. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  162. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  163. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  164. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  165. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  166. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  167. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  168. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  169. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  170. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  171. package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
  172. package/dist/sync/v3/syncDurabilityStore.js +31 -11
  173. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  174. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  175. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  176. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  177. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  178. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  179. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  180. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  181. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  182. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  183. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  184. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  185. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  186. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  187. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  188. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  189. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  190. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  191. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  192. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  193. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
  194. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  195. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  196. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
  197. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
  198. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
  199. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
  200. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  201. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
  202. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  203. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  204. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  205. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  206. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  207. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  208. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  209. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  210. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  211. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  212. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  213. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  214. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  215. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  216. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
  217. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  218. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  219. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  220. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  221. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  222. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  223. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  224. package/dist/sync/workspacePullFeed.js +2 -1
  225. package/dist/sync/workspaceRepair.d.ts +3 -0
  226. package/dist/sync/workspaceRepair.js +67 -6
  227. package/dist/types.d.ts +47 -1
  228. package/dist/types.js +6 -0
  229. package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  230. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  231. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  232. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  233. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
  234. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
  235. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
  236. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
  237. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  238. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  239. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  240. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  241. package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  242. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
  243. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  244. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  245. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
  246. package/dist/ui/index.html +3 -3
  247. package/dist/utils/taskActivity.d.ts +3 -2
  248. package/dist/utils/taskActivity.js +19 -2
  249. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  250. package/dist/utils/taskEditorReconciliation.js +217 -0
  251. package/dist/utils/taskNormalization.d.ts +1 -0
  252. package/dist/utils/taskNormalization.js +39 -0
  253. package/dist/utils/taskSearch.js +2 -2
  254. package/dist/utils/uiNotice.d.ts +4 -0
  255. package/package.json +16 -4
  256. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  257. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  258. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  259. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  260. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  261. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  262. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  263. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  264. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -32,7 +32,8 @@ import { WorkspaceSyncContentVersionStore, } from '../sync/engine/workspaceSyncC
32
32
  import { WorkspaceSyncPublishedContentResolver } from '../sync/engine/workspaceSyncPublishedContentResolver.js';
33
33
  import { createWorkspaceSyncServerContentRuntime } from '../sync/engine/workspaceSyncServerContentRuntime.js';
34
34
  import { resolveVirusScanConfigFromEnv, scanBufferForThreats } from '../security/virusScan.js';
35
- import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../runtime/appGates.js';
35
+ import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, } from '../runtime/appGates.js';
36
+ import { resolveNodeAppGateAccess } from '../runtime/nodeAppGates.js';
36
37
  import { getDocumentReferenceLabel, parseDocumentReference } from '../utils/documentReferences.js';
37
38
  import { getImageReferenceLabel, parseImageReference } from '../utils/imageReferences.js';
38
39
  import { inferCanonicalAssetKind, isValidCanonicalDocumentAsset } from '../utils/canonicalAssetKind.js';
@@ -1208,7 +1209,9 @@ export function createTaskforceMcpServer(options = {}) {
1208
1209
  },
1209
1210
  tools: {},
1210
1211
  },
1211
- instructions: `Before writes, check get_current_profile; if unbound, use resolve_profile. Keep profileToken private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`,
1212
+ instructions: runtimeMode === 'cloud' && options.auth?.actorType === 'token'
1213
+ ? `Cloud access-token clients must pass profileToken on every write; obtain it from resolve_profile and keep it private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`
1214
+ : `Before writes, check get_current_profile; if unbound, use resolve_profile. Keep profileToken private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`,
1212
1215
  });
1213
1216
  const TOOL_PERMISSIONS = MCP_TOOL_PERMISSIONS;
1214
1217
  const RESOURCE_PERMISSIONS = {
@@ -1311,9 +1314,10 @@ export function createTaskforceMcpServer(options = {}) {
1311
1314
  function buildBoundedTextContent(content, maxChars) {
1312
1315
  const requestedMaxChars = Number(maxChars);
1313
1316
  const hasRequestedMaxChars = maxChars !== undefined && maxChars !== null && maxChars !== '';
1314
- const resolvedMaxChars = hasRequestedMaxChars && Number.isFinite(requestedMaxChars)
1315
- ? Math.max(1, Math.floor(requestedMaxChars))
1316
- : 200000;
1317
+ if (hasRequestedMaxChars && (!Number.isInteger(requestedMaxChars) || requestedMaxChars < 1)) {
1318
+ throw new McpPublicError('maxChars must be a positive integer');
1319
+ }
1320
+ const resolvedMaxChars = hasRequestedMaxChars ? requestedMaxChars : 200000;
1317
1321
  const characters = Array.from(content);
1318
1322
  const totalChars = characters.length;
1319
1323
  const boundedContent = totalChars > resolvedMaxChars
@@ -1405,8 +1409,10 @@ export function createTaskforceMcpServer(options = {}) {
1405
1409
  }
1406
1410
  const requestedLimit = Number(options?.limit);
1407
1411
  const limit = Number.isFinite(requestedLimit)
1408
- ? Math.max(1, Math.min(50, Math.trunc(requestedLimit)))
1412
+ ? Math.max(1, Math.min(51, Math.trunc(requestedLimit)))
1409
1413
  : 10;
1414
+ const requestedOffset = Number(options?.offset);
1415
+ const offset = Number.isFinite(requestedOffset) ? Math.max(0, Math.trunc(requestedOffset)) : 0;
1410
1416
  const normalize = (value) => typeof value === 'string' ? value.trim().toLowerCase() : '';
1411
1417
  const tokenize = (value) => normalize(value).split(/[^a-z0-9]+/i).filter(Boolean);
1412
1418
  const allImages = workspaceAssetStore.listAllByWorkspace(ACTIVE_WORKSPACE_ID, { kind: 'image' });
@@ -1450,8 +1456,10 @@ export function createTaskforceMcpServer(options = {}) {
1450
1456
  return [{ asset, matchedOn: Array.from(matchedOn), score }];
1451
1457
  });
1452
1458
  return scoredMatches
1453
- .sort((a, b) => b.score - a.score || String(b.asset.updatedAt || '').localeCompare(String(a.asset.updatedAt || '')))
1454
- .slice(0, limit);
1459
+ .sort((a, b) => b.score - a.score
1460
+ || String(b.asset.updatedAt || '').localeCompare(String(a.asset.updatedAt || ''))
1461
+ || a.asset.assetId.localeCompare(b.asset.assetId))
1462
+ .slice(offset, offset + limit);
1455
1463
  }
1456
1464
  const searchDocumentAssets = documentHelpers.searchDocumentAssets;
1457
1465
  function buildImageDescriptor(asset) {
@@ -1465,7 +1473,9 @@ export function createTaskforceMcpServer(options = {}) {
1465
1473
  sizeBytes: typeof asset.sizeBytes === 'number' ? asset.sizeBytes : null,
1466
1474
  ...(runtimeMode === 'local' ? { storageKey: asset.storageKey ?? null } : {}),
1467
1475
  updatedAt: asset.updatedAt ?? null,
1468
- apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}`,
1476
+ ...(runtimeMode === 'local'
1477
+ ? { apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}` }
1478
+ : {}),
1469
1479
  };
1470
1480
  return withAttachmentUrl({ ...descriptor, kind: 'image' });
1471
1481
  }
@@ -1518,6 +1528,7 @@ export function createTaskforceMcpServer(options = {}) {
1518
1528
  || `Image ${normalizedValue} not found in workspace ${ACTIVE_WORKSPACE_ID}. If you only know a keyword, filename, or rough title, call search_images first.`, 'MCP_NOT_FOUND', 404);
1519
1529
  }
1520
1530
  const auditActorType = options.auth?.actorType || (runtimeMode === 'cloud' ? 'user' : 'local');
1531
+ const requiresExplicitTokenAuthorship = runtimeMode === 'cloud' && auditActorType === 'token';
1521
1532
  const auditActorUserId = String(options.auth?.userId || (runtimeMode === 'cloud' ? 'anonymous' : 'local-mcp')).trim() || 'anonymous';
1522
1533
  const auditTokenId = options.auth?.tokenId ? String(options.auth.tokenId).trim() : null;
1523
1534
  const configuredAiProfileId = options.auth?.aiProfileId ? String(options.auth.aiProfileId).trim() || null : null;
@@ -1591,7 +1602,7 @@ export function createTaskforceMcpServer(options = {}) {
1591
1602
  const shouldBindOAuthGrant = auditActorType === 'oauth'
1592
1603
  && Boolean(auditTokenId)
1593
1604
  && configuredAiProfileId !== profileId;
1594
- const shouldApplyClientContext = Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
1605
+ const shouldApplyClientContext = auditActorType !== 'token' && Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
1595
1606
  || configuredMcpConnectionId);
1596
1607
  if (!shouldBindOAuthGrant && !shouldApplyClientContext)
1597
1608
  return;
@@ -1755,6 +1766,10 @@ export function createTaskforceMcpServer(options = {}) {
1755
1766
  return resolved;
1756
1767
  }
1757
1768
  function findTrustedBoundProfile(options) {
1769
+ // A generic access token proves workspace/tool authorization, not which AI
1770
+ // is acting. Never use its historical connection row as authorship proof.
1771
+ if (requiresExplicitTokenAuthorship)
1772
+ return null;
1758
1773
  const grantProfile = findProfileForConfiguredGrant();
1759
1774
  const connectionProfile = findProfileForConfiguredConnection(options);
1760
1775
  if (grantProfile && connectionProfile && grantProfile.id !== connectionProfile.id) {
@@ -1771,6 +1786,19 @@ export function createTaskforceMcpServer(options = {}) {
1771
1786
  }
1772
1787
  return profile;
1773
1788
  }
1789
+ function assertOAuthProfileClientCompatibility(profile) {
1790
+ if (auditActorType !== 'oauth' || !configuredMcpClientId)
1791
+ return;
1792
+ const connection = profile.providerMetadata?.connection;
1793
+ const storedClientId = connection && typeof connection === 'object' && !Array.isArray(connection)
1794
+ ? parseMcpClientId(connection.clientId)
1795
+ : null;
1796
+ const inferredClientId = inferLegacyMcpClientIdFromProfile(profile);
1797
+ const profileClientId = storedClientId || inferredClientId;
1798
+ if (!profileClientId || profileClientId === configuredMcpClientId)
1799
+ return;
1800
+ throw new TaskforceRuleError('The supplied AI profile is not compatible with this OAuth client product.', 409, 'AI_PROFILE_SELECTION_INVALID', { profileId: profile.id, profileClientId, configuredClientId: configuredMcpClientId });
1801
+ }
1774
1802
  function listSelectableOAuthProfiles(profileName) {
1775
1803
  if (auditActorType !== 'oauth')
1776
1804
  return [];
@@ -2383,6 +2411,8 @@ export function createTaskforceMcpServer(options = {}) {
2383
2411
  function ensureResolvedProfileFromConnection(defaults) {
2384
2412
  if (activeAiProfileId)
2385
2413
  return;
2414
+ if (requiresExplicitTokenAuthorship)
2415
+ return;
2386
2416
  const boundProfile = findTrustedBoundProfile({ repairConnection: false });
2387
2417
  if (boundProfile) {
2388
2418
  if (configuredAiProfileIdentityAsserted) {
@@ -2457,6 +2487,25 @@ export function createTaskforceMcpServer(options = {}) {
2457
2487
  throw new TaskforceRuleError('Writes are blocked because this MCP session requested an identity that conflicts with its OAuth-bound profile. Reconnect with the intended profile and retry resolve_profile.', 409, 'AI_PROFILE_BINDING_CONFLICT', { reason: 'session_identity_quarantined', nextTool: 'resolve_profile' });
2458
2488
  }
2459
2489
  const profileToken = resolveProfileTokenFromArgs(args);
2490
+ if (requiresExplicitTokenAuthorship) {
2491
+ if (!profileToken) {
2492
+ throw new McpPublicError('Cloud access-token clients must pass profileToken on every mutating call. Call resolve_profile to obtain it, keep it private, and retry this write.', 'AI_PROFILE_PROOF_REQUIRED', 401);
2493
+ }
2494
+ const tokenProfile = core.getAiProfileByToken({
2495
+ workspaceId: ACTIVE_WORKSPACE_ID,
2496
+ profileToken,
2497
+ seatScope: configuredAiProfileSeatScope,
2498
+ });
2499
+ if (!tokenProfile) {
2500
+ throw new McpPublicError('Invalid AI profile token. Call resolve_profile again and retry.', 'INVALID_AI_PROFILE_TOKEN', 401);
2501
+ }
2502
+ if (activeAiProfileId && activeAiProfileId !== tokenProfile.id) {
2503
+ failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already active for this request.', { reason: 'request_token_mismatch', boundProfileId: activeAiProfileId, suppliedProfileId: tokenProfile.id });
2504
+ }
2505
+ if (!activeAiProfileId)
2506
+ activateAiProfile(tokenProfile.id, 'token_lookup');
2507
+ return;
2508
+ }
2460
2509
  if (activeAiProfileId) {
2461
2510
  if (!profileToken)
2462
2511
  return;
@@ -2491,6 +2540,8 @@ export function createTaskforceMcpServer(options = {}) {
2491
2540
  suppliedProfileId: profile.id,
2492
2541
  });
2493
2542
  }
2543
+ if (!trustedBoundProfile)
2544
+ assertOAuthProfileClientCompatibility(profile);
2494
2545
  if (trustedBoundProfile && configuredAiProfileIdentityAsserted) {
2495
2546
  assertRequestedIdentityMatchesBoundProfile(trustedBoundProfile, {
2496
2547
  name: configuredAiProfileName,
@@ -2525,7 +2576,7 @@ export function createTaskforceMcpServer(options = {}) {
2525
2576
  throw new McpPublicError('This tool requires an AI profile. Call resolve_profile first, then retry. If your MCP client is stateless, pass profileToken on mutating calls.', 'AI_PROFILE_REQUIRED');
2526
2577
  }
2527
2578
  function assertAnnotatedAttachmentReadAccess() {
2528
- const gate = resolveAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
2579
+ const gate = resolveNodeAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
2529
2580
  if (!gate.allowed) {
2530
2581
  throw new TaskforceRuleError('Annotated attachments are unavailable for this workspace.', 403, gate.code || 'ANNOTATED_ATTACHMENTS_UNAVAILABLE');
2531
2582
  }
@@ -3340,6 +3391,17 @@ export function createTaskforceMcpServer(options = {}) {
3340
3391
  offset: Number(normalizedOffset),
3341
3392
  };
3342
3393
  }
3394
+ function normalizeSearchReadPage(limit, offset, defaultLimit) {
3395
+ const normalizedLimit = limit === undefined ? defaultLimit : limit;
3396
+ const normalizedOffset = offset === undefined ? 0 : offset;
3397
+ if (!Number.isInteger(normalizedLimit) || Number(normalizedLimit) < 1 || Number(normalizedLimit) > 50) {
3398
+ throw new McpPublicError('limit must be an integer from 1 to 50');
3399
+ }
3400
+ if (!Number.isInteger(normalizedOffset) || Number(normalizedOffset) < 0) {
3401
+ throw new McpPublicError('offset must be a non-negative integer');
3402
+ }
3403
+ return { limit: Number(normalizedLimit), offset: Number(normalizedOffset) };
3404
+ }
3343
3405
  function buildCompactPlanningHistory(targetType, entity) {
3344
3406
  const duplicatedCommentAction = `${targetType}-comment-added`;
3345
3407
  const activity = Array.isArray(entity?.activity) ? entity.activity : [];
@@ -3516,17 +3578,26 @@ export function createTaskforceMcpServer(options = {}) {
3516
3578
  workstreamsByInitiativeId.set(normalizedInitiativeId, current);
3517
3579
  }
3518
3580
  const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
3519
- const initiatives = core.listInitiatives(ACTIVE_WORKSPACE_ID)
3520
- .filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []))
3521
- .map((initiative) => buildInitiativeResourcePayload(initiative, {
3522
- workstreamsByInitiativeId,
3523
- taskCountsByWorkstreamId,
3524
- detail: 'compact',
3525
- }));
3581
+ const matches = core.listInitiatives(ACTIVE_WORKSPACE_ID)
3582
+ .filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []));
3583
+ const offset = options?.offset ?? 0;
3584
+ const limit = options?.limit ?? 25;
3585
+ const initiatives = matches.slice(offset, offset + limit).map((initiative) => {
3586
+ const compact = buildInitiativeResourcePayload(initiative, {
3587
+ workstreamsByInitiativeId,
3588
+ taskCountsByWorkstreamId,
3589
+ detail: 'compact',
3590
+ });
3591
+ const { nextDetailOption: _nextDetailOption, ...summary } = compact;
3592
+ return summary;
3593
+ });
3594
+ const pagination = buildPlanningPagination(matches.length, offset, limit, initiatives.length);
3526
3595
  return {
3527
- total: initiatives.length,
3596
+ total: matches.length,
3528
3597
  ...(query ? { query } : {}),
3529
3598
  initiatives,
3599
+ pagination,
3600
+ guidance: 'Use get_initiative with the initiative ID or reference for details.',
3530
3601
  };
3531
3602
  }
3532
3603
  function buildWorkstreamsResourcePayload(options) {
@@ -3536,13 +3607,22 @@ export function createTaskforceMcpServer(options = {}) {
3536
3607
  initiativesById.set(initiative.id, initiative);
3537
3608
  }
3538
3609
  const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
3539
- const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
3540
- .filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')))
3541
- .map((workstream) => buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' }));
3610
+ const matches = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
3611
+ .filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')));
3612
+ const offset = options?.offset ?? 0;
3613
+ const limit = options?.limit ?? 25;
3614
+ const workstreams = matches.slice(offset, offset + limit).map((workstream) => {
3615
+ const compact = buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' });
3616
+ const { nextDetailOption: _nextDetailOption, nextTaskTool: _nextTaskTool, suggestedTaskArgs: _suggestedTaskArgs, ...summary } = compact;
3617
+ return summary;
3618
+ });
3619
+ const pagination = buildPlanningPagination(matches.length, offset, limit, workstreams.length);
3542
3620
  return {
3543
- total: workstreams.length,
3621
+ total: matches.length,
3544
3622
  ...(query ? { query } : {}),
3545
3623
  workstreams,
3624
+ pagination,
3625
+ guidance: 'Use get_workstream for details or list_tasks with workstreamId for active tasks.',
3546
3626
  };
3547
3627
  }
3548
3628
  function buildPlanningWriteEntitySummary(targetType, entity) {
@@ -3843,7 +3923,9 @@ export function createTaskforceMcpServer(options = {}) {
3843
3923
  registerAdminWorkspaceTools(registerTool, (name, args) => executeRegisteredTool(name, args));
3844
3924
  const mutatingProfileTokenProperty = {
3845
3925
  type: 'string',
3846
- description: 'Optional profileToken from resolve_profile for stateless MCP clients.'
3926
+ description: requiresExplicitTokenAuthorship
3927
+ ? 'Required authorship proof for cloud access-token clients; obtain from resolve_profile.'
3928
+ : 'Optional profileToken from resolve_profile for stateless MCP clients.'
3847
3929
  };
3848
3930
  const nonDestructiveWriteTools = new Set([
3849
3931
  'add_annotated_attachment_marker',
@@ -3876,6 +3958,9 @@ export function createTaskforceMcpServer(options = {}) {
3876
3958
  get priorityDesc() {
3877
3959
  return `Priority level: ${getActivePriorities().map((priority) => `${priority.value} (${priority.label})`).join(', ')}`;
3878
3960
  },
3961
+ get priorityEnum() {
3962
+ return getActivePriorities().map((priority) => priority.value);
3963
+ },
3879
3964
  get typeEnum() {
3880
3965
  return getActiveTypes().map((type) => type.value);
3881
3966
  },
@@ -3916,6 +4001,11 @@ export function createTaskforceMcpServer(options = {}) {
3916
4001
  inputSchema: {
3917
4002
  ...descriptor.inputSchema,
3918
4003
  properties,
4004
+ ...(requiresExplicitTokenAuthorship
4005
+ && descriptor.name !== 'resolve_profile'
4006
+ && isMcpWriteToolName(descriptor.name)
4007
+ ? { required: Array.from(new Set([...(descriptor.inputSchema.required || []), 'profileToken'])) }
4008
+ : {}),
3919
4009
  additionalProperties: false,
3920
4010
  },
3921
4011
  annotations: {
@@ -4992,15 +5082,15 @@ export function createTaskforceMcpServer(options = {}) {
4992
5082
  : resolveWorkstreamByIdentifierOrThrow(workstreamId).id;
4993
5083
  const paginated = resolvedWorkstreamId !== undefined || offset !== undefined;
4994
5084
  const parsedOffset = offset === undefined ? 0 : Number(offset);
4995
- if (!Number.isFinite(parsedOffset) || parsedOffset < 0)
4996
- throw new McpPublicError('offset must be a non-negative number');
4997
- const normalizedOffset = Math.floor(parsedOffset);
5085
+ if (!Number.isInteger(parsedOffset) || parsedOffset < 0)
5086
+ throw new McpPublicError('offset must be a non-negative integer');
5087
+ const normalizedOffset = parsedOffset;
4998
5088
  const parsedLimit = limit === undefined ? (paginated ? 50 : 0) : Number(limit);
4999
- if (!Number.isFinite(parsedLimit) || parsedLimit < 0)
5000
- throw new McpPublicError('limit must be a non-negative number');
5089
+ if (!Number.isInteger(parsedLimit) || parsedLimit < 0 || parsedLimit > 200)
5090
+ throw new McpPublicError('limit must be an integer from 1 to 200');
5001
5091
  const normalizedLimit = paginated
5002
- ? Math.min(200, Math.max(1, Math.floor(parsedLimit || 50)))
5003
- : Math.floor(parsedLimit);
5092
+ ? (parsedLimit || 50)
5093
+ : parsedLimit;
5004
5094
  const queriedTasks = core.listTasksSlim(ACTIVE_WORKSPACE_ID, {
5005
5095
  category,
5006
5096
  status,
@@ -5087,6 +5177,15 @@ export function createTaskforceMcpServer(options = {}) {
5087
5177
  failAiProfileBindingConflict('The selected AI profile conflicts with the profile already bound to this MCP session or authenticated connection.', { reason: 'selected_profile_mismatch', boundProfileId: alreadyBoundProfile.id, selectedProfileId: explicitlySelectedProfile.id });
5088
5178
  }
5089
5179
  const boundProfile = alreadyBoundProfile || explicitlySelectedProfile;
5180
+ if (!boundProfile && suppliedProfileToken) {
5181
+ const suppliedProfile = core.getAiProfileByToken({
5182
+ workspaceId: ACTIVE_WORKSPACE_ID,
5183
+ profileToken: suppliedProfileToken,
5184
+ seatScope: configuredAiProfileSeatScope,
5185
+ });
5186
+ if (suppliedProfile)
5187
+ assertOAuthProfileClientCompatibility(suppliedProfile);
5188
+ }
5090
5189
  if (boundProfile && suppliedProfileToken) {
5091
5190
  const tokenProfile = core.getAiProfileByToken({
5092
5191
  workspaceId: ACTIVE_WORKSPACE_ID,
@@ -5291,10 +5390,19 @@ export function createTaskforceMcpServer(options = {}) {
5291
5390
  };
5292
5391
  }
5293
5392
  case "search_tasks": {
5294
- const { query, scope = "all" } = args;
5295
- const results = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID);
5393
+ const { query, scope = "all", limit, offset } = args;
5394
+ const page = normalizeSearchReadPage(limit, offset, 25);
5395
+ const queried = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID, {
5396
+ limit: page.limit + 1,
5397
+ offset: page.offset,
5398
+ });
5399
+ const hasMore = queried.length > page.limit;
5400
+ const results = hasMore ? queried.slice(0, page.limit) : queried;
5296
5401
  return {
5297
- content: [{ type: "text", text: renderTaskTable(results) }],
5402
+ content: [{
5403
+ type: "text",
5404
+ text: `${renderTaskTable(results)}\n\nPage: offset ${page.offset}, limit ${page.limit}, returned ${results.length}, hasMore ${hasMore}${hasMore ? `, nextOffset ${page.offset + results.length}` : ''}`,
5405
+ }],
5298
5406
  };
5299
5407
  }
5300
5408
  case "get_task": {
@@ -5498,9 +5606,10 @@ export function createTaskforceMcpServer(options = {}) {
5498
5606
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5499
5607
  }
5500
5608
  case "list_initiatives": {
5501
- const { query } = args;
5609
+ const { query, limit, offset } = args;
5610
+ const page = normalizePlanningReadPage(limit, offset);
5502
5611
  return {
5503
- content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query }), null, 2) }],
5612
+ content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query, ...page }), null, 2) }],
5504
5613
  };
5505
5614
  }
5506
5615
  case "get_initiative": {
@@ -5771,9 +5880,10 @@ export function createTaskforceMcpServer(options = {}) {
5771
5880
  };
5772
5881
  }
5773
5882
  case "list_workstreams": {
5774
- const { query } = args;
5883
+ const { query, limit, offset } = args;
5884
+ const page = normalizePlanningReadPage(limit, offset);
5775
5885
  return {
5776
- content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query }), null, 2) }],
5886
+ content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query, ...page }), null, 2) }],
5777
5887
  };
5778
5888
  }
5779
5889
  case "get_workstream": {
@@ -6190,8 +6300,11 @@ export function createTaskforceMcpServer(options = {}) {
6190
6300
  }
6191
6301
  case "search_images": {
6192
6302
  assertAnnotatedAttachmentReadAccess();
6193
- const { query, limit } = args;
6194
- const results = searchImageAssets(query, { limit }).map(({ asset, matchedOn, score }) => ({
6303
+ const { query, limit, offset } = args;
6304
+ const page = normalizeSearchReadPage(limit, offset, 10);
6305
+ const queried = searchImageAssets(query, { limit: page.limit + 1, offset: page.offset });
6306
+ const hasMore = queried.length > page.limit;
6307
+ const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
6195
6308
  image: buildImageDescriptor(asset),
6196
6309
  matchedOn,
6197
6310
  score,
@@ -6207,6 +6320,13 @@ export function createTaskforceMcpServer(options = {}) {
6207
6320
  workspaceId: ACTIVE_WORKSPACE_ID,
6208
6321
  query: typeof query === 'string' ? query.trim() : '',
6209
6322
  results,
6323
+ pagination: {
6324
+ offset: page.offset,
6325
+ limit: page.limit,
6326
+ returned: results.length,
6327
+ hasMore,
6328
+ ...(hasMore ? { nextOffset: page.offset + results.length } : {}),
6329
+ },
6210
6330
  guidance: results.length > 0
6211
6331
  ? 'After picking a match, call get_image with the suggestedArgs.imageId to inspect linked tasks and image notes.'
6212
6332
  : 'No matching canonical images were found. Try an I-123 reference, title keyword, or filename fragment.',
@@ -6277,8 +6397,11 @@ export function createTaskforceMcpServer(options = {}) {
6277
6397
  };
6278
6398
  }
6279
6399
  case "search_documents": {
6280
- const { query, limit } = args;
6281
- const results = searchDocumentAssets(query, { limit }).map(({ asset, matchedOn, score }) => ({
6400
+ const { query, limit, offset } = args;
6401
+ const page = normalizeSearchReadPage(limit, offset, 10);
6402
+ const queried = searchDocumentAssets(query, { limit: page.limit + 1, offset: page.offset });
6403
+ const hasMore = queried.length > page.limit;
6404
+ const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
6282
6405
  document: buildDocumentDescriptor(asset),
6283
6406
  matchedOn,
6284
6407
  score,
@@ -6295,6 +6418,13 @@ export function createTaskforceMcpServer(options = {}) {
6295
6418
  workspaceId: ACTIVE_WORKSPACE_ID,
6296
6419
  query: typeof query === 'string' ? query.trim() : '',
6297
6420
  results,
6421
+ pagination: {
6422
+ offset: page.offset,
6423
+ limit: page.limit,
6424
+ returned: results.length,
6425
+ hasMore,
6426
+ ...(hasMore ? { nextOffset: page.offset + results.length } : {}),
6427
+ },
6298
6428
  guidance: results.length > 0
6299
6429
  ? 'After picking a match, call get_document with the suggestedArgs.documentId. Pass includeContent=true when you need the body.'
6300
6430
  : 'No matching canonical documents were found. Try a D-123 reference, document id, title keyword, or filename fragment.',