@taskforcehq/taskforce 0.3.327 → 0.3.328
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 +24 -53
- package/dist/components/activity/EntityActivityTimeline.js +15 -5
- package/dist/components/context/AttachmentPreviewHost.js +1 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
- package/dist/components/context/ContextAttachmentManager.js +116 -20
- package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
- package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
- package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
- package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
- package/dist/components/features/planning/PlanningModule.js +26 -9
- package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
- package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
- package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +2 -2
- package/dist/components/task/TaskCard.js +37 -4
- package/dist/components/task/TaskContextUpload.d.ts +3 -0
- package/dist/components/task/TaskContextUpload.js +2 -2
- package/dist/components/task/TaskForm.d.ts +4 -0
- package/dist/components/task/TaskForm.js +32 -7
- package/dist/components/task/TaskImageReviews.js +16 -16
- package/dist/components/task/TaskStatusActions.js +11 -2
- package/dist/components/task/TaskWorkstreamPicker.js +2 -1
- package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
- package/dist/components/ui/DocumentTypeIcon.js +8 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
- package/dist/components/ui/TaxonomyDropdown.js +30 -3
- package/dist/components/views/StandaloneLayout.js +62 -9
- package/dist/components/views/WidgetView.js +8 -5
- package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
- package/dist/components/views/panels/PlanningDrawer.js +48 -44
- package/dist/config/envSchema.js +1 -0
- package/dist/config/localServiceCloudTarget.d.ts +10 -0
- package/dist/config/localServiceCloudTarget.js +76 -0
- package/dist/core/AiProfileService.d.ts +1 -0
- package/dist/core/AiProfileService.js +2 -1
- package/dist/core/AttachmentLinkService.js +7 -4
- package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
- package/dist/core/DeletedTaskLifecycleService.js +21 -0
- package/dist/core/McpTokenService.d.ts +4 -0
- package/dist/core/McpTokenService.js +52 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
- package/dist/core/Taskforce.d.ts +63 -1
- package/dist/core/Taskforce.js +558 -73
- package/dist/core/types.d.ts +2 -1
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
- package/dist/documentReviews/service.d.ts +4 -0
- package/dist/documentReviews/service.js +141 -46
- package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
- package/dist/hooks/tasks/useTaskFormActions.js +33 -7
- package/dist/hooks/useTaskforce.d.ts +59 -50
- package/dist/hooks/useTaskforce.js +103 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
- package/dist/mcp/canonicalAssetHelpers.js +39 -5
- package/dist/mcp/collaborationRegistrar.js +15 -13
- package/dist/mcp/documentAssetRegistrar.js +16 -9
- package/dist/mcp/localServiceProxy.js +4 -1
- package/dist/mcp/runtime.d.ts +16 -0
- package/dist/mcp/runtime.js +566 -171
- package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
- package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
- package/dist/mcp/taskPlanningRegistrar.js +42 -34
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/migrations/taskSchemaMigrations.js +35 -0
- package/dist/runtime/appGateDefinitions.d.ts +2 -2
- package/dist/runtime/appGateDefinitions.js +2 -2
- package/dist/server/appAccess.d.ts +12 -0
- package/dist/server/appAccess.js +21 -0
- package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
- package/dist/server/authorizedWorkspaceAccess.js +40 -0
- package/dist/server/cloudCollectionRead.d.ts +25 -0
- package/dist/server/cloudCollectionRead.js +120 -0
- package/dist/server/cloudCollectionReadPool.d.ts +52 -0
- package/dist/server/cloudCollectionReadPool.js +140 -0
- package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
- package/dist/server/cloudCollectionReadWorker.js +36 -0
- package/dist/server/index.d.ts +3 -11
- package/dist/server/index.js +53 -40
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +160 -13
- package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
- package/dist/server/routes/durableTaskHttpRouters.js +2 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/shared.d.ts +24 -0
- package/dist/server/routes/shared.js +114 -18
- package/dist/server/routes/sync.js +22 -0
- package/dist/server/routes/syncV3FeedRoutes.js +4 -2
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
- package/dist/server/routes/syncV3OperationRoutes.js +17 -2
- package/dist/server/routes/tasks.js +199 -89
- package/dist/server/routes.js +35 -7
- package/dist/service/localServiceCli.js +36 -25
- package/dist/service/localServiceClientLifecycle.js +67 -6
- package/dist/shared/planningIdentity.d.ts +20 -0
- package/dist/shared/planningIdentity.js +79 -0
- package/dist/storage/postgresAdapter.js +4 -0
- package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
- package/dist/storage/postgresRequestDiagnostics.js +24 -0
- package/dist/storage/workspaceAssetStore.d.ts +32 -0
- package/dist/storage/workspaceAssetStore.js +70 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
- package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -1
- package/dist/sync/syncApplyHandlers.js +29 -0
- package/dist/sync/syncService.js +9 -0
- package/dist/sync/taskSyncPayload.d.ts +1 -0
- package/dist/sync/taskSyncPayload.js +6 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
- package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
- package/dist/sync/v3/localOutboxDispatcher.js +5 -1
- package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
- package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
- package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
- package/dist/sync/v3/syncDurabilityStore.js +171 -33
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
- package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
- package/dist/sync/workspacePullFeed.d.ts +6 -1
- package/dist/sync/workspacePullFeed.js +172 -161
- package/dist/sync/workspaceSyncModel.d.ts +11 -0
- package/dist/sync/workspaceSyncModel.js +27 -2
- package/dist/types.d.ts +8 -0
- package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
- package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
- package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
- package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
- package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
- package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
- package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
- package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
- package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
- package/dist/ui/assets/index-BUplSsv_.js +7 -0
- package/dist/ui/assets/index-gz2EXuoK.css +1 -0
- package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.js +1 -1
- package/dist/utils/contextAttachmentUpload.d.ts +2 -0
- package/dist/utils/contextAttachmentUpload.js +7 -0
- package/dist/utils/contextFiles.d.ts +1 -1
- package/dist/utils/contextFiles.js +4 -4
- package/package.json +6 -1
- package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
- package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
- package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
- package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
- package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
- package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
- package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
- package/dist/ui/assets/index-BzJlVnNS.css +0 -1
- package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
package/dist/mcp/runtime.js
CHANGED
|
@@ -16,7 +16,7 @@ import { AnnotatedAttachmentCommandError, createAnnotatedAttachmentCommandServic
|
|
|
16
16
|
import { createDocumentReviewCommandService, DocumentReviewCommandError, } from '../documentReviews/commandService.js';
|
|
17
17
|
import { COMPLEXITY_OPTIONS, resolveNumberOption, resolveStringOption, STATUS_OPTIONS } from './optionResolution.js';
|
|
18
18
|
import { buildResolveAiProfileInputFromBootstrap } from './aiProfileBootstrap.js';
|
|
19
|
-
import { createCanonicalAssetHelpers } from './canonicalAssetHelpers.js';
|
|
19
|
+
import { CanonicalDocumentHeadStaleError, createCanonicalAssetHelpers } from './canonicalAssetHelpers.js';
|
|
20
20
|
import { createDocumentHelpers } from './documentHelpers.js';
|
|
21
21
|
import { createTaskAttachmentHelpers } from './taskAttachmentHelpers.js';
|
|
22
22
|
import { listRegisteredGitWorktreeRoots } from './gitWorktreeRoots.js';
|
|
@@ -59,7 +59,7 @@ import { createTaskPlanCommand } from './taskPlanCommand.js';
|
|
|
59
59
|
import { WorkflowAssignmentService } from '../core/WorkflowAssignmentService.js';
|
|
60
60
|
import { WorkflowExecutionService, WorkflowExecutionServiceError, } from '../core/WorkflowExecutionService.js';
|
|
61
61
|
import { WorkflowStore } from '../storage/workflowStore.js';
|
|
62
|
-
import { getMcpClientRegistryEntry, normalizeMcpConnectionId, parseMcpClientId } from './clientRegistry.js';
|
|
62
|
+
import { getMcpClientRegistryEntry, inferLegacyMcpClientIdFromProfile, normalizeMcpConnectionId, parseMcpClientId } from './clientRegistry.js';
|
|
63
63
|
import { BedrockAvatarImageProvider } from '../services/avatarImageProvider.js';
|
|
64
64
|
import { AiProfileAvatarGenerationError, checkAiProfileAvatarGenerationRateLimit, generateAiProfileAvatar, } from '../services/aiProfileAvatarGenerationService.js';
|
|
65
65
|
import { isAiProfileAvatarGenerationEligible } from '../shared/aiProfileAvatarEligibility.js';
|
|
@@ -410,6 +410,22 @@ function resolveRecoverableToolError(error, context) {
|
|
|
410
410
|
};
|
|
411
411
|
}
|
|
412
412
|
if (error instanceof TaskforceRuleError) {
|
|
413
|
+
if (code === 'AI_PROFILE_SELECTION_REQUIRED') {
|
|
414
|
+
const selectionDetails = details && typeof details === 'object' && !Array.isArray(details)
|
|
415
|
+
? details
|
|
416
|
+
: {};
|
|
417
|
+
const suggestedArgs = selectionDetails.suggestedArgs;
|
|
418
|
+
return {
|
|
419
|
+
message,
|
|
420
|
+
code,
|
|
421
|
+
details,
|
|
422
|
+
nextTool: 'resolve_profile',
|
|
423
|
+
...(suggestedArgs && typeof suggestedArgs === 'object' && !Array.isArray(suggestedArgs)
|
|
424
|
+
? { suggestedArgs: suggestedArgs }
|
|
425
|
+
: {}),
|
|
426
|
+
retryHint: 'Choose the intended compatible profile candidate and retry resolve_profile with the same name plus its profileId. If more than one candidate is present and the intended identity is unclear, ask the user before selecting.',
|
|
427
|
+
};
|
|
428
|
+
}
|
|
413
429
|
if (code === 'AI_PROFILE_AVATAR_PROVIDER_FAILED' || code === 'AI_PROFILE_AVATAR_PERSISTENCE_FAILED') {
|
|
414
430
|
return {
|
|
415
431
|
message,
|
|
@@ -677,6 +693,14 @@ export function buildUnexpectedToolErrorResult(requestId) {
|
|
|
677
693
|
isError: true,
|
|
678
694
|
};
|
|
679
695
|
}
|
|
696
|
+
function measureToolResultBytes(result) {
|
|
697
|
+
try {
|
|
698
|
+
return Buffer.byteLength(JSON.stringify(result), 'utf8');
|
|
699
|
+
}
|
|
700
|
+
catch {
|
|
701
|
+
return 0;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
680
704
|
export function buildBatchToolError(error, fallbackStatusCode) {
|
|
681
705
|
const resolved = resolveRecoverableToolError(error);
|
|
682
706
|
if (!resolved) {
|
|
@@ -786,6 +810,29 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
786
810
|
}
|
|
787
811
|
const mcpRequestIdentity = new AsyncLocalStorage();
|
|
788
812
|
const cloudCreateRequestIdentity = new AsyncLocalStorage();
|
|
813
|
+
const mcpToolSubphaseTimings = new AsyncLocalStorage();
|
|
814
|
+
const measureToolSubphase = (phase, operation) => {
|
|
815
|
+
const startedAtMs = Date.now();
|
|
816
|
+
try {
|
|
817
|
+
return operation();
|
|
818
|
+
}
|
|
819
|
+
finally {
|
|
820
|
+
const timings = mcpToolSubphaseTimings.getStore();
|
|
821
|
+
if (timings)
|
|
822
|
+
timings[phase] = (timings[phase] || 0) + (Date.now() - startedAtMs);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
const measureToolSubphaseAsync = async (phase, operation) => {
|
|
826
|
+
const startedAtMs = Date.now();
|
|
827
|
+
try {
|
|
828
|
+
return await operation();
|
|
829
|
+
}
|
|
830
|
+
finally {
|
|
831
|
+
const timings = mcpToolSubphaseTimings.getStore();
|
|
832
|
+
if (timings)
|
|
833
|
+
timings[phase] = (timings[phase] || 0) + (Date.now() - startedAtMs);
|
|
834
|
+
}
|
|
835
|
+
};
|
|
789
836
|
const operationIdentityScope = options.operationIdentityScope
|
|
790
837
|
? {
|
|
791
838
|
generation: String(options.operationIdentityScope.generation || '').trim(),
|
|
@@ -824,28 +871,34 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
824
871
|
return null;
|
|
825
872
|
return `${kind}-${createHash('sha256').update(`${kind}:${requestIdentity}:${discriminator || ''}`).digest('hex').slice(0, 32)}`;
|
|
826
873
|
};
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
874
|
+
const normalizeExplicitIdempotencyKey = (value) => {
|
|
875
|
+
if (value === undefined || value === null)
|
|
876
|
+
return null;
|
|
877
|
+
const key = String(value).trim();
|
|
878
|
+
if (!key)
|
|
879
|
+
throw new McpPublicError('idempotencyKey must contain non-whitespace text.');
|
|
880
|
+
if (key.length > 128)
|
|
881
|
+
throw new McpPublicError('idempotencyKey must contain at most 128 characters.');
|
|
882
|
+
return key;
|
|
883
|
+
};
|
|
884
|
+
const stableExplicitMutationId = (kind, tool, idempotencyKey, discriminator) => {
|
|
885
|
+
const fingerprint = createHash('sha256').update(JSON.stringify([
|
|
886
|
+
kind,
|
|
887
|
+
tenantId,
|
|
888
|
+
ACTIVE_WORKSPACE_ID,
|
|
889
|
+
getActiveMcpActorRef(),
|
|
890
|
+
tool,
|
|
891
|
+
idempotencyKey,
|
|
892
|
+
discriminator || '',
|
|
893
|
+
])).digest('hex');
|
|
847
894
|
return `${kind}-${fingerprint.slice(0, 32)}`;
|
|
848
895
|
};
|
|
896
|
+
const stableTaskCreateIdempotencyId = (kind, idempotencyKey, discriminator) => stableExplicitMutationId(kind, 'create_task', idempotencyKey, discriminator);
|
|
897
|
+
const requireDurableExplicitIdempotency = (enabled, tool) => {
|
|
898
|
+
if (enabled)
|
|
899
|
+
return;
|
|
900
|
+
throw new McpPublicError(`idempotencyKey requires the durable ${tool} mutation boundary, which is unavailable for this runtime.`, 'MCP_IDEMPOTENCY_UNAVAILABLE', 503);
|
|
901
|
+
};
|
|
849
902
|
const durableTaskMutationRouter = options.durableTaskMutationRouter || createDurableTaskMutationRouter({
|
|
850
903
|
core,
|
|
851
904
|
runtimeMode,
|
|
@@ -929,7 +982,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
929
982
|
cloudClientId: 'mcp-cloud',
|
|
930
983
|
createOperationId: () => mutationOptions?.operationId
|
|
931
984
|
|| requireStableMcpRequestId('operation', 'initiative'),
|
|
932
|
-
createCommentId: () =>
|
|
985
|
+
createCommentId: () => mutationOptions?.commentId
|
|
986
|
+
|| requireStableMcpRequestId('comment', 'initiative-comment'),
|
|
933
987
|
deferPostCommitNotification: mutationOptions?.deferPostCommitNotification,
|
|
934
988
|
});
|
|
935
989
|
const initiativeFromDurableResult = (result) => result.state === 'pending' ? result.provisionalInitiative : result.authoritativeInitiative;
|
|
@@ -946,7 +1000,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
946
1000
|
cloudClientId: 'mcp-cloud',
|
|
947
1001
|
createOperationId: () => mutationOptions?.operationId
|
|
948
1002
|
|| requireStableMcpRequestId('operation', 'workstream'),
|
|
949
|
-
createCommentId: () =>
|
|
1003
|
+
createCommentId: () => mutationOptions?.commentId
|
|
1004
|
+
|| requireStableMcpRequestId('comment', 'workstream-comment'),
|
|
950
1005
|
deferPostCommitNotification: mutationOptions?.deferPostCommitNotification,
|
|
951
1006
|
});
|
|
952
1007
|
const workstreamFromDurableResult = (result) => result.state === 'pending' ? result.provisionalWorkstream : result.authoritativeWorkstream;
|
|
@@ -1049,7 +1104,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1049
1104
|
legacyProvider: asset.storageProvider,
|
|
1050
1105
|
});
|
|
1051
1106
|
return resolved.source === 'versioned'
|
|
1052
|
-
&& resolved.
|
|
1107
|
+
&& resolved.storageProvider === asset.storageProvider
|
|
1108
|
+
&& resolved.contentSha256 === asset.contentSha256
|
|
1109
|
+
&& resolved.sizeBytes === asset.sizeBytes;
|
|
1053
1110
|
}
|
|
1054
1111
|
catch {
|
|
1055
1112
|
return false;
|
|
@@ -1243,7 +1300,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1243
1300
|
}
|
|
1244
1301
|
return { id: task.id, task };
|
|
1245
1302
|
}
|
|
1246
|
-
const buildDocumentDescriptor = (asset, options) =>
|
|
1303
|
+
const buildDocumentDescriptor = (asset, options) => {
|
|
1304
|
+
const descriptor = withDocumentUrl(documentHelpers.buildDocumentDescriptor(asset, options));
|
|
1305
|
+
if (runtimeMode === 'local')
|
|
1306
|
+
return descriptor;
|
|
1307
|
+
const { storageKey: _storageKey, ...cloudDescriptor } = descriptor;
|
|
1308
|
+
return cloudDescriptor;
|
|
1309
|
+
};
|
|
1247
1310
|
const buildCanonicalDocumentAttachmentHint = documentHelpers.buildCanonicalDocumentAttachmentHint;
|
|
1248
1311
|
function buildBoundedTextContent(content, maxChars) {
|
|
1249
1312
|
const requestedMaxChars = Number(maxChars);
|
|
@@ -1271,7 +1334,19 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1271
1334
|
format: "metadata"
|
|
1272
1335
|
};
|
|
1273
1336
|
}
|
|
1337
|
+
const assertCurrentHead = () => {
|
|
1338
|
+
try {
|
|
1339
|
+
canonicalAssetHelpers.assertCanonicalDocumentHeadCurrent(asset);
|
|
1340
|
+
}
|
|
1341
|
+
catch (error) {
|
|
1342
|
+
if (error instanceof CanonicalDocumentHeadStaleError) {
|
|
1343
|
+
throw new McpPublicError(error.message, error.code, error.statusCode, error.details);
|
|
1344
|
+
}
|
|
1345
|
+
throw error;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1274
1348
|
if (format === "download") {
|
|
1349
|
+
assertCurrentHead();
|
|
1275
1350
|
return {
|
|
1276
1351
|
format: "download",
|
|
1277
1352
|
downloadUrl: canonicalAssetHelpers.buildDocumentDownloadUrl(asset),
|
|
@@ -1279,6 +1354,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1279
1354
|
};
|
|
1280
1355
|
}
|
|
1281
1356
|
if (!isTextReadableMimeType(asset.mimeType || '')) {
|
|
1357
|
+
assertCurrentHead();
|
|
1282
1358
|
return {
|
|
1283
1359
|
format: "download",
|
|
1284
1360
|
downloadUrl: canonicalAssetHelpers.buildDocumentDownloadUrl(asset),
|
|
@@ -1290,6 +1366,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1290
1366
|
buffer = await canonicalAssetHelpers.readCanonicalDocumentBuffer(asset);
|
|
1291
1367
|
}
|
|
1292
1368
|
catch (error) {
|
|
1369
|
+
if (error instanceof CanonicalDocumentHeadStaleError) {
|
|
1370
|
+
throw new McpPublicError(error.message, error.code, error.statusCode, error.details);
|
|
1371
|
+
}
|
|
1293
1372
|
const message = error instanceof Error ? String(error.message || '').trim() : '';
|
|
1294
1373
|
if (/^Document .+ not found in workspace /i.test(message)) {
|
|
1295
1374
|
throw new McpPublicError(message, 'MCP_NOT_FOUND', 404);
|
|
@@ -1384,7 +1463,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1384
1463
|
originalFilename: asset.originalFilename ?? null,
|
|
1385
1464
|
mimeType: asset.mimeType ?? null,
|
|
1386
1465
|
sizeBytes: typeof asset.sizeBytes === 'number' ? asset.sizeBytes : null,
|
|
1387
|
-
storageKey: asset.storageKey ?? null,
|
|
1466
|
+
...(runtimeMode === 'local' ? { storageKey: asset.storageKey ?? null } : {}),
|
|
1388
1467
|
updatedAt: asset.updatedAt ?? null,
|
|
1389
1468
|
apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}`,
|
|
1390
1469
|
};
|
|
@@ -1396,6 +1475,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1396
1475
|
}
|
|
1397
1476
|
catch (error) {
|
|
1398
1477
|
const requestedIdentifier = typeof documentIdentifier === 'string' ? documentIdentifier.trim() : '';
|
|
1478
|
+
if (!requestedIdentifier) {
|
|
1479
|
+
throw new McpPublicError('documentId is required', 'MCP_INVALID_ARGUMENT', 400);
|
|
1480
|
+
}
|
|
1399
1481
|
if (requestedIdentifier && /not found in workspace/i.test(String(error?.message || error))) {
|
|
1400
1482
|
throw new TaskforceRuleError(String(error?.message || error), 404, 'DOCUMENT_NOT_FOUND', {
|
|
1401
1483
|
requestedIdentifier,
|
|
@@ -1439,6 +1521,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1439
1521
|
const auditActorUserId = String(options.auth?.userId || (runtimeMode === 'cloud' ? 'anonymous' : 'local-mcp')).trim() || 'anonymous';
|
|
1440
1522
|
const auditTokenId = options.auth?.tokenId ? String(options.auth.tokenId).trim() : null;
|
|
1441
1523
|
const configuredAiProfileId = options.auth?.aiProfileId ? String(options.auth.aiProfileId).trim() || null : null;
|
|
1524
|
+
const configuredAiProfileIdentityAsserted = options.auth?.aiProfileIdentityAsserted === true;
|
|
1442
1525
|
const configuredAiProfileToken = options.auth?.aiProfileToken ? String(options.auth.aiProfileToken).trim() || null : null;
|
|
1443
1526
|
const configuredAiProfileName = options.auth?.aiProfileName ? String(options.auth.aiProfileName).trim() || null : null;
|
|
1444
1527
|
const configuredAiProfileIcon = options.auth?.aiProfileIcon ? String(options.auth.aiProfileIcon).trim() || null : null;
|
|
@@ -1456,6 +1539,54 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1456
1539
|
const auditUserAgent = options.auth?.userAgent ? String(options.auth.userAgent).trim() || null : null;
|
|
1457
1540
|
let activeAiProfileId = null;
|
|
1458
1541
|
let activeAiBindingSource = null;
|
|
1542
|
+
let aiProfileIdentityConflictDetected = false;
|
|
1543
|
+
function failAiProfileBindingConflict(message, details) {
|
|
1544
|
+
aiProfileIdentityConflictDetected = true;
|
|
1545
|
+
let credentialRevoked = false;
|
|
1546
|
+
if (auditActorType === 'oauth' && auditTokenId) {
|
|
1547
|
+
core.revokeMcpOAuthConnectorGrant({
|
|
1548
|
+
actorUserId: auditActorUserId,
|
|
1549
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1550
|
+
grantId: auditTokenId,
|
|
1551
|
+
});
|
|
1552
|
+
credentialRevoked = true;
|
|
1553
|
+
}
|
|
1554
|
+
throw new TaskforceRuleError(credentialRevoked ? `${message} The conflicting credential was revoked; reconnect before retrying.` : message, 409, 'AI_PROFILE_BINDING_CONFLICT', { ...details, credentialRevoked, nextTool: 'resolve_profile' });
|
|
1555
|
+
}
|
|
1556
|
+
function assertRequestedIdentityMatchesBoundProfile(profile, input) {
|
|
1557
|
+
if (auditActorType !== 'oauth')
|
|
1558
|
+
return;
|
|
1559
|
+
const requestedName = String(input.name || '').trim();
|
|
1560
|
+
const requestedNameKey = requestedName.toLowerCase();
|
|
1561
|
+
const requestedUsernameKey = normalizeConfiguredAiProfileUsername(undefined, requestedName).toLowerCase();
|
|
1562
|
+
const boundNameKey = String(profile.name || '').trim().toLowerCase();
|
|
1563
|
+
const boundUsernameKey = String(profile.username || '').trim().toLowerCase();
|
|
1564
|
+
const nameMatches = Boolean(requestedNameKey) && (requestedNameKey === boundNameKey
|
|
1565
|
+
|| requestedNameKey === boundUsernameKey
|
|
1566
|
+
|| requestedUsernameKey === boundUsernameKey);
|
|
1567
|
+
const normalizeProvider = (value) => String(value || '')
|
|
1568
|
+
.trim()
|
|
1569
|
+
.toLowerCase()
|
|
1570
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
1571
|
+
const requestedProvider = normalizeProvider(input.provider);
|
|
1572
|
+
const boundProvider = normalizeProvider(profile.provider);
|
|
1573
|
+
const providerConflicts = Boolean(requestedProvider && boundProvider && requestedProvider !== boundProvider);
|
|
1574
|
+
if (nameMatches && !providerConflicts)
|
|
1575
|
+
return;
|
|
1576
|
+
failAiProfileBindingConflict('The requested AI identity conflicts with the profile already bound to this OAuth connection.', {
|
|
1577
|
+
reason: 'requested_identity_mismatch',
|
|
1578
|
+
boundProfile: {
|
|
1579
|
+
id: profile.id,
|
|
1580
|
+
name: profile.name,
|
|
1581
|
+
username: profile.username,
|
|
1582
|
+
provider: profile.provider || null,
|
|
1583
|
+
},
|
|
1584
|
+
requestedIdentity: {
|
|
1585
|
+
name: requestedName || null,
|
|
1586
|
+
provider: String(input.provider || '').trim() || null,
|
|
1587
|
+
},
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1459
1590
|
function applyConfiguredMcpClientContext(profileId) {
|
|
1460
1591
|
const shouldBindOAuthGrant = auditActorType === 'oauth'
|
|
1461
1592
|
&& Boolean(auditTokenId)
|
|
@@ -1541,6 +1672,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1541
1672
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1542
1673
|
connectionId: configuredMcpConnectionId,
|
|
1543
1674
|
seatScope: configuredAiProfileSeatScope,
|
|
1675
|
+
repairLegacyBinding: options?.repairConnection !== false,
|
|
1544
1676
|
});
|
|
1545
1677
|
if (!match && auditActorType !== 'oauth' && configuredMcpClientId && configuredMcpClientId !== 'general') {
|
|
1546
1678
|
const candidates = core.listAiProfiles(ACTIVE_WORKSPACE_ID).filter((profile) => {
|
|
@@ -1551,12 +1683,14 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1551
1683
|
&& connection.clientId === configuredMcpClientId);
|
|
1552
1684
|
});
|
|
1553
1685
|
if (candidates.length === 1) {
|
|
1554
|
-
match =
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1686
|
+
match = options?.repairConnection === false
|
|
1687
|
+
? candidates[0]
|
|
1688
|
+
: core.bindAiProfileConnection({
|
|
1689
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1690
|
+
profileId: candidates[0].id,
|
|
1691
|
+
connectionId: configuredMcpConnectionId,
|
|
1692
|
+
clientId: configuredMcpClientId,
|
|
1693
|
+
});
|
|
1560
1694
|
}
|
|
1561
1695
|
}
|
|
1562
1696
|
if (match?.archivedAt) {
|
|
@@ -1575,6 +1709,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1575
1709
|
mergeTarget = next;
|
|
1576
1710
|
}
|
|
1577
1711
|
if (!mergeTarget.archivedAt) {
|
|
1712
|
+
if (options?.repairConnection === false)
|
|
1713
|
+
return mergeTarget;
|
|
1578
1714
|
return core.bindAiProfileConnection({
|
|
1579
1715
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1580
1716
|
profileId: mergeTarget.id,
|
|
@@ -1622,7 +1758,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1622
1758
|
const grantProfile = findProfileForConfiguredGrant();
|
|
1623
1759
|
const connectionProfile = findProfileForConfiguredConnection(options);
|
|
1624
1760
|
if (grantProfile && connectionProfile && grantProfile.id !== connectionProfile.id) {
|
|
1625
|
-
|
|
1761
|
+
failAiProfileBindingConflict('The authenticated OAuth grant and MCP connection are bound to different AI profiles. Select the intended profile in Taskforce before retrying.', { reason: 'grant_connection_mismatch', grantProfileId: grantProfile.id, connectionProfileId: connectionProfile.id });
|
|
1626
1762
|
}
|
|
1627
1763
|
const profile = grantProfile || connectionProfile;
|
|
1628
1764
|
if (profile && configuredMcpConnectionId && !connectionProfile && options?.repairConnection !== false) {
|
|
@@ -1635,6 +1771,38 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1635
1771
|
}
|
|
1636
1772
|
return profile;
|
|
1637
1773
|
}
|
|
1774
|
+
function listSelectableOAuthProfiles(profileName) {
|
|
1775
|
+
if (auditActorType !== 'oauth')
|
|
1776
|
+
return [];
|
|
1777
|
+
const requestedName = String(profileName || '').trim().toLowerCase();
|
|
1778
|
+
if (!requestedName)
|
|
1779
|
+
return [];
|
|
1780
|
+
const requestedUsername = normalizeConfiguredAiProfileUsername(undefined, requestedName).toLowerCase();
|
|
1781
|
+
const restorableProfileIds = !configuredMcpClientId && auditTokenId
|
|
1782
|
+
? new Set(core.listMcpOAuthRestorableAiProfileIds({
|
|
1783
|
+
grantId: auditTokenId,
|
|
1784
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1785
|
+
}))
|
|
1786
|
+
: null;
|
|
1787
|
+
return core.listAiProfiles(ACTIVE_WORKSPACE_ID).filter((profile) => {
|
|
1788
|
+
if ((profile.seatScope ?? null) !== configuredAiProfileSeatScope)
|
|
1789
|
+
return false;
|
|
1790
|
+
const profileNameMatches = profile.name.trim().toLowerCase() === requestedName
|
|
1791
|
+
|| profile.username.trim().toLowerCase() === requestedUsername;
|
|
1792
|
+
if (!profileNameMatches)
|
|
1793
|
+
return false;
|
|
1794
|
+
const connection = profile.providerMetadata?.connection;
|
|
1795
|
+
const storedClientId = connection && typeof connection === 'object' && !Array.isArray(connection)
|
|
1796
|
+
? parseMcpClientId(connection.clientId)
|
|
1797
|
+
: null;
|
|
1798
|
+
const inferredClientId = inferLegacyMcpClientIdFromProfile(profile);
|
|
1799
|
+
if (!configuredMcpClientId)
|
|
1800
|
+
return restorableProfileIds?.has(profile.id) === true;
|
|
1801
|
+
return storedClientId
|
|
1802
|
+
? storedClientId === configuredMcpClientId
|
|
1803
|
+
: inferredClientId === configuredMcpClientId;
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1638
1806
|
function getEligibleAssigneeActors() {
|
|
1639
1807
|
return core.listAssignableActors(ACTIVE_WORKSPACE_ID)
|
|
1640
1808
|
.filter((actor) => String(actor.id || '').trim().toLowerCase() !== 'agent')
|
|
@@ -1646,7 +1814,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1646
1814
|
.filter((actor) => Boolean(actor.id));
|
|
1647
1815
|
}
|
|
1648
1816
|
function describeEligibleAssignees() {
|
|
1649
|
-
return "Use 'self'
|
|
1817
|
+
return "Use 'self', 'unassigned', or an ID from list_assignees.";
|
|
1650
1818
|
}
|
|
1651
1819
|
function buildEligibleAssigneePayload() {
|
|
1652
1820
|
return core.listAssignableActors(ACTIVE_WORKSPACE_ID)
|
|
@@ -2215,8 +2383,14 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2215
2383
|
function ensureResolvedProfileFromConnection(defaults) {
|
|
2216
2384
|
if (activeAiProfileId)
|
|
2217
2385
|
return;
|
|
2218
|
-
const boundProfile = findTrustedBoundProfile();
|
|
2386
|
+
const boundProfile = findTrustedBoundProfile({ repairConnection: false });
|
|
2219
2387
|
if (boundProfile) {
|
|
2388
|
+
if (configuredAiProfileIdentityAsserted) {
|
|
2389
|
+
assertRequestedIdentityMatchesBoundProfile(boundProfile, {
|
|
2390
|
+
name: configuredAiProfileName,
|
|
2391
|
+
provider: configuredAiProfileProvider,
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2220
2394
|
const configuredClientEntry = getMcpClientRegistryEntry(configuredMcpClientId);
|
|
2221
2395
|
const resolved = core.resolveAiProfile({
|
|
2222
2396
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -2279,6 +2453,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2279
2453
|
return;
|
|
2280
2454
|
if (!isMcpWriteToolName(name))
|
|
2281
2455
|
return;
|
|
2456
|
+
if (aiProfileIdentityConflictDetected) {
|
|
2457
|
+
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
|
+
}
|
|
2282
2459
|
const profileToken = resolveProfileTokenFromArgs(args);
|
|
2283
2460
|
if (activeAiProfileId) {
|
|
2284
2461
|
if (!profileToken)
|
|
@@ -2292,7 +2469,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2292
2469
|
throw new McpPublicError('Invalid AI profile token. Call resolve_profile again and retry.', 'INVALID_AI_PROFILE_TOKEN', 401);
|
|
2293
2470
|
}
|
|
2294
2471
|
if (tokenProfile.id !== activeAiProfileId) {
|
|
2295
|
-
|
|
2472
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already bound to this MCP session.', { reason: 'session_token_mismatch', boundProfileId: activeAiProfileId, suppliedProfileId: tokenProfile.id });
|
|
2296
2473
|
}
|
|
2297
2474
|
return;
|
|
2298
2475
|
}
|
|
@@ -2306,6 +2483,20 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2306
2483
|
seatScope: configuredAiProfileSeatScope,
|
|
2307
2484
|
});
|
|
2308
2485
|
if (profile) {
|
|
2486
|
+
const trustedBoundProfile = findTrustedBoundProfile({ repairConnection: false });
|
|
2487
|
+
if (trustedBoundProfile && trustedBoundProfile.id !== profile.id) {
|
|
2488
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the profile already bound to this OAuth grant or MCP connection.', {
|
|
2489
|
+
reason: 'stateless_token_mismatch',
|
|
2490
|
+
boundProfileId: trustedBoundProfile.id,
|
|
2491
|
+
suppliedProfileId: profile.id,
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
if (trustedBoundProfile && configuredAiProfileIdentityAsserted) {
|
|
2495
|
+
assertRequestedIdentityMatchesBoundProfile(trustedBoundProfile, {
|
|
2496
|
+
name: configuredAiProfileName,
|
|
2497
|
+
provider: configuredAiProfileProvider,
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2309
2500
|
activateAiProfile(profile.id, 'token_lookup');
|
|
2310
2501
|
return;
|
|
2311
2502
|
}
|
|
@@ -3049,15 +3240,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3049
3240
|
|| planningSearchIncludes(initiative?.referenceLabel || formatInitiativeReference(initiative?.referenceNumber), query)
|
|
3050
3241
|
|| planningSearchIncludes(initiative?.title, query);
|
|
3051
3242
|
}
|
|
3052
|
-
function buildHistoricalWorkspaceTaskSnapshot() {
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
byId.set(task.id, task);
|
|
3058
|
-
for (const task of archivedTasks)
|
|
3059
|
-
byId.set(task.id, task);
|
|
3060
|
-
const tasks = Array.from(byId.values());
|
|
3243
|
+
function buildHistoricalWorkspaceTaskSnapshot(workstreamId) {
|
|
3244
|
+
const tasks = core.listTasksSlim(ACTIVE_WORKSPACE_ID, {
|
|
3245
|
+
...(workstreamId ? { workstreamId } : {}),
|
|
3246
|
+
includeArchived: true,
|
|
3247
|
+
});
|
|
3061
3248
|
const tasksByWorkstreamId = new Map();
|
|
3062
3249
|
for (const task of tasks) {
|
|
3063
3250
|
const normalizedWorkstreamId = String(task.workstreamId || '').trim();
|
|
@@ -3090,6 +3277,34 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3090
3277
|
semantics: 'historical: active + archived',
|
|
3091
3278
|
};
|
|
3092
3279
|
}
|
|
3280
|
+
function emptyPlanningTaskCounts() {
|
|
3281
|
+
return buildPlanningTaskCounts([]);
|
|
3282
|
+
}
|
|
3283
|
+
function buildHistoricalTaskCountSnapshot(workstreamId) {
|
|
3284
|
+
return new Map(core.listTaskCountsByWorkstream(ACTIVE_WORKSPACE_ID, workstreamId).map((entry) => [
|
|
3285
|
+
entry.workstreamId,
|
|
3286
|
+
{
|
|
3287
|
+
total: entry.total,
|
|
3288
|
+
active: entry.active,
|
|
3289
|
+
archived: entry.archived,
|
|
3290
|
+
semantics: 'historical: active + archived',
|
|
3291
|
+
},
|
|
3292
|
+
]));
|
|
3293
|
+
}
|
|
3294
|
+
function sumPlanningTaskCounts(workstreams, countsByWorkstreamId) {
|
|
3295
|
+
let total = 0;
|
|
3296
|
+
let active = 0;
|
|
3297
|
+
let archived = 0;
|
|
3298
|
+
for (const workstream of workstreams) {
|
|
3299
|
+
const counts = countsByWorkstreamId.get(workstream.id);
|
|
3300
|
+
if (!counts)
|
|
3301
|
+
continue;
|
|
3302
|
+
total += counts.total;
|
|
3303
|
+
active += counts.active;
|
|
3304
|
+
archived += counts.archived;
|
|
3305
|
+
}
|
|
3306
|
+
return { total, active, archived, semantics: 'historical: active + archived' };
|
|
3307
|
+
}
|
|
3093
3308
|
function buildPlanningWorkstreamCounts(workstreams) {
|
|
3094
3309
|
const active = workstreams.filter((workstream) => !workstream?.isArchived).length;
|
|
3095
3310
|
const archived = workstreams.length - active;
|
|
@@ -3192,16 +3407,19 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3192
3407
|
};
|
|
3193
3408
|
}
|
|
3194
3409
|
function buildInitiativeResourcePayload(initiative, options) {
|
|
3195
|
-
|
|
3410
|
+
if (options?.detail === 'history') {
|
|
3411
|
+
return buildPlanningHistoryPayload('initiative', initiative, options.offset ?? 0, options.limit ?? 25);
|
|
3412
|
+
}
|
|
3196
3413
|
const workstreams = options?.workstreamsByInitiativeId?.get(initiative.id)
|
|
3197
3414
|
|| core.listWorkstreams(ACTIVE_WORKSPACE_ID)
|
|
3198
3415
|
.filter((workstream) => workstream.initiativeId === initiative.id);
|
|
3199
|
-
const
|
|
3416
|
+
const taskCountsByWorkstreamId = options?.taskCountsByWorkstreamId || buildHistoricalTaskCountSnapshot();
|
|
3417
|
+
const taskCounts = sumPlanningTaskCounts(workstreams, taskCountsByWorkstreamId);
|
|
3200
3418
|
if (options?.detail === 'compact') {
|
|
3201
3419
|
return {
|
|
3202
3420
|
initiative: buildPlanningInitiativeSummary(initiative),
|
|
3203
3421
|
workstreamCount: workstreams.length,
|
|
3204
|
-
taskCount:
|
|
3422
|
+
taskCount: taskCounts.total,
|
|
3205
3423
|
nextDetailOption: 'full',
|
|
3206
3424
|
};
|
|
3207
3425
|
}
|
|
@@ -3215,13 +3433,12 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3215
3433
|
initiative: withPlanningEntityUrl('initiative', buildPlanningEntityMetadata('initiative', initiative)),
|
|
3216
3434
|
attachments: listPlanningAttachmentDescriptors({ id: initiative.id, entity: initiative }),
|
|
3217
3435
|
workstreamCounts: buildPlanningWorkstreamCounts(workstreams),
|
|
3218
|
-
taskCounts
|
|
3436
|
+
taskCounts,
|
|
3219
3437
|
workstreams: page.map((workstream) => {
|
|
3220
|
-
const workstreamTasks = tasksByWorkstreamId.get(workstream.id) || [];
|
|
3221
3438
|
return {
|
|
3222
3439
|
workstream: buildPlanningWorkstreamSummary(workstream),
|
|
3223
3440
|
attachments: listPlanningAttachmentDescriptors({ id: workstream.id, entity: workstream }),
|
|
3224
|
-
taskCounts:
|
|
3441
|
+
taskCounts: taskCountsByWorkstreamId.get(workstream.id) || emptyPlanningTaskCounts(),
|
|
3225
3442
|
};
|
|
3226
3443
|
}),
|
|
3227
3444
|
pagination,
|
|
@@ -3237,27 +3454,38 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3237
3454
|
} : { truncated: false }),
|
|
3238
3455
|
};
|
|
3239
3456
|
}
|
|
3240
|
-
if (options?.detail === 'history') {
|
|
3241
|
-
return buildPlanningHistoryPayload('initiative', initiative, options.offset ?? 0, options.limit ?? 25);
|
|
3242
|
-
}
|
|
3243
3457
|
return {
|
|
3244
3458
|
initiative: buildPlanningInitiativeDetail(initiative),
|
|
3245
3459
|
workstreamCount: workstreams.length,
|
|
3246
|
-
taskCount:
|
|
3460
|
+
taskCount: taskCounts.total,
|
|
3247
3461
|
workstreams: workstreams.map((workstream) => buildPlanningWorkstreamSummary(workstream)),
|
|
3248
3462
|
};
|
|
3249
3463
|
}
|
|
3250
3464
|
function buildWorkstreamResourcePayload(workstream, options) {
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3465
|
+
if (options?.detail === 'history') {
|
|
3466
|
+
return buildPlanningHistoryPayload('workstream', workstream, options.offset ?? 0, options.limit ?? 25);
|
|
3467
|
+
}
|
|
3254
3468
|
const workstreamReference = typeof workstream.referenceLabel === 'string'
|
|
3255
3469
|
? workstream.referenceLabel
|
|
3256
3470
|
: formatWorkstreamReference(workstream);
|
|
3471
|
+
if (options?.detail !== 'compact' && options?.detail !== 'planning') {
|
|
3472
|
+
const tasksByWorkstreamId = options?.tasksByWorkstreamId
|
|
3473
|
+
|| buildHistoricalWorkspaceTaskSnapshot(workstream.id).tasksByWorkstreamId;
|
|
3474
|
+
const tasks = tasksByWorkstreamId.get(workstream.id) || [];
|
|
3475
|
+
return {
|
|
3476
|
+
workstream: buildPlanningWorkstreamDetail(workstream),
|
|
3477
|
+
taskCount: tasks.length,
|
|
3478
|
+
tasks,
|
|
3479
|
+
nextTaskTool: 'list_tasks',
|
|
3480
|
+
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
const taskCounts = (options?.taskCountsByWorkstreamId || buildHistoricalTaskCountSnapshot(workstream.id))
|
|
3484
|
+
.get(workstream.id) || emptyPlanningTaskCounts();
|
|
3257
3485
|
if (options?.detail === 'compact') {
|
|
3258
3486
|
return {
|
|
3259
3487
|
workstream: buildPlanningWorkstreamSummary(workstream),
|
|
3260
|
-
taskCount:
|
|
3488
|
+
taskCount: taskCounts.total,
|
|
3261
3489
|
nextDetailOption: 'full',
|
|
3262
3490
|
nextTaskTool: 'list_tasks',
|
|
3263
3491
|
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
@@ -3267,22 +3495,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3267
3495
|
return {
|
|
3268
3496
|
workstream: withPlanningEntityUrl('workstream', buildPlanningEntityMetadata('workstream', workstream)),
|
|
3269
3497
|
attachments: listPlanningAttachmentDescriptors({ id: workstream.id, entity: workstream }),
|
|
3270
|
-
taskCounts
|
|
3498
|
+
taskCounts,
|
|
3271
3499
|
nextTaskTool: 'list_tasks',
|
|
3272
3500
|
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3273
3501
|
guidance: 'taskCounts are historical (active + archived). list_tasks returns bounded active task rows; use search_tasks with scope=archive for archived task discovery.',
|
|
3274
3502
|
};
|
|
3275
3503
|
}
|
|
3276
|
-
|
|
3277
|
-
return buildPlanningHistoryPayload('workstream', workstream, options.offset ?? 0, options.limit ?? 25);
|
|
3278
|
-
}
|
|
3279
|
-
return {
|
|
3280
|
-
workstream: buildPlanningWorkstreamDetail(workstream),
|
|
3281
|
-
taskCount: tasks.length,
|
|
3282
|
-
tasks,
|
|
3283
|
-
nextTaskTool: 'list_tasks',
|
|
3284
|
-
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3285
|
-
};
|
|
3504
|
+
throw new Error(`Unhandled workstream detail: ${options?.detail}`);
|
|
3286
3505
|
}
|
|
3287
3506
|
function buildInitiativesResourcePayload(options) {
|
|
3288
3507
|
const query = normalizePlanningSearchQuery(options?.query);
|
|
@@ -3296,12 +3515,12 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3296
3515
|
current.push(workstream);
|
|
3297
3516
|
workstreamsByInitiativeId.set(normalizedInitiativeId, current);
|
|
3298
3517
|
}
|
|
3299
|
-
const
|
|
3518
|
+
const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
|
|
3300
3519
|
const initiatives = core.listInitiatives(ACTIVE_WORKSPACE_ID)
|
|
3301
3520
|
.filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []))
|
|
3302
3521
|
.map((initiative) => buildInitiativeResourcePayload(initiative, {
|
|
3303
3522
|
workstreamsByInitiativeId,
|
|
3304
|
-
|
|
3523
|
+
taskCountsByWorkstreamId,
|
|
3305
3524
|
detail: 'compact',
|
|
3306
3525
|
}));
|
|
3307
3526
|
return {
|
|
@@ -3316,10 +3535,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3316
3535
|
for (const initiative of core.listInitiatives(ACTIVE_WORKSPACE_ID)) {
|
|
3317
3536
|
initiativesById.set(initiative.id, initiative);
|
|
3318
3537
|
}
|
|
3319
|
-
const
|
|
3538
|
+
const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
|
|
3320
3539
|
const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
|
|
3321
3540
|
.filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')))
|
|
3322
|
-
.map((workstream) => buildWorkstreamResourcePayload(workstream, {
|
|
3541
|
+
.map((workstream) => buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' }));
|
|
3323
3542
|
return {
|
|
3324
3543
|
total: workstreams.length,
|
|
3325
3544
|
...(query ? { query } : {}),
|
|
@@ -3631,6 +3850,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3631
3850
|
'add_comment',
|
|
3632
3851
|
'add_document_review_comment',
|
|
3633
3852
|
'add_initiative_comment',
|
|
3853
|
+
'add_task_checklist_item',
|
|
3634
3854
|
'add_workstream_comment',
|
|
3635
3855
|
'bulk_add_annotated_attachment_markers',
|
|
3636
3856
|
'create_annotated_attachment_session',
|
|
@@ -3639,6 +3859,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3639
3859
|
'create_task',
|
|
3640
3860
|
'create_workstream',
|
|
3641
3861
|
'link_document',
|
|
3862
|
+
'link_task_relationship',
|
|
3642
3863
|
'rehome_document',
|
|
3643
3864
|
'link_task_context',
|
|
3644
3865
|
'resolve_profile',
|
|
@@ -3662,7 +3883,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3662
3883
|
get assigneeHint() {
|
|
3663
3884
|
return describeEligibleAssignees();
|
|
3664
3885
|
},
|
|
3665
|
-
describeTool: (text) =>
|
|
3886
|
+
describeTool: (text) => text,
|
|
3666
3887
|
};
|
|
3667
3888
|
};
|
|
3668
3889
|
const titleForTool = (name) => String(name || '')
|
|
@@ -4529,6 +4750,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4529
4750
|
uploadDraftId: result.uploadDraftId,
|
|
4530
4751
|
mimeType: result.mimeType,
|
|
4531
4752
|
uploadedPath: result.uploadedPath,
|
|
4753
|
+
...(result.displayName ? { displayName: result.displayName } : {}),
|
|
4532
4754
|
...(result.caption ? { caption: result.caption } : {}),
|
|
4533
4755
|
...(result.overwrite ? { overwrite: true } : {}),
|
|
4534
4756
|
...(result.expectedSizeBytes !== undefined ? { expectedSizeBytes: result.expectedSizeBytes } : {}),
|
|
@@ -4827,8 +5049,12 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4827
5049
|
};
|
|
4828
5050
|
}
|
|
4829
5051
|
case "resolve_profile": {
|
|
4830
|
-
const { name: profileName, icon, color, description, role, provider, model, surfaceType, profileToken } = args;
|
|
5052
|
+
const { name: profileName, icon, color, description, role, provider, model, surfaceType, profileId, profileToken } = args;
|
|
5053
|
+
if (aiProfileIdentityConflictDetected) {
|
|
5054
|
+
throw new TaskforceRuleError('This MCP session is quarantined after a profile identity conflict. Reconnect with the intended profile before retrying.', 409, 'AI_PROFILE_BINDING_CONFLICT', { reason: 'session_identity_quarantined', nextTool: 'resolve_profile' });
|
|
5055
|
+
}
|
|
4831
5056
|
const suppliedProfileToken = typeof profileToken === 'string' ? profileToken.trim() : '';
|
|
5057
|
+
const selectedProfileId = typeof profileId === 'string' ? profileId.trim() : '';
|
|
4832
5058
|
const sessionBoundProfile = activeAiProfileId
|
|
4833
5059
|
? core.listAiProfiles(ACTIVE_WORKSPACE_ID, { includeArchived: true })
|
|
4834
5060
|
.find((profile) => profile.id === activeAiProfileId) || null
|
|
@@ -4841,9 +5067,26 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4841
5067
|
repairConnection: false,
|
|
4842
5068
|
});
|
|
4843
5069
|
if (sessionBoundProfile && trustedBoundProfile && sessionBoundProfile.id !== trustedBoundProfile.id) {
|
|
4844
|
-
|
|
5070
|
+
failAiProfileBindingConflict('The MCP session and authenticated connection are bound to different AI profiles. Reconnect before retrying.', { reason: 'session_connection_mismatch', sessionProfileId: sessionBoundProfile.id, trustedProfileId: trustedBoundProfile.id });
|
|
5071
|
+
}
|
|
5072
|
+
const selectableProfiles = listSelectableOAuthProfiles(profileName);
|
|
5073
|
+
const explicitlySelectedProfile = selectedProfileId
|
|
5074
|
+
? selectableProfiles.find((profile) => profile.id === selectedProfileId) || null
|
|
5075
|
+
: null;
|
|
5076
|
+
if (selectedProfileId && !explicitlySelectedProfile) {
|
|
5077
|
+
throw new TaskforceRuleError('The selected AI profile is not an active compatible identity for this OAuth client and requested name.', 409, 'AI_PROFILE_SELECTION_INVALID', { selectedProfileId });
|
|
5078
|
+
}
|
|
5079
|
+
const alreadyBoundProfile = sessionBoundProfile || trustedBoundProfile;
|
|
5080
|
+
if (alreadyBoundProfile) {
|
|
5081
|
+
assertRequestedIdentityMatchesBoundProfile(alreadyBoundProfile, {
|
|
5082
|
+
name: profileName,
|
|
5083
|
+
provider,
|
|
5084
|
+
});
|
|
4845
5085
|
}
|
|
4846
|
-
|
|
5086
|
+
if (alreadyBoundProfile && explicitlySelectedProfile && alreadyBoundProfile.id !== explicitlySelectedProfile.id) {
|
|
5087
|
+
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
|
+
}
|
|
5089
|
+
const boundProfile = alreadyBoundProfile || explicitlySelectedProfile;
|
|
4847
5090
|
if (boundProfile && suppliedProfileToken) {
|
|
4848
5091
|
const tokenProfile = core.getAiProfileByToken({
|
|
4849
5092
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -4851,7 +5094,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4851
5094
|
seatScope: configuredAiProfileSeatScope,
|
|
4852
5095
|
});
|
|
4853
5096
|
if (!tokenProfile || tokenProfile.id !== boundProfile.id) {
|
|
4854
|
-
|
|
5097
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already bound to this MCP session or authenticated connection.', { reason: 'bound_token_mismatch', boundProfileId: boundProfile.id, ...(tokenProfile ? { suppliedProfileId: tokenProfile.id } : {}) });
|
|
4855
5098
|
}
|
|
4856
5099
|
}
|
|
4857
5100
|
if (!boundProfile && !suppliedProfileToken && auditActorType === 'oauth') {
|
|
@@ -4860,7 +5103,16 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4860
5103
|
const hasIdentityCandidate = core.listAiProfiles(ACTIVE_WORKSPACE_ID).some((profile) => ((profile.seatScope ?? null) === configuredAiProfileSeatScope
|
|
4861
5104
|
&& (profile.name.trim().toLowerCase() === requestedName || profile.username.trim().toLowerCase() === requestedUsername)));
|
|
4862
5105
|
if (hasIdentityCandidate) {
|
|
4863
|
-
|
|
5106
|
+
const candidates = selectableProfiles.map((profile) => buildAiProfileDescriptor(profile));
|
|
5107
|
+
const suggestedArgs = candidates.length === 1
|
|
5108
|
+
? { name: profileName, profileId: candidates[0].id }
|
|
5109
|
+
: undefined;
|
|
5110
|
+
throw new TaskforceRuleError(candidates.length > 0
|
|
5111
|
+
? 'An AI profile with this identity already exists but is not bound to this OAuth client. Select a compatible candidate explicitly and retry resolve_profile.'
|
|
5112
|
+
: 'An AI profile with this identity already exists but is not compatible with this OAuth client. Select the intended profile in Taskforce settings before retrying.', 409, 'AI_PROFILE_SELECTION_REQUIRED', {
|
|
5113
|
+
candidates,
|
|
5114
|
+
...(suggestedArgs ? { suggestedArgs } : {}),
|
|
5115
|
+
});
|
|
4864
5116
|
}
|
|
4865
5117
|
}
|
|
4866
5118
|
const configuredClientEntry = getMcpClientRegistryEntry(configuredMcpClientId);
|
|
@@ -5040,7 +5292,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5040
5292
|
}
|
|
5041
5293
|
case "search_tasks": {
|
|
5042
5294
|
const { query, scope = "all" } = args;
|
|
5043
|
-
const results = core.
|
|
5295
|
+
const results = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID);
|
|
5044
5296
|
return {
|
|
5045
5297
|
content: [{ type: "text", text: renderTaskTable(results) }],
|
|
5046
5298
|
};
|
|
@@ -5200,9 +5452,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5200
5452
|
case "list_task_relationships": {
|
|
5201
5453
|
const { taskId, type, direction = 'both' } = args;
|
|
5202
5454
|
const { id } = resolveTaskByIdentifierOrThrow(taskId);
|
|
5203
|
-
const
|
|
5204
|
-
|
|
5205
|
-
|
|
5455
|
+
const relationshipRows = core.listTaskRelationships(id, ACTIVE_WORKSPACE_ID, { type, direction });
|
|
5456
|
+
const taskSummariesById = new Map(core.listTaskReferenceSummaries(relationshipRows.flatMap((relationship) => [relationship.sourceTaskId, relationship.targetTaskId]), ACTIVE_WORKSPACE_ID).map((task) => [task.id, task]));
|
|
5457
|
+
const relationships = relationshipRows.map((relationship) => {
|
|
5458
|
+
const source = taskSummariesById.get(relationship.sourceTaskId) || null;
|
|
5459
|
+
const target = taskSummariesById.get(relationship.targetTaskId) || null;
|
|
5206
5460
|
const perspectiveType = relationship.kind === 'dependency'
|
|
5207
5461
|
? (relationship.sourceTaskId === id ? 'depends_on' : 'blocks')
|
|
5208
5462
|
: relationship.kind === 'related' ? 'related_to' : 'supersedes';
|
|
@@ -5367,12 +5621,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5367
5621
|
return { content: [{ type: 'text', text: JSON.stringify(linkedResult, null, 2) }] };
|
|
5368
5622
|
}
|
|
5369
5623
|
case "create_initiative": {
|
|
5370
|
-
const
|
|
5624
|
+
const { idempotencyKey: rawIdempotencyKey, ...initiativeArgs } = args;
|
|
5625
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5626
|
+
const operationId = idempotencyKey
|
|
5627
|
+
? stableExplicitMutationId('operation', 'create_initiative', idempotencyKey)
|
|
5628
|
+
: undefined;
|
|
5629
|
+
const router = getDurableInitiativeMutationRouter(operationId ? { operationId } : undefined);
|
|
5630
|
+
if (idempotencyKey)
|
|
5631
|
+
requireDurableExplicitIdempotency(router.enabled, 'initiative');
|
|
5371
5632
|
let prepared;
|
|
5372
5633
|
try {
|
|
5373
|
-
prepared = planningCommandPreparationService.prepareInitiativeCreate(
|
|
5634
|
+
prepared = planningCommandPreparationService.prepareInitiativeCreate(initiativeArgs, {
|
|
5374
5635
|
durable: router.enabled,
|
|
5375
|
-
entityId:
|
|
5636
|
+
entityId: idempotencyKey
|
|
5637
|
+
? stableExplicitMutationId('initiative', 'create_initiative', idempotencyKey)
|
|
5638
|
+
: requireStableMcpRequestId('initiative', 'create'),
|
|
5376
5639
|
createdAt: new Date().toISOString(),
|
|
5377
5640
|
});
|
|
5378
5641
|
}
|
|
@@ -5421,14 +5684,23 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5421
5684
|
};
|
|
5422
5685
|
}
|
|
5423
5686
|
case "add_initiative_comment": {
|
|
5424
|
-
const { id, text } = args;
|
|
5687
|
+
const { id, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
5425
5688
|
const { id: initiativeId } = resolveInitiativeByIdentifierOrThrow(id);
|
|
5426
5689
|
const validation = validateStructuredCommentForAdd(text);
|
|
5427
5690
|
if (!validation.ok) {
|
|
5428
5691
|
throw new McpPublicError(validation.message);
|
|
5429
5692
|
}
|
|
5430
5693
|
const effectiveAuthor = getActiveMcpActorRef();
|
|
5431
|
-
const
|
|
5694
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5695
|
+
const explicitCommentId = idempotencyKey
|
|
5696
|
+
? stableExplicitMutationId('comment', 'add_initiative_comment', idempotencyKey)
|
|
5697
|
+
: null;
|
|
5698
|
+
const router = getDurableInitiativeMutationRouter(idempotencyKey ? {
|
|
5699
|
+
operationId: stableExplicitMutationId('operation', 'add_initiative_comment', idempotencyKey),
|
|
5700
|
+
commentId: explicitCommentId,
|
|
5701
|
+
} : undefined);
|
|
5702
|
+
if (idempotencyKey)
|
|
5703
|
+
requireDurableExplicitIdempotency(router.enabled, 'initiative comment');
|
|
5432
5704
|
const updated = router.enabled
|
|
5433
5705
|
? initiativeFromDurableResult(router.appendComment(initiativeId, {
|
|
5434
5706
|
author: effectiveAuthor,
|
|
@@ -5440,7 +5712,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5440
5712
|
if (!updated) {
|
|
5441
5713
|
throw new McpPublicError(buildInitiativeLookupFailureMessage(String(id || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5442
5714
|
}
|
|
5443
|
-
const persistedComment = Array.isArray(updated.comments)
|
|
5715
|
+
const persistedComment = Array.isArray(updated.comments)
|
|
5716
|
+
? (explicitCommentId
|
|
5717
|
+
? updated.comments.find((candidate) => candidate.id === explicitCommentId) || null
|
|
5718
|
+
: updated.comments.at(-1) || null)
|
|
5719
|
+
: null;
|
|
5444
5720
|
recordMcpWriteAudit('add_initiative_comment', {
|
|
5445
5721
|
entityType: 'initiative',
|
|
5446
5722
|
initiativeId: updated.id,
|
|
@@ -5454,7 +5730,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5454
5730
|
return {
|
|
5455
5731
|
content: [{ type: "text", text: JSON.stringify(buildPlanningWriteAcknowledgement('initiative', updated, 'add_initiative_comment', {
|
|
5456
5732
|
changedFields: ['comments'],
|
|
5457
|
-
comment:
|
|
5733
|
+
comment: persistedComment,
|
|
5458
5734
|
}), null, 2) }],
|
|
5459
5735
|
};
|
|
5460
5736
|
}
|
|
@@ -5515,12 +5791,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5515
5791
|
};
|
|
5516
5792
|
}
|
|
5517
5793
|
case "create_workstream": {
|
|
5518
|
-
const
|
|
5794
|
+
const { idempotencyKey: rawIdempotencyKey, ...workstreamArgs } = args;
|
|
5795
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5796
|
+
const operationId = idempotencyKey
|
|
5797
|
+
? stableExplicitMutationId('operation', 'create_workstream', idempotencyKey)
|
|
5798
|
+
: undefined;
|
|
5799
|
+
const router = getDurableWorkstreamMutationRouter(operationId ? { operationId } : undefined);
|
|
5800
|
+
if (idempotencyKey)
|
|
5801
|
+
requireDurableExplicitIdempotency(router.enabled, 'workstream');
|
|
5519
5802
|
let prepared;
|
|
5520
5803
|
try {
|
|
5521
|
-
prepared = planningCommandPreparationService.prepareWorkstreamCreate(
|
|
5804
|
+
prepared = planningCommandPreparationService.prepareWorkstreamCreate(workstreamArgs, {
|
|
5522
5805
|
durable: router.enabled,
|
|
5523
|
-
entityId:
|
|
5806
|
+
entityId: idempotencyKey
|
|
5807
|
+
? stableExplicitMutationId('workstream', 'create_workstream', idempotencyKey)
|
|
5808
|
+
: requireStableMcpRequestId('workstream', 'create'),
|
|
5524
5809
|
createdAt: new Date().toISOString(),
|
|
5525
5810
|
});
|
|
5526
5811
|
}
|
|
@@ -5576,43 +5861,64 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5576
5861
|
};
|
|
5577
5862
|
}
|
|
5578
5863
|
case "add_workstream_comment": {
|
|
5579
|
-
const { id, text } = args;
|
|
5580
|
-
const { id: workstreamId } = resolveWorkstreamByIdentifierOrThrow(id);
|
|
5581
|
-
const
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5864
|
+
const { id, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
5865
|
+
const { id: workstreamId } = measureToolSubphase('lookup', () => resolveWorkstreamByIdentifierOrThrow(id));
|
|
5866
|
+
const { effectiveAuthor, router, comment, explicitCommentId } = measureToolSubphase('prepare', () => {
|
|
5867
|
+
const validation = validateStructuredCommentForAdd(text);
|
|
5868
|
+
if (!validation.ok) {
|
|
5869
|
+
throw new McpPublicError(validation.message);
|
|
5870
|
+
}
|
|
5871
|
+
const effectiveAuthor = getActiveMcpActorRef();
|
|
5872
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5873
|
+
const explicitCommentId = idempotencyKey
|
|
5874
|
+
? stableExplicitMutationId('comment', 'add_workstream_comment', idempotencyKey)
|
|
5875
|
+
: null;
|
|
5876
|
+
const router = getDurableWorkstreamMutationRouter(idempotencyKey ? {
|
|
5877
|
+
operationId: stableExplicitMutationId('operation', 'add_workstream_comment', idempotencyKey),
|
|
5878
|
+
commentId: explicitCommentId,
|
|
5879
|
+
} : undefined);
|
|
5880
|
+
if (idempotencyKey)
|
|
5881
|
+
requireDurableExplicitIdempotency(router.enabled, 'workstream comment');
|
|
5882
|
+
return {
|
|
5883
|
+
effectiveAuthor,
|
|
5884
|
+
router,
|
|
5885
|
+
explicitCommentId,
|
|
5886
|
+
comment: {
|
|
5887
|
+
author: effectiveAuthor,
|
|
5888
|
+
submittedAuthor: effectiveAuthor,
|
|
5889
|
+
text,
|
|
5890
|
+
timestamp: new Date().toISOString(),
|
|
5891
|
+
},
|
|
5892
|
+
};
|
|
5893
|
+
});
|
|
5894
|
+
const updated = measureToolSubphase('durable-write', () => router.enabled
|
|
5895
|
+
? workstreamFromDurableResult(router.appendComment(workstreamId, comment))
|
|
5896
|
+
: core.addWorkstreamComment(workstreamId, text, effectiveAuthor, ACTIVE_WORKSPACE_ID, { actorRef: effectiveAuthor }));
|
|
5595
5897
|
if (!updated) {
|
|
5596
5898
|
throw new McpPublicError(buildWorkstreamLookupFailureMessage(String(id || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5597
5899
|
}
|
|
5598
|
-
const persistedComment = Array.isArray(updated.comments)
|
|
5599
|
-
|
|
5900
|
+
const persistedComment = Array.isArray(updated.comments)
|
|
5901
|
+
? (explicitCommentId
|
|
5902
|
+
? updated.comments.find((candidate) => candidate.id === explicitCommentId) || null
|
|
5903
|
+
: updated.comments.at(-1) || null)
|
|
5904
|
+
: null;
|
|
5905
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('add_workstream_comment', {
|
|
5600
5906
|
entityType: 'workstream',
|
|
5601
5907
|
workstreamId: updated.id,
|
|
5602
5908
|
commentId: persistedComment?.id || null,
|
|
5603
5909
|
contentActorId: effectiveAuthor,
|
|
5604
|
-
});
|
|
5605
|
-
await notifyResourceMutation({
|
|
5910
|
+
}));
|
|
5911
|
+
await measureToolSubphaseAsync('notify', () => notifyResourceMutation({
|
|
5606
5912
|
includeWorkstreams: true,
|
|
5607
5913
|
workstreamIds: [updated.id],
|
|
5608
5914
|
...(updated.initiativeId ? { includeInitiatives: true, initiativeIds: [updated.initiativeId] } : {}),
|
|
5609
|
-
});
|
|
5610
|
-
return {
|
|
5915
|
+
}));
|
|
5916
|
+
return measureToolSubphase('serialize', () => ({
|
|
5611
5917
|
content: [{ type: "text", text: JSON.stringify(buildPlanningWriteAcknowledgement('workstream', updated, 'add_workstream_comment', {
|
|
5612
5918
|
changedFields: ['comments'],
|
|
5613
|
-
comment:
|
|
5919
|
+
comment: persistedComment,
|
|
5614
5920
|
}), null, 2) }],
|
|
5615
|
-
};
|
|
5921
|
+
}));
|
|
5616
5922
|
}
|
|
5617
5923
|
case "archive_workstream": {
|
|
5618
5924
|
const { id } = args;
|
|
@@ -5655,34 +5961,54 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5655
5961
|
case "update_task_workstream": {
|
|
5656
5962
|
const { id: legacyInputId, taskId, workstreamId } = args;
|
|
5657
5963
|
const inputId = taskId ?? legacyInputId;
|
|
5658
|
-
const { task } = resolveTaskByIdentifierOrThrow(inputId);
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5964
|
+
const { task } = measureToolSubphase('lookup', () => resolveTaskByIdentifierOrThrow(inputId));
|
|
5965
|
+
const prepared = measureToolSubphase('prepare', () => {
|
|
5966
|
+
try {
|
|
5967
|
+
return planningCommandPreparationService.prepareTaskWorkstreamPlacement(task, workstreamId);
|
|
5968
|
+
}
|
|
5969
|
+
catch (error) {
|
|
5970
|
+
return rethrowPlanningPreparationError(error);
|
|
5971
|
+
}
|
|
5972
|
+
});
|
|
5973
|
+
if (prepared.nextWorkstreamId === prepared.previousWorkstreamId) {
|
|
5974
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('update_task_workstream', {
|
|
5975
|
+
taskId: task.id,
|
|
5976
|
+
workstreamId: prepared.nextWorkstreamId,
|
|
5977
|
+
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5978
|
+
noOp: true,
|
|
5979
|
+
}));
|
|
5980
|
+
return measureToolSubphase('serialize', () => ({
|
|
5981
|
+
content: [{
|
|
5982
|
+
type: "text",
|
|
5983
|
+
text: JSON.stringify({
|
|
5984
|
+
task: buildTaskWriteSummary(task),
|
|
5985
|
+
workstreamId: prepared.nextWorkstreamId,
|
|
5986
|
+
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5987
|
+
noOp: true,
|
|
5988
|
+
}, null, 2)
|
|
5989
|
+
}],
|
|
5990
|
+
}));
|
|
5665
5991
|
}
|
|
5666
|
-
const updated = mutateTaskThroughStatusBoundary({
|
|
5992
|
+
const updated = measureToolSubphase('durable-write', () => mutateTaskThroughStatusBoundary({
|
|
5667
5993
|
task,
|
|
5668
5994
|
updates: { workstreamId: prepared.nextWorkstreamId },
|
|
5669
|
-
});
|
|
5995
|
+
}));
|
|
5670
5996
|
if (!updated) {
|
|
5671
5997
|
throw new McpPublicError(buildTaskLookupFailureMessage(String(inputId || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5672
5998
|
}
|
|
5673
|
-
recordMcpWriteAudit('update_task_workstream', {
|
|
5999
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('update_task_workstream', {
|
|
5674
6000
|
taskId: updated.id,
|
|
5675
6001
|
workstreamId: prepared.nextWorkstreamId,
|
|
5676
6002
|
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5677
|
-
});
|
|
5678
|
-
await notifyResourceMutation({
|
|
6003
|
+
}));
|
|
6004
|
+
await measureToolSubphaseAsync('notify', () => notifyResourceMutation({
|
|
5679
6005
|
includeTasks: true,
|
|
5680
6006
|
taskIds: [updated.id],
|
|
5681
6007
|
...(prepared.affectedWorkstreamIds.length > 0
|
|
5682
6008
|
? { includeWorkstreams: true, workstreamIds: prepared.affectedWorkstreamIds }
|
|
5683
6009
|
: {}),
|
|
5684
|
-
});
|
|
5685
|
-
return {
|
|
6010
|
+
}));
|
|
6011
|
+
return measureToolSubphase('serialize', () => ({
|
|
5686
6012
|
content: [{
|
|
5687
6013
|
type: "text",
|
|
5688
6014
|
text: JSON.stringify({
|
|
@@ -5691,7 +6017,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5691
6017
|
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5692
6018
|
}, null, 2)
|
|
5693
6019
|
}],
|
|
5694
|
-
};
|
|
6020
|
+
}));
|
|
5695
6021
|
}
|
|
5696
6022
|
case "update_workstream_initiative": {
|
|
5697
6023
|
const { id: legacyInputId, workstreamId, initiativeId } = args;
|
|
@@ -6585,7 +6911,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6585
6911
|
};
|
|
6586
6912
|
}
|
|
6587
6913
|
case "add_document_review_comment": {
|
|
6588
|
-
const { sessionId, body, anchor, parentCommentId } = args;
|
|
6914
|
+
const { sessionId, body, anchor, parentCommentId, idempotencyKey: rawIdempotencyKey } = args;
|
|
6589
6915
|
if (!sessionId || typeof sessionId !== 'string') {
|
|
6590
6916
|
throw new McpPublicError('sessionId is required');
|
|
6591
6917
|
}
|
|
@@ -6596,6 +6922,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6596
6922
|
&& Object.prototype.hasOwnProperty.call(args, 'anchor') && anchor !== null && anchor !== undefined) {
|
|
6597
6923
|
throw new McpPublicError('Replies cannot have anchors');
|
|
6598
6924
|
}
|
|
6925
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
6926
|
+
const idempotentCommentId = idempotencyKey
|
|
6927
|
+
? stableExplicitMutationId('comment', 'add_document_review_comment', idempotencyKey)
|
|
6928
|
+
: null;
|
|
6599
6929
|
const session = typeof parentCommentId === 'string' && parentCommentId.trim()
|
|
6600
6930
|
? core.addDocumentReviewReply({
|
|
6601
6931
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -6603,6 +6933,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6603
6933
|
parentCommentId: parentCommentId.trim(),
|
|
6604
6934
|
body,
|
|
6605
6935
|
actorId: getActiveMcpActorRef(),
|
|
6936
|
+
idempotencyKey,
|
|
6937
|
+
commentId: idempotentCommentId,
|
|
6606
6938
|
})
|
|
6607
6939
|
: core.addDocumentReviewComment({
|
|
6608
6940
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -6610,9 +6942,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6610
6942
|
body,
|
|
6611
6943
|
anchor: anchor ?? null,
|
|
6612
6944
|
actorId: getActiveMcpActorRef(),
|
|
6945
|
+
idempotencyKey,
|
|
6946
|
+
commentId: idempotentCommentId,
|
|
6613
6947
|
});
|
|
6614
6948
|
const asset = workspaceAssetStore?.getDocument(session.assetId, ACTIVE_WORKSPACE_ID) || null;
|
|
6615
|
-
const comment =
|
|
6949
|
+
const comment = idempotentCommentId
|
|
6950
|
+
? session.comments.find((candidate) => candidate.id === idempotentCommentId) || null
|
|
6951
|
+
: session.comments.at(-1) || null;
|
|
6616
6952
|
recordMcpWriteAudit('add_document_review_comment', {
|
|
6617
6953
|
sessionId: session.id,
|
|
6618
6954
|
documentAssetId: session.assetId,
|
|
@@ -7150,17 +7486,24 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
7150
7486
|
};
|
|
7151
7487
|
}
|
|
7152
7488
|
case "add_comment": {
|
|
7153
|
-
const { id: inputId, text } = args;
|
|
7489
|
+
const { id: inputId, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
7154
7490
|
const { id, task } = resolveTaskByIdentifierOrThrow(inputId);
|
|
7155
7491
|
const validation = validateStructuredCommentForAdd(text);
|
|
7156
7492
|
if (!validation.ok) {
|
|
7157
7493
|
throw new McpPublicError(validation.message);
|
|
7158
7494
|
}
|
|
7159
7495
|
const effectiveAuthor = getActiveMcpActorRef();
|
|
7160
|
-
const
|
|
7161
|
-
const
|
|
7496
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
7497
|
+
const operationId = idempotencyKey
|
|
7498
|
+
? stableExplicitMutationId('operation', 'add_comment', idempotencyKey)
|
|
7499
|
+
: requireStableMcpRequestId('operation', `task-comment:${id}`);
|
|
7500
|
+
const commentId = idempotencyKey
|
|
7501
|
+
? stableExplicitMutationId('comment', 'add_comment', idempotencyKey)
|
|
7502
|
+
: requireStableMcpRequestId('comment', id);
|
|
7162
7503
|
const existingCommentIds = new Set((task.comments || []).map((comment) => comment.id));
|
|
7163
7504
|
const durableRouter = getDurableTaskCommentMutationRouter(effectiveAuthor, operationId);
|
|
7505
|
+
if (idempotencyKey)
|
|
7506
|
+
requireDurableExplicitIdempotency(durableRouter.enabled, 'task comment');
|
|
7164
7507
|
const updated = durableRouter.enabled
|
|
7165
7508
|
? durableRouter.append({
|
|
7166
7509
|
taskId: id,
|
|
@@ -7558,61 +7901,113 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
7558
7901
|
const mcpInputValidator = createMcpInputValidator();
|
|
7559
7902
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
7560
7903
|
const { name, arguments: args } = resolveMcpToolCallCompatibility(request.params.name, request.params.arguments);
|
|
7904
|
+
const toolStartedAtMs = Date.now();
|
|
7905
|
+
let preparationMs = 0;
|
|
7906
|
+
let validationMs = 0;
|
|
7907
|
+
let handlerMs = 0;
|
|
7908
|
+
let responseBytes = 0;
|
|
7909
|
+
let timingOutcome = 'success';
|
|
7910
|
+
const subphases = {};
|
|
7561
7911
|
try {
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7912
|
+
const preparationStartedAtMs = Date.now();
|
|
7913
|
+
let handler;
|
|
7914
|
+
try {
|
|
7915
|
+
assertToolProfile(name);
|
|
7916
|
+
assertToolPermission(name);
|
|
7917
|
+
handler = toolHandlers.get(name);
|
|
7918
|
+
if (!handler) {
|
|
7919
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
7920
|
+
}
|
|
7921
|
+
ensureResolvedProfileForTool(name, args);
|
|
7922
|
+
assertResolvedProfileForMutatingTool(name);
|
|
7923
|
+
}
|
|
7924
|
+
finally {
|
|
7925
|
+
preparationMs = Date.now() - preparationStartedAtMs;
|
|
7567
7926
|
}
|
|
7568
|
-
ensureResolvedProfileForTool(name, args);
|
|
7569
|
-
assertResolvedProfileForMutatingTool(name);
|
|
7570
7927
|
const execute = () => executeMcpToolWithReadRetry(name, (args || {}), async (validatedArgs) => {
|
|
7928
|
+
const validationStartedAtMs = Date.now();
|
|
7571
7929
|
const descriptor = getCurrentToolDescriptor(name);
|
|
7572
7930
|
if (!descriptor)
|
|
7573
7931
|
throw new Error(`Unknown tool: ${name}`);
|
|
7574
7932
|
const validation = mcpInputValidator.validate(name, descriptor.inputSchema, validatedArgs);
|
|
7933
|
+
validationMs += Date.now() - validationStartedAtMs;
|
|
7575
7934
|
if (!validation.valid) {
|
|
7576
7935
|
throw new McpPublicError(`Arguments for ${name} do not match the declared input schema.`, 'MCP_INVALID_ARGUMENT', 400, { issues: validation.issues });
|
|
7577
7936
|
}
|
|
7578
|
-
|
|
7937
|
+
const handlerStartedAtMs = Date.now();
|
|
7938
|
+
try {
|
|
7939
|
+
return await mcpToolSubphaseTimings.run(subphases, () => handler(stripMcpEnvelopeArgsForHandler(name, validatedArgs)));
|
|
7940
|
+
}
|
|
7941
|
+
finally {
|
|
7942
|
+
handlerMs += Date.now() - handlerStartedAtMs;
|
|
7943
|
+
}
|
|
7579
7944
|
});
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7945
|
+
let result;
|
|
7946
|
+
if (extra.requestId === undefined || extra.requestId === null) {
|
|
7947
|
+
result = await execute();
|
|
7948
|
+
}
|
|
7949
|
+
else {
|
|
7950
|
+
const transportSessionId = String(extra.sessionId || '').trim();
|
|
7951
|
+
const requestIdentity = operationIdentityScope
|
|
7952
|
+
? fingerprintWorkspaceSyncPayload({
|
|
7953
|
+
kind: 'shared-mcp-request',
|
|
7954
|
+
operationIdentityScope,
|
|
7955
|
+
transportSessionId: transportSessionId
|
|
7956
|
+
|| options.auth?.mcpConnectionId
|
|
7957
|
+
|| options.auth?.mcpClientId
|
|
7958
|
+
|| 'mcp',
|
|
7959
|
+
requestId: { type: typeof extra.requestId, value: extra.requestId },
|
|
7960
|
+
tool: name,
|
|
7961
|
+
})
|
|
7962
|
+
: [
|
|
7963
|
+
...(runtimeMode === 'local' || !transportSessionId ? [runtimeRequestNamespace] : []),
|
|
7964
|
+
String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
|
|
7965
|
+
String(extra.requestId),
|
|
7966
|
+
name,
|
|
7967
|
+
].join(':');
|
|
7968
|
+
const logicalCloudCreateIdentity = [
|
|
7596
7969
|
String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
|
|
7597
7970
|
String(extra.requestId),
|
|
7598
7971
|
name,
|
|
7599
7972
|
].join(':');
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
].join(':');
|
|
7605
|
-
return await cloudCreateRequestIdentity.run(logicalCloudCreateIdentity, () => mcpRequestIdentity.run(requestIdentity, execute));
|
|
7973
|
+
result = await cloudCreateRequestIdentity.run(logicalCloudCreateIdentity, () => mcpRequestIdentity.run(requestIdentity, execute));
|
|
7974
|
+
}
|
|
7975
|
+
responseBytes = measureToolResultBytes(result);
|
|
7976
|
+
return result;
|
|
7606
7977
|
}
|
|
7607
7978
|
catch (error) {
|
|
7979
|
+
timingOutcome = 'failure';
|
|
7608
7980
|
const recoverable = buildRecoverableToolErrorResult(error, auditRequestId);
|
|
7609
7981
|
if (recoverable) {
|
|
7610
|
-
|
|
7982
|
+
const result = {
|
|
7611
7983
|
...recoverable,
|
|
7612
7984
|
isError: true,
|
|
7613
7985
|
};
|
|
7986
|
+
responseBytes = measureToolResultBytes(result);
|
|
7987
|
+
return result;
|
|
7988
|
+
}
|
|
7989
|
+
const result = buildUnexpectedToolErrorResult(auditRequestId || extra.requestId);
|
|
7990
|
+
responseBytes = measureToolResultBytes(result);
|
|
7991
|
+
return result;
|
|
7992
|
+
}
|
|
7993
|
+
finally {
|
|
7994
|
+
try {
|
|
7995
|
+
options.onToolCallTiming?.({
|
|
7996
|
+
requestId: String(auditRequestId || extra.requestId || '').trim() || null,
|
|
7997
|
+
toolName: name,
|
|
7998
|
+
isWrite: isMcpWriteToolName(name),
|
|
7999
|
+
outcome: timingOutcome,
|
|
8000
|
+
totalMs: Date.now() - toolStartedAtMs,
|
|
8001
|
+
preparationMs,
|
|
8002
|
+
validationMs,
|
|
8003
|
+
handlerMs,
|
|
8004
|
+
responseBytes,
|
|
8005
|
+
subphases: { ...subphases },
|
|
8006
|
+
});
|
|
8007
|
+
}
|
|
8008
|
+
catch {
|
|
8009
|
+
// Observability must never change tool behavior.
|
|
7614
8010
|
}
|
|
7615
|
-
return buildUnexpectedToolErrorResult(auditRequestId || extra.requestId);
|
|
7616
8011
|
}
|
|
7617
8012
|
});
|
|
7618
8013
|
return {
|