@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
|
@@ -17,7 +17,9 @@ import { resolveCanonicalMcpToolName } from '../../mcp/toolCallCompatibility.js'
|
|
|
17
17
|
import { applyPrefixedRateLimitHeaders } from '../rateLimit.js';
|
|
18
18
|
import { parseAiProfileBootstrapHeaders } from '../../mcp/aiProfileBootstrap.js';
|
|
19
19
|
import { parseMcpClientId } from '../../mcp/clientRegistry.js';
|
|
20
|
+
import { resolveAppAccessDeniedResponse } from '../appAccess.js';
|
|
20
21
|
import { resolveCloudApplicationBaseUrl } from '../../config/applicationBaseUrl.js';
|
|
22
|
+
import { getRequestPerformanceDiagnostics, runWithRequestPerformanceDiagnostics, } from '../../storage/postgresRequestDiagnostics.js';
|
|
21
23
|
// === SECTION: Error helpers ===
|
|
22
24
|
export function resolveErrorStatusCode(error, fallback = 400) {
|
|
23
25
|
if (error instanceof TaskforceRuleError)
|
|
@@ -39,6 +41,7 @@ export function buildCloudMcpProfileConnectionId(input) {
|
|
|
39
41
|
// === SECTION: Route timing helpers ===
|
|
40
42
|
export const BOOTSTRAP_ROUTE_SLOW_THRESHOLD_MS = 1500;
|
|
41
43
|
export const BOOTSTRAP_ROUTE_SEVERE_THRESHOLD_MS = 5000;
|
|
44
|
+
export const TASKFORCE_PERFORMANCE_TRACE_HEADER = 'x-taskforce-performance-trace';
|
|
42
45
|
function sanitizeServerTimingMetricName(name) {
|
|
43
46
|
return String(name || 'taskforce-route')
|
|
44
47
|
.trim()
|
|
@@ -68,6 +71,55 @@ export function serializeJsonWithTiming(payload, startedAt = getRouteTimingNow()
|
|
|
68
71
|
durationMs: Math.max(0, getRouteTimingNow() - startedAt),
|
|
69
72
|
};
|
|
70
73
|
}
|
|
74
|
+
export function runWithPerformanceTrace(req, callback) {
|
|
75
|
+
const enabled = requestHeader(req, TASKFORCE_PERFORMANCE_TRACE_HEADER, '').trim() === '1';
|
|
76
|
+
return runWithRequestPerformanceDiagnostics(enabled, callback);
|
|
77
|
+
}
|
|
78
|
+
export function currentPerformanceTraceDiagnostics() {
|
|
79
|
+
return getRequestPerformanceDiagnostics();
|
|
80
|
+
}
|
|
81
|
+
export function performanceTraceServerTimingMetrics(diagnostics) {
|
|
82
|
+
return diagnostics ? [{ name: 'postgres-worker-wait', durationMs: diagnostics.postgresWorkerWaitMs }] : [];
|
|
83
|
+
}
|
|
84
|
+
export function setPerformanceTraceHeaders(res, diagnostics, responseBytes) {
|
|
85
|
+
if (!diagnostics)
|
|
86
|
+
return;
|
|
87
|
+
res.setHeader('X-Taskforce-Postgres-Query-Count', String(diagnostics.postgresQueryCount));
|
|
88
|
+
res.setHeader('X-Taskforce-Postgres-Worker-Call-Count', String(diagnostics.postgresWorkerCallCount));
|
|
89
|
+
res.setHeader('X-Taskforce-Response-Bytes', String(responseBytes));
|
|
90
|
+
}
|
|
91
|
+
export function respondWithPerformanceTraceJson(input) {
|
|
92
|
+
runWithPerformanceTrace(input.req, () => {
|
|
93
|
+
const startedAt = getRouteTimingNow();
|
|
94
|
+
const data = input.load();
|
|
95
|
+
const loadCompletedAt = getRouteTimingNow();
|
|
96
|
+
const serialized = serializeJsonWithTiming(data);
|
|
97
|
+
const diagnostics = getRequestPerformanceDiagnostics();
|
|
98
|
+
const totalDurationMs = Math.max(0, getRouteTimingNow() - startedAt);
|
|
99
|
+
setServerTimingHeader(input.res, [
|
|
100
|
+
{ name: 'route-total', durationMs: totalDurationMs },
|
|
101
|
+
{ name: 'route-load', durationMs: loadCompletedAt - startedAt },
|
|
102
|
+
{ name: 'route-json', durationMs: serialized.durationMs },
|
|
103
|
+
...performanceTraceServerTimingMetrics(diagnostics),
|
|
104
|
+
]);
|
|
105
|
+
setPerformanceTraceHeaders(input.res, diagnostics, serialized.byteLength);
|
|
106
|
+
logSlowBootstrapRoute({
|
|
107
|
+
route: input.route,
|
|
108
|
+
workspaceId: input.workspaceId,
|
|
109
|
+
traceId: requestBootstrapTraceId(input.req),
|
|
110
|
+
durationMs: totalDurationMs,
|
|
111
|
+
details: diagnostics ? {
|
|
112
|
+
responseBytes: serialized.byteLength,
|
|
113
|
+
postgresQueryCount: diagnostics.postgresQueryCount,
|
|
114
|
+
postgresWorkerCallCount: diagnostics.postgresWorkerCallCount,
|
|
115
|
+
postgresWorkerWaitMs: Math.round(diagnostics.postgresWorkerWaitMs),
|
|
116
|
+
jsonSerializeMs: serialized.durationMs,
|
|
117
|
+
} : { responseBytes: serialized.byteLength },
|
|
118
|
+
});
|
|
119
|
+
input.res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
120
|
+
input.res.end(serialized.body);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
71
123
|
export function requestBootstrapTraceId(req) {
|
|
72
124
|
return requestHeader(req, 'x-taskforce-bootstrap-trace-id', '').trim();
|
|
73
125
|
}
|
|
@@ -100,6 +152,7 @@ export function buildCloudMcpRuntimeAuth(input) {
|
|
|
100
152
|
tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
|
|
101
153
|
aiProfileToken: auth.aiProfileToken,
|
|
102
154
|
aiProfileId: auth.authenticatedViaOAuth?.aiProfileId || null,
|
|
155
|
+
aiProfileIdentityAsserted: auth.aiProfileIdentityAsserted === true,
|
|
103
156
|
aiProfileName: auth.aiProfileName,
|
|
104
157
|
aiProfileIcon: auth.aiProfileIcon,
|
|
105
158
|
aiProfileColor: auth.aiProfileColor,
|
|
@@ -183,6 +236,7 @@ export function makeAuthenticateCloudMcpRequest(core, deps) {
|
|
|
183
236
|
userId: null,
|
|
184
237
|
...bootstrap,
|
|
185
238
|
aiProfileId: oauthAuth?.aiProfileId || null,
|
|
239
|
+
aiProfileIdentityAsserted: Boolean(bootstrap.aiProfileName || bootstrap.aiProfileProvider),
|
|
186
240
|
aiProfileToken: bootstrap.aiProfileToken || oauthAuth?.aiProfileToken || null,
|
|
187
241
|
aiProfileName: bootstrap.aiProfileName || oauthAuth?.aiProfileName || null,
|
|
188
242
|
mcpClientId: oauthAuth
|
|
@@ -337,6 +391,9 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
337
391
|
};
|
|
338
392
|
return async function handleCloudMcpRequest(req, res) {
|
|
339
393
|
const requestStartedAtMs = Date.now();
|
|
394
|
+
let authMs = 0;
|
|
395
|
+
let bodyParseMs = 0;
|
|
396
|
+
let rateLimitMs = 0;
|
|
340
397
|
if (requestRuntimeMode(req) !== 'cloud') {
|
|
341
398
|
writeMcpHttpError(res, 400, 'The /mcp HTTP endpoint is only available in cloud runtime. In local runtime, use the local stdio MCP server (for example: `taskforce mcp <project-path>`).');
|
|
342
399
|
return;
|
|
@@ -347,7 +404,9 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
347
404
|
writeMcpHttpError(res, 405, 'Method not allowed.');
|
|
348
405
|
return;
|
|
349
406
|
}
|
|
407
|
+
const authStartedAtMs = Date.now();
|
|
350
408
|
const auth = authenticateCloudMcpRequest(req, res);
|
|
409
|
+
authMs = Date.now() - authStartedAtMs;
|
|
351
410
|
if (!auth)
|
|
352
411
|
return;
|
|
353
412
|
if (req.method !== 'POST') {
|
|
@@ -356,7 +415,9 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
356
415
|
}
|
|
357
416
|
let body = undefined;
|
|
358
417
|
try {
|
|
418
|
+
const bodyParseStartedAtMs = Date.now();
|
|
359
419
|
body = await parseJsonBody(req);
|
|
420
|
+
bodyParseMs = Date.now() - bodyParseStartedAtMs;
|
|
360
421
|
}
|
|
361
422
|
catch (error) {
|
|
362
423
|
writeMcpHttpError(res, resolveErrorStatusCode(error, 400), 'Invalid JSON body.');
|
|
@@ -364,6 +425,7 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
364
425
|
}
|
|
365
426
|
const authScopes = auth.authenticatedViaToken?.scopes || auth.authenticatedViaOAuth?.scopes || [];
|
|
366
427
|
const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID();
|
|
428
|
+
res.setHeader('X-Request-ID', requestId);
|
|
367
429
|
const mcpMethod = !Array.isArray(body) && body !== null && typeof body === 'object'
|
|
368
430
|
? String(body.method || '')
|
|
369
431
|
: '';
|
|
@@ -389,7 +451,11 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
389
451
|
writeMcpHttpError(res, 403, 'Forbidden.');
|
|
390
452
|
return;
|
|
391
453
|
}
|
|
392
|
-
|
|
454
|
+
const writeToolNames = extractMcpWriteToolNames(body);
|
|
455
|
+
const rateLimitStartedAtMs = Date.now();
|
|
456
|
+
const rateLimitAllowed = applyCloudMcpWriteRateLimit(req, res, auth, body);
|
|
457
|
+
rateLimitMs = Date.now() - rateLimitStartedAtMs;
|
|
458
|
+
if (!rateLimitAllowed)
|
|
393
459
|
return;
|
|
394
460
|
let runtime = null;
|
|
395
461
|
let runtimeConstructionMs = 0;
|
|
@@ -397,6 +463,7 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
397
463
|
let requestHandlingMs = 0;
|
|
398
464
|
let cleanupMs = 0;
|
|
399
465
|
let outcome = 'success';
|
|
466
|
+
const toolTimingState = { value: null };
|
|
400
467
|
try {
|
|
401
468
|
const runtimeStartedAtMs = Date.now();
|
|
402
469
|
// Intentionally isolate the MCP runtime per HTTP request. The runtime closes over
|
|
@@ -420,6 +487,25 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
420
487
|
requestId,
|
|
421
488
|
authScopes,
|
|
422
489
|
}),
|
|
490
|
+
onToolCallTiming: (timing) => {
|
|
491
|
+
toolTimingState.value = timing;
|
|
492
|
+
if (!res.headersSent) {
|
|
493
|
+
setServerTimingHeader(res, [
|
|
494
|
+
{ name: 'auth', durationMs: authMs },
|
|
495
|
+
{ name: 'body-parse', durationMs: bodyParseMs },
|
|
496
|
+
{ name: 'rate-limit', durationMs: rateLimitMs },
|
|
497
|
+
{ name: 'runtime', durationMs: runtimeConstructionMs },
|
|
498
|
+
{ name: 'transport-connect', durationMs: transportConnectMs },
|
|
499
|
+
{ name: 'tool-envelope-prepare', durationMs: timing.preparationMs },
|
|
500
|
+
{ name: 'tool-envelope-validate', durationMs: timing.validationMs },
|
|
501
|
+
{ name: 'tool-handler', durationMs: timing.handlerMs },
|
|
502
|
+
...Object.entries(timing.subphases).map(([phase, durationMs]) => ({
|
|
503
|
+
name: `tool-${phase}`,
|
|
504
|
+
durationMs,
|
|
505
|
+
})),
|
|
506
|
+
]);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
423
509
|
});
|
|
424
510
|
runtimeConstructionMs = Date.now() - runtimeStartedAtMs;
|
|
425
511
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -465,7 +551,8 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
465
551
|
cleanupMs = Date.now() - cleanupStartedAtMs;
|
|
466
552
|
}
|
|
467
553
|
const totalDurationMs = Date.now() - requestStartedAtMs;
|
|
468
|
-
if (initializeRequest || mcpMethod === 'tools/list' || totalDurationMs >= 250) {
|
|
554
|
+
if (initializeRequest || mcpMethod === 'tools/list' || writeToolNames.length > 0 || totalDurationMs >= 250) {
|
|
555
|
+
const timingSnapshot = toolTimingState.value;
|
|
469
556
|
console.info(`[Taskforce MCP] ${JSON.stringify({
|
|
470
557
|
event: 'cloud_request_timing',
|
|
471
558
|
requestId,
|
|
@@ -477,13 +564,23 @@ export function makeHandleCloudMcpRequest(core, deps) {
|
|
|
477
564
|
method: mcpMethod || null,
|
|
478
565
|
toolName: extractFirstMcpToolName(body),
|
|
479
566
|
workspaceId: auth.workspaceId,
|
|
567
|
+
databaseProvider: core.getDatabaseProvider(),
|
|
568
|
+
authMode: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
|
|
569
|
+
batchSize: Array.isArray(body) ? body.length : 1,
|
|
570
|
+
isWrite: writeToolNames.length > 0,
|
|
480
571
|
outcome,
|
|
572
|
+
httpStatus: res.statusCode,
|
|
573
|
+
responseBytes: timingSnapshot?.responseBytes ?? null,
|
|
481
574
|
totalDurationMs,
|
|
482
575
|
preRuntimeMs: Math.max(0, totalDurationMs - runtimeConstructionMs - transportConnectMs - requestHandlingMs - cleanupMs),
|
|
576
|
+
authMs,
|
|
577
|
+
bodyParseMs,
|
|
578
|
+
rateLimitMs,
|
|
483
579
|
runtimeConstructionMs,
|
|
484
580
|
transportConnectMs,
|
|
485
581
|
requestHandlingMs,
|
|
486
582
|
cleanupMs,
|
|
583
|
+
toolTiming: timingSnapshot,
|
|
487
584
|
})}`);
|
|
488
585
|
}
|
|
489
586
|
}
|
|
@@ -521,6 +618,17 @@ export function requestSystemRole(req) {
|
|
|
521
618
|
export function requestUserId(req) {
|
|
522
619
|
return requestHeader(req, 'x-taskforce-user-id', 'anonymous') || 'anonymous';
|
|
523
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Resolve the human actor recorded by ordinary HTTP mutations. The standalone
|
|
623
|
+
* server has already replaced this header with authenticated cloud identity
|
|
624
|
+
* when available; local signed-in clients provide the same account id through
|
|
625
|
+
* the request-scoped mutation header. Anonymous sessions retain the legacy
|
|
626
|
+
* generic actor for compatibility.
|
|
627
|
+
*/
|
|
628
|
+
export function requestHumanActorRef(req) {
|
|
629
|
+
const userId = String(requestUserId(req) || '').trim();
|
|
630
|
+
return userId && userId !== 'anonymous' ? userId : 'user';
|
|
631
|
+
}
|
|
524
632
|
// === SECTION: Workspace ID resolution ===
|
|
525
633
|
export function makeRequestWorkspaceId(core, context) {
|
|
526
634
|
return function requestWorkspaceId(req) {
|
|
@@ -625,23 +733,11 @@ export function makeEnsureAppAccess(core, context) {
|
|
|
625
733
|
if (!access.authenticated || !access.userId || access.userId === 'anonymous')
|
|
626
734
|
return true;
|
|
627
735
|
const accountAccess = core.getAccountAccessStatus(access.userId);
|
|
628
|
-
|
|
736
|
+
const denied = resolveAppAccessDeniedResponse(accountAccess);
|
|
737
|
+
if (!denied)
|
|
629
738
|
return true;
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
: accountAccess.gate === 'checkout_pending'
|
|
633
|
-
? 'CHECKOUT_PENDING'
|
|
634
|
-
: accountAccess.gate === 'misconfigured'
|
|
635
|
-
? 'ACCOUNT_ACCESS_MISCONFIGURED'
|
|
636
|
-
: 'ACCOUNT_ENTITLEMENT_REQUIRED';
|
|
637
|
-
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
638
|
-
res.end(JSON.stringify({
|
|
639
|
-
error: accountAccess.message || 'This account cannot access the app.',
|
|
640
|
-
code,
|
|
641
|
-
gate: accountAccess.gate,
|
|
642
|
-
canAccessPlans: accountAccess.canAccessPlans,
|
|
643
|
-
planSelectionRequired: accountAccess.planSelectionRequired
|
|
644
|
-
}));
|
|
739
|
+
res.writeHead(denied.statusCode, { 'Content-Type': 'application/json' });
|
|
740
|
+
res.end(JSON.stringify(denied.body));
|
|
645
741
|
return false;
|
|
646
742
|
};
|
|
647
743
|
}
|
|
@@ -2,6 +2,7 @@ import { parseJsonBody } from './primitives.js';
|
|
|
2
2
|
import { resolveSessionCookieAttributesForRequest } from '../auth.js';
|
|
3
3
|
import { isAiProfileSurfaceType, parseAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
|
|
4
4
|
import { isAiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
|
|
5
|
+
import { assertPlanningIdentityColor, assertPlanningIdentityIcon } from '../../shared/planningIdentity.js';
|
|
5
6
|
import { serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload, } from '../../sync/contentSyncPayload.js';
|
|
6
7
|
import { normalizeAiProfileSyncPayload, normalizeAgentConversationSyncPayload, normalizeAgentRoleSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, normalizeTaskforceAgentSkillSyncPayload, normalizeTaskforceAgentSyncPayload, } from '../../sync/collaborationSyncPayload.js';
|
|
7
8
|
import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from '../../sync/planningSyncPayload.js';
|
|
@@ -451,6 +452,27 @@ export function registerSyncRoutes(deps) {
|
|
|
451
452
|
op: 'workstream-upsert',
|
|
452
453
|
workstream
|
|
453
454
|
})),
|
|
455
|
+
...[
|
|
456
|
+
...incomingInitiatives.map((entity) => ({
|
|
457
|
+
entityType: 'initiative',
|
|
458
|
+
entityId: String(entity.id || '').trim(),
|
|
459
|
+
icon: assertPlanningIdentityIcon(entity.icon),
|
|
460
|
+
color: assertPlanningIdentityColor(entity.color),
|
|
461
|
+
updatedAt: String(entity.identityUpdatedAt || '').trim(),
|
|
462
|
+
})),
|
|
463
|
+
...incomingWorkstreams.map((entity) => ({
|
|
464
|
+
entityType: 'workstream',
|
|
465
|
+
entityId: String(entity.id || '').trim(),
|
|
466
|
+
icon: assertPlanningIdentityIcon(entity.icon),
|
|
467
|
+
color: assertPlanningIdentityColor(entity.color),
|
|
468
|
+
updatedAt: String(entity.identityUpdatedAt || '').trim(),
|
|
469
|
+
})),
|
|
470
|
+
]
|
|
471
|
+
.filter((identity) => identity.entityId.length > 0 && Number.isFinite(Date.parse(identity.updatedAt)))
|
|
472
|
+
.map((identity) => ({
|
|
473
|
+
op: 'planning-identity-upsert',
|
|
474
|
+
identity,
|
|
475
|
+
})),
|
|
454
476
|
...incomingAiProfiles
|
|
455
477
|
.map((profile) => normalizeAiProfileSyncPayload(profile, cloudWorkspaceId, normalizeIncomingAiProfileSurfaceType, normalizeIncomingAiProfileSeatScope, 'push.aiProfileUpsert'))
|
|
456
478
|
.filter((profile) => Boolean(profile && profile.id.length > 0))
|
|
@@ -352,7 +352,8 @@ export function registerWorkspaceSyncV3FeedRoutes(input) {
|
|
|
352
352
|
pageSize: Number.isFinite(limit) ? limit : undefined,
|
|
353
353
|
nestedCoverage: coverage.wire.nestedWriterPrerequisitesRequired,
|
|
354
354
|
workflowCoverage: coverage.wire.workflowWriterPrerequisitesRequired,
|
|
355
|
-
planningOrderCoverage: coverage.wire.planningOrderWriterPrerequisitesRequired
|
|
355
|
+
planningOrderCoverage: coverage.wire.planningOrderWriterPrerequisitesRequired,
|
|
356
|
+
coveredLegacyWritesFenced: combinedLegacyPushDisabled })
|
|
356
357
|
: buildWorkspaceSyncV3InitiativeRepairPage({ core: input.core, workspaceId,
|
|
357
358
|
cursorKeyring: cursorKeyring, cursor: repairCursor,
|
|
358
359
|
pageSize: Number.isFinite(limit) ? limit : undefined });
|
|
@@ -397,7 +398,8 @@ export function registerWorkspaceSyncV3FeedRoutes(input) {
|
|
|
397
398
|
cursorKeyring: cursorKeyring, pageSize: Number.isFinite(limit) ? limit : undefined,
|
|
398
399
|
nestedCoverage: coverage.wire.nestedWriterPrerequisitesRequired,
|
|
399
400
|
workflowCoverage: coverage.wire.workflowWriterPrerequisitesRequired,
|
|
400
|
-
planningOrderCoverage: coverage.wire.planningOrderWriterPrerequisitesRequired
|
|
401
|
+
planningOrderCoverage: coverage.wire.planningOrderWriterPrerequisitesRequired,
|
|
402
|
+
coveredLegacyWritesFenced: combinedLegacyPushDisabled })
|
|
401
403
|
: buildWorkspaceSyncV3InitiativeRepairPage({ core: input.core, workspaceId,
|
|
402
404
|
cursorKeyring: cursorKeyring, pageSize: Number.isFinite(limit) ? limit : undefined });
|
|
403
405
|
const repair = coverage.kind === 'combined' ? page : { ...page,
|
|
@@ -203,7 +203,8 @@ function parseEnvelope(raw) {
|
|
|
203
203
|
return { ...common, domain: 'task-checklist', mutationKind: 'replace', items };
|
|
204
204
|
}
|
|
205
205
|
if (operation.domain === 'task-attachment-links' && operation.mutationKind === 'replace') {
|
|
206
|
-
|
|
206
|
+
const hasCardCoverAssetId = Object.prototype.hasOwnProperty.call(payload, 'cardCoverAssetId');
|
|
207
|
+
assertExactKeys(payload, hasCardCoverAssetId ? ['links', 'cardCoverAssetId'] : ['links'], 'operation.payload');
|
|
207
208
|
let links;
|
|
208
209
|
try {
|
|
209
210
|
links = validateWorkspaceSyncV3TaskAttachmentLinksPayload({ taskId: common.entityId, links: payload.links }).links;
|
|
@@ -211,7 +212,21 @@ function parseEnvelope(raw) {
|
|
|
211
212
|
catch (error) {
|
|
212
213
|
throw new LocalCaptureRequestError(error instanceof Error ? error.message : 'operation.payload.links is invalid.');
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
+
if (hasCardCoverAssetId && payload.cardCoverAssetId !== null
|
|
216
|
+
&& (typeof payload.cardCoverAssetId !== 'string' || !payload.cardCoverAssetId.trim())) {
|
|
217
|
+
throw new LocalCaptureRequestError('operation.payload.cardCoverAssetId must be a non-empty string or null.');
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
...common,
|
|
221
|
+
domain: 'task-attachment-links',
|
|
222
|
+
mutationKind: 'replace',
|
|
223
|
+
links,
|
|
224
|
+
...(hasCardCoverAssetId ? {
|
|
225
|
+
cardCoverAssetId: payload.cardCoverAssetId === null
|
|
226
|
+
? null
|
|
227
|
+
: String(payload.cardCoverAssetId).trim(),
|
|
228
|
+
} : {}),
|
|
229
|
+
};
|
|
215
230
|
}
|
|
216
231
|
if (operation.domain === 'initiative') {
|
|
217
232
|
const base = {
|
|
@@ -701,6 +716,9 @@ export function registerWorkspaceSyncV3LocalCaptureRoutes(input) {
|
|
|
701
716
|
identity,
|
|
702
717
|
taskId: envelope.entityId,
|
|
703
718
|
links: envelope.links,
|
|
719
|
+
...(Object.prototype.hasOwnProperty.call(envelope, 'cardCoverAssetId')
|
|
720
|
+
? { cardCoverAssetId: envelope.cardCoverAssetId ?? null }
|
|
721
|
+
: {}),
|
|
704
722
|
now: acceptedAt,
|
|
705
723
|
actorRef: 'user',
|
|
706
724
|
onPostCommitError: input.onPostCommitError,
|
|
@@ -471,7 +471,11 @@ export function parseWorkspaceSyncV3OperationEnvelope(body) {
|
|
|
471
471
|
if (baseLifecycleRevision !== '0') {
|
|
472
472
|
throw new SyncV3RequestError('Task attachment-link lifecycle revision must be 0.');
|
|
473
473
|
}
|
|
474
|
-
assertExactKeys(payload,
|
|
474
|
+
assertExactKeys(payload, [
|
|
475
|
+
'links',
|
|
476
|
+
...(payload.cardCoverAssetId === undefined ? [] : ['cardCoverAssetId']),
|
|
477
|
+
...(payload.originActor === undefined ? [] : ['originActor']),
|
|
478
|
+
], 'operation.payload');
|
|
475
479
|
let links;
|
|
476
480
|
try {
|
|
477
481
|
links = validateWorkspaceSyncV3TaskAttachmentLinksPayload({ taskId: entityId, links: payload.links }).links;
|
|
@@ -482,10 +486,21 @@ export function parseWorkspaceSyncV3OperationEnvelope(body) {
|
|
|
482
486
|
const originActor = payload.originActor === undefined
|
|
483
487
|
? undefined
|
|
484
488
|
: requireOriginActor(payload.originActor);
|
|
489
|
+
const cardCoverAssetId = payload.cardCoverAssetId === undefined
|
|
490
|
+
? undefined
|
|
491
|
+
: payload.cardCoverAssetId === null
|
|
492
|
+
? null
|
|
493
|
+
: typeof payload.cardCoverAssetId === 'string' && payload.cardCoverAssetId.trim()
|
|
494
|
+
? payload.cardCoverAssetId.trim()
|
|
495
|
+
: (() => { throw new SyncV3RequestError('operation.payload.cardCoverAssetId must be a string or null.'); })();
|
|
485
496
|
return { ...common, operation: {
|
|
486
497
|
domain: 'task-attachment-links', entityId, mutationKind: 'replace', baseEntityRevision,
|
|
487
498
|
baseLifecycleRevision: '0',
|
|
488
|
-
payload: {
|
|
499
|
+
payload: {
|
|
500
|
+
links,
|
|
501
|
+
...(cardCoverAssetId !== undefined ? { cardCoverAssetId } : {}),
|
|
502
|
+
...(originActor ? { originActor } : {}),
|
|
503
|
+
},
|
|
489
504
|
} };
|
|
490
505
|
}
|
|
491
506
|
if (operation.domain === 'entity-event'
|