@taskforcehq/taskforce 0.3.301 → 0.3.303

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 (171) hide show
  1. package/dist/Taskforce.module.css +37 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +72 -33
  4. package/dist/TaskforceCore.test.js +848 -130
  5. package/dist/components/features/TaskSettings.js +14 -82
  6. package/dist/components/features/TaskSettings.test.js +33 -70
  7. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  8. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  9. package/dist/components/task/TaskActionHeader.js +6 -3
  10. package/dist/components/task/TaskCard.js +6 -3
  11. package/dist/components/task/TaskForm.js +2 -2
  12. package/dist/components/task/TaskForm.test.js +41 -2
  13. package/dist/components/task/TaskKanban.test.js +17 -0
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.d.ts +3 -1
  16. package/dist/components/views/PlansPage.js +109 -49
  17. package/dist/components/views/PlansPage.test.d.ts +1 -0
  18. package/dist/components/views/PlansPage.test.js +100 -0
  19. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  20. package/dist/components/views/StandaloneLayout.js +473 -256
  21. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  22. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
  23. package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
  24. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  25. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  26. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  27. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  28. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  29. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  30. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  31. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  32. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  33. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  34. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  35. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  36. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  37. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  38. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  39. package/dist/components/views/standalone/modals/types.js +1 -0
  40. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  41. package/dist/components/views/teamManagementAccess.js +2 -2
  42. package/dist/components/views/teamManagementAccess.test.js +3 -3
  43. package/dist/config/deployment.js +2 -2
  44. package/dist/config/deployment.test.js +5 -3
  45. package/dist/core/AiProfileService.js +7 -2
  46. package/dist/core/AiProfiles.test.js +19 -0
  47. package/dist/core/EntitlementsPolicy.test.js +2 -2
  48. package/dist/core/GlobalSettingsService.js +78 -18
  49. package/dist/core/PlanEntitlementService.d.ts +29 -5
  50. package/dist/core/PlanEntitlementService.js +297 -219
  51. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  52. package/dist/core/SyncReconciliationService.d.ts +2 -0
  53. package/dist/core/SyncReconciliationService.js +3 -0
  54. package/dist/core/SystemAdmin.test.js +186 -96
  55. package/dist/core/TaskAuditTimeline.test.js +40 -0
  56. package/dist/core/Taskforce.d.ts +100 -10
  57. package/dist/core/Taskforce.ids.test.js +133 -0
  58. package/dist/core/Taskforce.js +459 -127
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  61. package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
  62. package/dist/core/taskReferencePolicy.d.ts +28 -0
  63. package/dist/core/taskReferencePolicy.js +189 -0
  64. package/dist/core/types.d.ts +13 -12
  65. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  66. package/dist/hooks/useSyncOrchestrator.js +17 -3
  67. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  68. package/dist/hooks/useTaskData.js +3 -2
  69. package/dist/hooks/useTaskData.test.js +66 -0
  70. package/dist/hooks/useTaskforce.d.ts +8 -0
  71. package/dist/hooks/useTaskforce.js +72 -19
  72. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  73. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  74. package/dist/mcp/aiProfileBootstrap.js +80 -0
  75. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  76. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  77. package/dist/mcp/clientIntegrations.d.ts +2 -8
  78. package/dist/mcp/clientIntegrations.js +21 -40
  79. package/dist/mcp/clientIntegrations.test.js +28 -2
  80. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  81. package/dist/mcp/clientProfileDefaults.js +37 -0
  82. package/dist/mcp/runtime.d.ts +10 -7
  83. package/dist/mcp/runtime.js +131 -48
  84. package/dist/mcp/runtime.test.js +321 -83
  85. package/dist/migrations/billingSchemaParity.test.js +3 -0
  86. package/dist/migrations/taskSchemaMigrations.js +68 -1
  87. package/dist/qaOpen.test.d.ts +1 -0
  88. package/dist/qaOpen.test.js +56 -0
  89. package/dist/server/index.cookieProxy.test.js +2 -0
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +17 -1
  92. package/dist/server/index.test.js +15 -1
  93. package/dist/server/mockStripe.d.ts +1 -0
  94. package/dist/server/mockStripe.js +2 -0
  95. package/dist/server/routes/admin.js +105 -45
  96. package/dist/server/routes/auth.d.ts +6 -0
  97. package/dist/server/routes/auth.js +505 -30
  98. package/dist/server/routes/billing.js +517 -24
  99. package/dist/server/routes/billing.test.js +967 -45
  100. package/dist/server/routes/documents.js +6 -1
  101. package/dist/server/routes/shared.d.ts +1 -1
  102. package/dist/server/routes/shared.js +16 -6
  103. package/dist/server/routes/sync.integration.test.js +290 -0
  104. package/dist/server/routes/sync.js +234 -1922
  105. package/dist/server/routes/tasks.js +18 -4
  106. package/dist/server/routes.js +29 -22
  107. package/dist/server/routes.test.js +639 -82
  108. package/dist/shared/runtimeContract.d.ts +1 -1
  109. package/dist/sync/cloudSyncApi.d.ts +1 -0
  110. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  111. package/dist/sync/collaborationSyncPayload.js +131 -0
  112. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  113. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  114. package/dist/sync/collaborationSyncState.d.ts +15 -0
  115. package/dist/sync/collaborationSyncState.js +60 -0
  116. package/dist/sync/contentSyncPayload.d.ts +45 -0
  117. package/dist/sync/contentSyncPayload.js +104 -0
  118. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  119. package/dist/sync/contentSyncPayload.test.js +104 -0
  120. package/dist/sync/contentSyncState.d.ts +98 -0
  121. package/dist/sync/contentSyncState.js +1004 -0
  122. package/dist/sync/planningSyncPayload.d.ts +9 -0
  123. package/dist/sync/planningSyncPayload.js +82 -0
  124. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  125. package/dist/sync/planningSyncPayload.test.js +68 -0
  126. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  127. package/dist/sync/syncApplyHandlers.js +445 -0
  128. package/dist/sync/syncService.d.ts +1 -0
  129. package/dist/sync/syncService.js +21 -8
  130. package/dist/sync/taskSyncPayload.d.ts +33 -0
  131. package/dist/sync/taskSyncPayload.js +126 -0
  132. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  133. package/dist/sync/taskSyncPayload.test.js +54 -0
  134. package/dist/sync/workspacePullFeed.d.ts +38 -0
  135. package/dist/sync/workspacePullFeed.js +349 -0
  136. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  137. package/dist/sync/workspacePullFeed.test.js +278 -0
  138. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  139. package/dist/sync/workspaceSyncModel.js +9 -2
  140. package/dist/sync/workspaceSyncModel.test.js +29 -0
  141. package/dist/test/setup.js +1 -1
  142. package/dist/types.d.ts +1 -0
  143. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
  144. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  145. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
  146. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  147. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  148. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
  149. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  150. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  151. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  152. package/dist/ui/assets/index-DneETxcu.css +1 -0
  153. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
  154. package/dist/ui/index.html +3 -3
  155. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  156. package/dist/utils/accountProfileSummaryCache.js +97 -0
  157. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  158. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  159. package/dist/utils/taskActivity.js +17 -4
  160. package/dist/utils/taskActivity.test.js +27 -0
  161. package/dist/utils/taskNormalization.js +10 -2
  162. package/dist/utils/taskReferences.d.ts +11 -1
  163. package/dist/utils/taskReferences.js +33 -1
  164. package/package.json +4 -3
  165. package/scripts/qa-open-lib.mjs +175 -0
  166. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  167. package/scripts/qa-open.mjs +352 -0
  168. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  169. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  170. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  171. package/dist/ui/assets/index-xBWLPOEb.js +0 -6
@@ -6,6 +6,7 @@ import * as path from 'path';
6
6
  import { createHash, randomUUID } from 'node:crypto';
7
7
  import { TaskforceCore, TaskforceRuleError } from '../core/Taskforce.js'; // Note .js extension for ESM
8
8
  import { COMPLEXITY_OPTIONS, resolveNumberOption, resolveStringOption, STATUS_OPTIONS } from './optionResolution.js';
9
+ import { buildResolveAiProfileInputFromBootstrap } from './aiProfileBootstrap.js';
9
10
  import { validateStructuredDocForAttach } from './structuredDocValidation.js';
10
11
  import { validateStructuredCommentForAdd } from './structuredCommentValidation.js';
11
12
  import { resolveDatabaseConfigFromEnv } from '../storage/providerConfig.js';
@@ -16,9 +17,9 @@ import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } fr
16
17
  import { getDocumentReferenceLabel, parseDocumentReference } from '../utils/documentReferences.js';
17
18
  import { getImageReferenceLabel, parseImageReference } from '../utils/imageReferences.js';
18
19
  import { inferCanonicalAssetKind, isValidCanonicalDocumentAsset } from '../utils/canonicalAssetKind.js';
20
+ import { getTaskReferenceLabel, shouldUseProvisionalTaskReferences } from '../utils/taskReferences.js';
19
21
  import { AI_PROFILE_SURFACE_TYPES, parseAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
20
22
  import { formatWorkstreamReference, resolveInitiativeReference, parseWorkstreamReference, } from '../utils/planningReferences.js';
21
- import { getTaskReferenceLabel } from '../utils/taskReferences.js';
22
23
  import { MCP_TOOL_PERMISSIONS, MCP_WRITE_TOOL_NAMES } from './toolRegistry.js';
23
24
  export function isMcpWriteToolName(name) {
24
25
  return MCP_WRITE_TOOL_NAMES.has(String(name || '').trim());
@@ -71,7 +72,7 @@ function resolveRecoverableToolError(error) {
71
72
  code: 'AI_PROFILE_REQUIRED',
72
73
  nextTool: 'resolve_profile',
73
74
  suggestedArgs: {},
74
- retryHint: 'Call resolve_profile, store the returned profileToken, and pass profileToken on later mutating calls if the client is stateless.'
75
+ retryHint: 'Call get_current_profile to inspect binding state first, or call resolve_profile to bind an AI identity. Only pass profileToken on later mutating calls if your client is stateless.'
75
76
  };
76
77
  }
77
78
  if (/^Invalid AI profile token\./i.test(message)) {
@@ -80,7 +81,7 @@ function resolveRecoverableToolError(error) {
80
81
  code: 'INVALID_AI_PROFILE_TOKEN',
81
82
  nextTool: 'resolve_profile',
82
83
  suggestedArgs: {},
83
- retryHint: 'Call resolve_profile again to obtain a fresh profileToken, then retry the original mutating tool call.'
84
+ retryHint: 'Call get_current_profile to confirm whether ambient identity is already bound. If not, call resolve_profile again to obtain a fresh profileToken, then retry the original mutating tool call.'
84
85
  };
85
86
  }
86
87
  if (error instanceof TaskforceRuleError) {
@@ -586,7 +587,7 @@ export function createTaskforceMcpServer(options = {}) {
586
587
  },
587
588
  tools: {},
588
589
  },
589
- instructions: `You are an AI collaborator connected to Taskforce, a shared task management system for AI-developer pairs.\n\nIMPORTANT: At the start of any session, call resolve_profile before attempting any mutating Taskforce tools. Pass your name and a profileToken if you received one in a previous session. Store the returned profileToken for reuse across sessions. Use the returned profile id as the assignee or author on tasks and comments you create.\n\nStateless client pattern: call resolve_profile, save the returned profileToken, and include profileToken on later mutating tool calls if your MCP client does not preserve session identity between calls.\n\nAutomation-friendly connection pattern: if your MCP connection is configured with x-taskforce-ai-name and optionally x-taskforce-ai-profile-token headers, Taskforce can auto-bind that AI identity for get_current_profile and later writes without requiring profileToken on every tool call.\n\nWhen setting resolve_profile.surfaceType, classify yourself by the kind of identity you are registering: use agent for a persistent named AI collaborator in Taskforce, coding_tool for the coding surface or terminal tool itself, chat_app for a conversational app identity, ide for an IDE-native copilot identity, and other only when none of those fit. If you are unsure and you are registering a named collaborator such as a teammate-style AI persona, choose agent.\n\nTo get tasks assigned to yourself, call get_current_profile, then use the returned suggestedArgs with list_tasks. When you begin active work on a tracked task, prefer start_task. It sets the task to in-progress and claims it for your AI profile when the task is currently unassigned. When work on a tracked task is complete, prefer finish_task for the final handoff: use outcome='review' when the next step is human validation or approval, and use outcome='done' only when no explicit human action is requested. Do not leave a task in-progress after reporting completed implementation unless the user explicitly wants more work to continue in the same task.\n\nTaxonomy rule: always call get_config before setting category, type, priority, approach, status, or complexity. Do not rely on remembered enum values.\n\nUse identifier prefixes to choose the correct retrieval path: T-123 goes to task tools such as get_task, IN-123 goes to initiative tools such as get_initiative, WS-123 goes to workstream tools such as get_workstream, I-123 goes to image tools such as get_image, and D-123 goes to document tools such as get_document. Do not send I-123 or D-123 identifiers to get_task.\n\nFor annotated images, use search_images first when you know only a keyword, filename, or rough title. Once you have the exact identifier, call get_image, then use get_annotated_attachment_sessions to list saved sessions, then use read_annotated_attachment_payload for the structured annotation payload.\n\nFor documents, use search_documents first when you know only a keyword, filename, or rough title. Once you have the exact identifier, call get_document, then read_document_content to inspect the document body. For a lower-friction review flow, prefer review_document; otherwise use get_document_review_sessions or create_document_review_session, then add_document_review_comment to record review feedback.`,
590
+ instructions: `You are an AI collaborator connected to Taskforce, a shared task management system for AI-developer pairs.\n\nIMPORTANT: Before mutating Taskforce data, ensure Taskforce knows which AI profile you are acting as. The preferred path is ambient session binding: if your MCP connection is configured with x-taskforce-ai-name and optionally x-taskforce-ai-profile-token headers, Taskforce can auto-bind that AI identity for get_current_profile and later writes. If no AI identity is currently bound, call resolve_profile with your name. Reuse the returned profileToken across sessions, especially for stateless clients.\n\nStateless client pattern: call resolve_profile, save the returned profileToken, and include profileToken on later mutating tool calls only when your MCP client does not preserve session identity between calls.\n\nTo inspect your current attribution state before writes, call get_current_profile. It reports whether identity is bound, how it was resolved, and the suggested args to use with list_tasks.\n\nWhen setting resolve_profile.surfaceType, classify yourself by the kind of identity you are registering: use agent for a persistent named AI collaborator in Taskforce, coding_tool for the coding surface or terminal tool itself, chat_app for a conversational app identity, ide for an IDE-native copilot identity, and other only when none of those fit. If you are unsure and you are registering a named collaborator such as a teammate-style AI persona, choose agent.\n\nTo get tasks assigned to yourself, call get_current_profile, then use the returned suggestedArgs with list_tasks. When you begin active work on a tracked task, prefer start_task. It sets the task to in-progress and claims it for your AI profile when the task is currently unassigned. When work on a tracked task is complete, prefer finish_task for the final handoff: use outcome='review' when the next step is human validation or approval, and use outcome='done' only when no explicit human action is requested. Do not leave a task in-progress after reporting completed implementation unless the user explicitly wants more work to continue in the same task.\n\nTaxonomy rule: always call get_config before setting category, type, priority, approach, status, or complexity. Do not rely on remembered enum values.\n\nUse identifier prefixes to choose the correct retrieval path: T-123 goes to task tools such as get_task, IN-123 goes to initiative tools such as get_initiative, WS-123 goes to workstream tools such as get_workstream, I-123 goes to image tools such as get_image, and D-123 goes to document tools such as get_document. Do not send I-123 or D-123 identifiers to get_task.\n\nFor annotated images, use search_images first when you know only a keyword, filename, or rough title. Once you have the exact identifier, call get_image, then use get_annotated_attachment_sessions to list saved sessions, then use read_annotated_attachment_payload for the structured annotation payload.\n\nFor documents, use search_documents first when you know only a keyword, filename, or rough title. Once you have the exact identifier, call get_document, then read_document_content to inspect the document body. For a lower-friction review flow, prefer review_document; otherwise use get_document_review_sessions or create_document_review_session, then add_document_review_comment to record review feedback.`,
590
591
  });
591
592
  const TOOL_PERMISSIONS = MCP_TOOL_PERMISSIONS;
592
593
  const RESOURCE_PERMISSIONS = {
@@ -923,6 +924,8 @@ export function createTaskforceMcpServer(options = {}) {
923
924
  const auditTokenId = options.auth?.tokenId ? String(options.auth.tokenId).trim() : null;
924
925
  const configuredAiProfileToken = options.auth?.aiProfileToken ? String(options.auth.aiProfileToken).trim() || null : null;
925
926
  const configuredAiProfileName = options.auth?.aiProfileName ? String(options.auth.aiProfileName).trim() || null : null;
927
+ const configuredAiProfileIcon = options.auth?.aiProfileIcon ? String(options.auth.aiProfileIcon).trim() || null : null;
928
+ const configuredAiProfileColor = options.auth?.aiProfileColor ? String(options.auth.aiProfileColor).trim() || null : null;
926
929
  const configuredAiProfileDescription = options.auth?.aiProfileDescription ? String(options.auth.aiProfileDescription).trim() || null : null;
927
930
  const configuredAiProfileRole = options.auth?.aiProfileRole ? String(options.auth.aiProfileRole).trim() || null : null;
928
931
  const configuredAiProfileProvider = options.auth?.aiProfileProvider ? String(options.auth.aiProfileProvider).trim() || null : null;
@@ -932,6 +935,7 @@ export function createTaskforceMcpServer(options = {}) {
932
935
  const auditIp = options.auth?.ip ? String(options.auth.ip).trim() || null : null;
933
936
  const auditUserAgent = options.auth?.userAgent ? String(options.auth.userAgent).trim() || null : null;
934
937
  let activeAiProfileId = null;
938
+ let activeAiBindingSource = null;
935
939
  function getEligibleAssigneeActors() {
936
940
  return core.listAssignableActors(ACTIVE_WORKSPACE_ID)
937
941
  .filter((actor) => String(actor.id || '').trim().toLowerCase() !== 'agent')
@@ -1084,20 +1088,24 @@ export function createTaskforceMcpServer(options = {}) {
1084
1088
  const token = String(args.profileToken || '').trim();
1085
1089
  return token || null;
1086
1090
  }
1091
+ function getCurrentAiBindingSource() {
1092
+ if (!activeAiProfileId)
1093
+ return 'none';
1094
+ return activeAiBindingSource || 'session';
1095
+ }
1087
1096
  function buildConfiguredAiProfileResolveInput(includeToken = true) {
1088
- if (!configuredAiProfileName)
1089
- return null;
1090
1097
  const configuredSurfaceType = parseAiProfileSurfaceType(configuredAiProfileSurfaceType);
1091
- return {
1092
- workspaceId: ACTIVE_WORKSPACE_ID,
1093
- name: configuredAiProfileName,
1094
- ...(includeToken && configuredAiProfileToken ? { profileToken: configuredAiProfileToken } : {}),
1095
- ...(configuredAiProfileDescription ? { description: configuredAiProfileDescription } : {}),
1096
- ...(configuredAiProfileRole ? { role: configuredAiProfileRole } : {}),
1097
- ...(configuredAiProfileProvider ? { provider: configuredAiProfileProvider } : {}),
1098
- ...(configuredAiProfileModel ? { model: configuredAiProfileModel } : {}),
1099
- ...(configuredSurfaceType ? { surfaceType: configuredSurfaceType } : {}),
1100
- };
1098
+ return buildResolveAiProfileInputFromBootstrap(ACTIVE_WORKSPACE_ID, {
1099
+ aiProfileToken: configuredAiProfileToken,
1100
+ aiProfileName: configuredAiProfileName,
1101
+ aiProfileIcon: configuredAiProfileIcon,
1102
+ aiProfileColor: configuredAiProfileColor,
1103
+ aiProfileDescription: configuredAiProfileDescription,
1104
+ aiProfileRole: configuredAiProfileRole,
1105
+ aiProfileProvider: configuredAiProfileProvider,
1106
+ aiProfileModel: configuredAiProfileModel,
1107
+ aiProfileSurfaceType: configuredSurfaceType,
1108
+ }, includeToken);
1101
1109
  }
1102
1110
  function ensureResolvedProfileFromConnection(defaults) {
1103
1111
  if (activeAiProfileId)
@@ -1110,6 +1118,7 @@ export function createTaskforceMcpServer(options = {}) {
1110
1118
  try {
1111
1119
  const resolved = core.resolveAiProfile(configuredResolveInput);
1112
1120
  activeAiProfileId = resolved.profile.id;
1121
+ activeAiBindingSource = 'connection_bootstrap';
1113
1122
  return;
1114
1123
  }
1115
1124
  catch {
@@ -1122,6 +1131,7 @@ export function createTaskforceMcpServer(options = {}) {
1122
1131
  });
1123
1132
  if (profile) {
1124
1133
  activeAiProfileId = profile.id;
1134
+ activeAiBindingSource = 'connection_bootstrap';
1125
1135
  return;
1126
1136
  }
1127
1137
  if (!allowCreateFromName || !configuredAiProfileName) {
@@ -1131,6 +1141,7 @@ export function createTaskforceMcpServer(options = {}) {
1131
1141
  if (allowCreateFromName && configuredResolveFallbackInput) {
1132
1142
  const resolved = core.resolveAiProfile(configuredResolveFallbackInput);
1133
1143
  activeAiProfileId = resolved.profile.id;
1144
+ activeAiBindingSource = 'connection_bootstrap';
1134
1145
  }
1135
1146
  }
1136
1147
  function ensureResolvedProfileForTool(name, args) {
@@ -1151,6 +1162,7 @@ export function createTaskforceMcpServer(options = {}) {
1151
1162
  });
1152
1163
  if (profile) {
1153
1164
  activeAiProfileId = profile.id;
1165
+ activeAiBindingSource = 'token_lookup';
1154
1166
  return;
1155
1167
  }
1156
1168
  const configuredResolveInput = buildConfiguredAiProfileResolveInput(false);
@@ -1158,6 +1170,7 @@ export function createTaskforceMcpServer(options = {}) {
1158
1170
  try {
1159
1171
  const resolved = core.resolveAiProfile(configuredResolveInput);
1160
1172
  activeAiProfileId = resolved.profile.id;
1173
+ activeAiBindingSource = 'connection_bootstrap';
1161
1174
  return;
1162
1175
  }
1163
1176
  catch {
@@ -1651,7 +1664,7 @@ export function createTaskforceMcpServer(options = {}) {
1651
1664
  console.error(`[Taskforce MCP] Failed to send resources/updated for ${uri}: ${error.message}`);
1652
1665
  }
1653
1666
  }
1654
- async function notifyResourceMutation(options) {
1667
+ function collectResourceMutationUris(options) {
1655
1668
  const uris = new Set();
1656
1669
  if (options.includeConfig)
1657
1670
  uris.add(RESOURCE_CONFIG_URI);
@@ -1678,10 +1691,39 @@ export function createTaskforceMcpServer(options = {}) {
1678
1691
  uris.add(workstreamUriFor(workstreamId));
1679
1692
  }
1680
1693
  }
1694
+ return uris;
1695
+ }
1696
+ const pendingResourceInvalidations = new Set();
1697
+ let resourceInvalidationFlushHandle = null;
1698
+ function scheduleResourceInvalidationFlush() {
1699
+ if (resourceInvalidationFlushHandle)
1700
+ return;
1701
+ resourceInvalidationFlushHandle = setTimeout(() => {
1702
+ resourceInvalidationFlushHandle = null;
1703
+ void flushScheduledResourceInvalidations();
1704
+ }, 0);
1705
+ }
1706
+ async function flushScheduledResourceInvalidations() {
1707
+ if (pendingResourceInvalidations.size === 0)
1708
+ return;
1709
+ const uris = Array.from(pendingResourceInvalidations);
1710
+ pendingResourceInvalidations.clear();
1681
1711
  for (const uri of uris) {
1682
1712
  await notifyResourceUpdatedIfSubscribed(uri);
1683
1713
  }
1684
1714
  }
1715
+ function scheduleResourceMutation(options) {
1716
+ const uris = collectResourceMutationUris(options);
1717
+ if (uris.size === 0)
1718
+ return;
1719
+ for (const uri of uris) {
1720
+ pendingResourceInvalidations.add(uri);
1721
+ }
1722
+ scheduleResourceInvalidationFlush();
1723
+ }
1724
+ async function notifyResourceMutation(options) {
1725
+ scheduleResourceMutation(options);
1726
+ }
1685
1727
  function buildConfigResourcePayload() {
1686
1728
  return {
1687
1729
  project: {
@@ -1703,13 +1745,26 @@ export function createTaskforceMcpServer(options = {}) {
1703
1745
  };
1704
1746
  }
1705
1747
  function buildTasksResourcePayload() {
1706
- const tasks = core.listTasks(ACTIVE_WORKSPACE_ID).tasks;
1748
+ const tasks = core.listTasks(ACTIVE_WORKSPACE_ID).tasks
1749
+ .map((task) => buildPlanningTaskSummary(task));
1707
1750
  return {
1708
1751
  total: tasks.length,
1709
1752
  tasks,
1710
1753
  };
1711
1754
  }
1712
- function listHistoricalWorkspaceTasks() {
1755
+ function buildPlanningTaskSummary(task) {
1756
+ const { description, comments, taskEvents, activity, createdByActor, assigneeActor, attachments, checklistItems, ...rest } = task;
1757
+ return rest;
1758
+ }
1759
+ function buildPlanningWorkstreamSummary(workstream) {
1760
+ const { description, taskIds, ...rest } = workstream;
1761
+ return rest;
1762
+ }
1763
+ function buildPlanningInitiativeSummary(initiative) {
1764
+ const { description, ...rest } = initiative;
1765
+ return rest;
1766
+ }
1767
+ function buildHistoricalWorkspaceTaskSnapshot() {
1713
1768
  const activeTasks = core.listTasks(ACTIVE_WORKSPACE_ID).tasks;
1714
1769
  const archivedTasks = core.listArchive(ACTIVE_WORKSPACE_ID).archived;
1715
1770
  const byId = new Map();
@@ -1717,40 +1772,70 @@ export function createTaskforceMcpServer(options = {}) {
1717
1772
  byId.set(task.id, task);
1718
1773
  for (const task of archivedTasks)
1719
1774
  byId.set(task.id, task);
1720
- return Array.from(byId.values());
1775
+ const tasks = Array.from(byId.values());
1776
+ const tasksByWorkstreamId = new Map();
1777
+ for (const task of tasks) {
1778
+ const normalizedWorkstreamId = String(task.workstreamId || '').trim();
1779
+ if (!normalizedWorkstreamId)
1780
+ continue;
1781
+ const current = tasksByWorkstreamId.get(normalizedWorkstreamId) || [];
1782
+ current.push(buildPlanningTaskSummary(task));
1783
+ tasksByWorkstreamId.set(normalizedWorkstreamId, current);
1784
+ }
1785
+ return {
1786
+ tasks,
1787
+ tasksByWorkstreamId,
1788
+ };
1721
1789
  }
1722
- function buildInitiativeResourcePayload(initiative) {
1723
- const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
1724
- .filter((workstream) => workstream.initiativeId === initiative.id);
1725
- const tasks = listHistoricalWorkspaceTasks()
1726
- .filter((task) => workstreams.some((workstream) => workstream.id === task.workstreamId));
1790
+ function buildInitiativeResourcePayload(initiative, options) {
1791
+ const tasksByWorkstreamId = options?.tasksByWorkstreamId || buildHistoricalWorkspaceTaskSnapshot().tasksByWorkstreamId;
1792
+ const workstreams = options?.workstreamsByInitiativeId?.get(initiative.id)
1793
+ || core.listWorkstreams(ACTIVE_WORKSPACE_ID)
1794
+ .filter((workstream) => workstream.initiativeId === initiative.id);
1795
+ const tasks = workstreams.flatMap((workstream) => tasksByWorkstreamId.get(workstream.id) || []);
1727
1796
  return {
1728
- initiative,
1797
+ initiative: buildPlanningInitiativeSummary(initiative),
1729
1798
  workstreamCount: workstreams.length,
1730
1799
  taskCount: tasks.length,
1731
- workstreams,
1800
+ workstreams: workstreams.map((workstream) => buildPlanningWorkstreamSummary(workstream)),
1732
1801
  };
1733
1802
  }
1734
- function buildWorkstreamResourcePayload(workstream) {
1735
- const tasks = listHistoricalWorkspaceTasks()
1736
- .filter((task) => task.workstreamId === workstream.id);
1803
+ function buildWorkstreamResourcePayload(workstream, options) {
1804
+ const tasksByWorkstreamId = options?.tasksByWorkstreamId || buildHistoricalWorkspaceTaskSnapshot().tasksByWorkstreamId;
1805
+ const tasks = tasksByWorkstreamId.get(workstream.id)
1806
+ || [];
1737
1807
  return {
1738
- workstream,
1808
+ workstream: buildPlanningWorkstreamSummary(workstream),
1739
1809
  taskCount: tasks.length,
1740
1810
  tasks,
1741
1811
  };
1742
1812
  }
1743
1813
  function buildInitiativesResourcePayload() {
1814
+ const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID);
1815
+ const workstreamsByInitiativeId = new Map();
1816
+ for (const workstream of workstreams) {
1817
+ const normalizedInitiativeId = String(workstream.initiativeId || '').trim();
1818
+ if (!normalizedInitiativeId)
1819
+ continue;
1820
+ const current = workstreamsByInitiativeId.get(normalizedInitiativeId) || [];
1821
+ current.push(workstream);
1822
+ workstreamsByInitiativeId.set(normalizedInitiativeId, current);
1823
+ }
1824
+ const { tasksByWorkstreamId } = buildHistoricalWorkspaceTaskSnapshot();
1744
1825
  const initiatives = core.listInitiatives(ACTIVE_WORKSPACE_ID)
1745
- .map((initiative) => buildInitiativeResourcePayload(initiative));
1826
+ .map((initiative) => buildInitiativeResourcePayload(initiative, {
1827
+ workstreamsByInitiativeId,
1828
+ tasksByWorkstreamId,
1829
+ }));
1746
1830
  return {
1747
1831
  total: initiatives.length,
1748
1832
  initiatives,
1749
1833
  };
1750
1834
  }
1751
1835
  function buildWorkstreamsResourcePayload() {
1836
+ const { tasksByWorkstreamId } = buildHistoricalWorkspaceTaskSnapshot();
1752
1837
  const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
1753
- .map((workstream) => buildWorkstreamResourcePayload(workstream));
1838
+ .map((workstream) => buildWorkstreamResourcePayload(workstream, { tasksByWorkstreamId }));
1754
1839
  return {
1755
1840
  total: workstreams.length,
1756
1841
  workstreams,
@@ -2038,8 +2123,8 @@ export function createTaskforceMcpServer(options = {}) {
2038
2123
  type: "object",
2039
2124
  properties: {
2040
2125
  name: { type: "string", description: "Display name for the AI collaborator." },
2041
- icon: { type: "string", description: "Optional icon name or emoji saved with the AI profile." },
2042
- color: { type: "string", description: "Optional profile color saved with the AI profile." },
2126
+ icon: { type: "string", description: "Optional icon name or emoji saved with the AI profile. Omit to leave unchanged when profileToken is provided." },
2127
+ color: { type: "string", description: "Optional profile color saved with the AI profile. Omit to leave unchanged when profileToken is provided." },
2043
2128
  description: { type: "string", description: "Optional short description stored on the AI profile. Omit to leave unchanged when profileToken is provided." },
2044
2129
  role: { type: "string", description: "Optional role label stored on the AI profile. Omit to leave unchanged when profileToken is provided." },
2045
2130
  provider: { type: "string", description: "Optional provider name stored as secondary diagnostic metadata on the AI profile. Omit to leave unchanged when profileToken is provided." },
@@ -3217,18 +3302,7 @@ export function createTaskforceMcpServer(options = {}) {
3217
3302
  }
3218
3303
  }
3219
3304
  };
3220
- }).filter((tool) => {
3221
- if (!hasPermission(TOOL_PERMISSIONS[tool.name]))
3222
- return false;
3223
- const taskforceToolsEnv = process.env.TASKFORCE_TOOLS;
3224
- if (taskforceToolsEnv) {
3225
- const enabledTools = taskforceToolsEnv.split(',').map(t => t.trim()).filter(Boolean);
3226
- if (enabledTools.length > 0 && !enabledTools.includes(tool.name)) {
3227
- return false;
3228
- }
3229
- }
3230
- return true;
3231
- });
3305
+ }).filter((tool) => hasPermission(TOOL_PERMISSIONS[tool.name]));
3232
3306
  return {
3233
3307
  tools,
3234
3308
  };
@@ -3311,6 +3385,7 @@ export function createTaskforceMcpServer(options = {}) {
3311
3385
  profileToken: typeof profileToken === 'string' ? profileToken : null
3312
3386
  });
3313
3387
  activeAiProfileId = resolved.profile.id;
3388
+ activeAiBindingSource = 'session';
3314
3389
  recordMcpWriteAudit('resolve_profile', {
3315
3390
  profileId: resolved.profile.id,
3316
3391
  created: resolved.created
@@ -3358,6 +3433,7 @@ export function createTaskforceMcpServer(options = {}) {
3358
3433
  let profile = activeAiProfileId
3359
3434
  ? core.listAiProfiles(ACTIVE_WORKSPACE_ID).find((entry) => entry.id === activeAiProfileId) || null
3360
3435
  : null;
3436
+ let bindingSource = getCurrentAiBindingSource();
3361
3437
  if (!profile && typeof profileToken === 'string' && profileToken.trim()) {
3362
3438
  profile = core.getAiProfileByToken({
3363
3439
  workspaceId: ACTIVE_WORKSPACE_ID,
@@ -3366,6 +3442,7 @@ export function createTaskforceMcpServer(options = {}) {
3366
3442
  if (!profile) {
3367
3443
  throw new Error('Invalid AI profile token. Call resolve_profile again and retry.');
3368
3444
  }
3445
+ bindingSource = 'token_lookup';
3369
3446
  }
3370
3447
  return {
3371
3448
  content: [{
@@ -3373,6 +3450,8 @@ export function createTaskforceMcpServer(options = {}) {
3373
3450
  text: JSON.stringify({
3374
3451
  workspaceId: ACTIVE_WORKSPACE_ID,
3375
3452
  identityBound: Boolean(activeAiProfileId),
3453
+ bindingSource,
3454
+ writeReady: Boolean(profile),
3376
3455
  profileToken: profile?.profileToken ?? null,
3377
3456
  profile: profile ? buildAiProfileDescriptor(profile) : null,
3378
3457
  taskFilter: profile ? { assignee: profile.id } : null,
@@ -4779,7 +4858,11 @@ export function createTaskforceMcpServer(options = {}) {
4779
4858
  ...createArgs,
4780
4859
  ...normalized,
4781
4860
  createdBy: getActiveMcpCreatedBy()
4782
- }, { workspaceId: ACTIVE_WORKSPACE_ID, actorRef: getActiveMcpActorRef() });
4861
+ }, {
4862
+ workspaceId: ACTIVE_WORKSPACE_ID,
4863
+ actorRef: getActiveMcpActorRef(),
4864
+ provisionalLocalReference: shouldUseProvisionalTaskReferences(runtimeMode)
4865
+ });
4783
4866
  recordMcpWriteAudit('create_task', {
4784
4867
  taskId: task?.id || null,
4785
4868
  title: task?.title || null