@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.
- package/dist/Taskforce.module.css +37 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +72 -33
- package/dist/TaskforceCore.test.js +848 -130
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +109 -49
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +473 -256
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +100 -10
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +459 -127
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +13 -12
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +8 -0
- package/dist/hooks/useTaskforce.js +72 -19
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +131 -48
- package/dist/mcp/runtime.test.js +321 -83
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +68 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +17 -1
- package/dist/server/index.test.js +15 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +105 -45
- package/dist/server/routes/auth.d.ts +6 -0
- package/dist/server/routes/auth.js +505 -30
- package/dist/server/routes/billing.js +517 -24
- package/dist/server/routes/billing.test.js +967 -45
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +16 -6
- package/dist/server/routes/sync.integration.test.js +290 -0
- package/dist/server/routes/sync.js +234 -1922
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +29 -22
- package/dist/server/routes.test.js +639 -82
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +98 -0
- package/dist/sync/contentSyncState.js +1004 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +38 -0
- package/dist/sync/workspacePullFeed.js +349 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +278 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -3
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
package/dist/mcp/runtime.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
|
1736
|
-
|
|
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
|
-
}, {
|
|
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
|