@taskforcehq/taskforce 0.3.330 → 0.3.333

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +7 -2
  2. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
  3. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
  4. package/dist/components/features/planning/PlanningModule.js +15 -3
  5. package/dist/components/features/planning/planningMetadata.d.ts +10 -0
  6. package/dist/components/features/planning/planningMetadata.js +8 -0
  7. package/dist/components/views/StandaloneLayout.js +35 -1
  8. package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
  9. package/dist/components/views/panels/PlanningDrawer.js +12 -8
  10. package/dist/config/envSchema.js +7 -0
  11. package/dist/core/McpTokenService.d.ts +2 -0
  12. package/dist/core/McpTokenService.js +13 -5
  13. package/dist/core/Taskforce.d.ts +11 -3
  14. package/dist/core/Taskforce.js +33 -3
  15. package/dist/core/types.d.ts +1 -0
  16. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
  17. package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
  18. package/dist/mcp/httpProtocolRouting.d.ts +1 -0
  19. package/dist/mcp/httpProtocolRouting.js +8 -5
  20. package/dist/mcp/runtime.js +24 -5
  21. package/dist/mcp/taskPlanningRegistrar.js +2 -2
  22. package/dist/mcp/toolProfiles.js +1 -0
  23. package/dist/mcp/toolRegistry.js +8 -8
  24. package/dist/server/buildInfo.d.ts +1 -0
  25. package/dist/server/buildInfo.js +5 -1
  26. package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
  27. package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
  28. package/dist/server/localWorkspaceSyncServerRuntime.js +13 -5
  29. package/dist/server/routes/admin.js +14 -2
  30. package/dist/server/routes/localService.js +1 -0
  31. package/dist/server/routes/shared.d.ts +3 -0
  32. package/dist/server/routes/shared.js +280 -240
  33. package/dist/server/routes/syncPushRoutes.d.ts +3 -0
  34. package/dist/server/routes/syncPushRoutes.js +8 -2
  35. package/dist/service/localServiceCli.d.ts +2 -2
  36. package/dist/service/localServiceCli.js +4 -3
  37. package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
  38. package/dist/services/executorPhaseAIssuerCore.js +107 -0
  39. package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
  40. package/dist/services/taskforceAgentMcpBridge.js +35 -5
  41. package/dist/shared/executorPhaseASchemaValidation.js +1 -1
  42. package/dist/storage/postgresAdapter.js +3 -1
  43. package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
  44. package/dist/storage/postgresRequestDiagnostics.js +15 -2
  45. package/dist/storage/postgresWorker.js +13 -3
  46. package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
  47. package/dist/storage/workflowStore.d.ts +2 -0
  48. package/dist/storage/workflowStore.js +39 -2
  49. package/dist/sync/cloudSyncApi.js +4 -2
  50. package/dist/sync/collaborationSyncState.js +5 -0
  51. package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
  52. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
  53. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +1 -0
  54. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +7 -6
  55. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +2 -2
  56. package/dist/sync/engine/workspaceSyncEngineLocalApply.d.ts +1 -0
  57. package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +6 -0
  58. package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +34 -1
  59. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +2 -2
  60. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +4 -1
  61. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
  62. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +55 -10
  63. package/dist/sync/syncApplyHandlers.d.ts +1 -0
  64. package/dist/sync/syncApplyHandlers.js +72 -15
  65. package/dist/sync/syncService.d.ts +1 -0
  66. package/dist/sync/syncService.js +5 -2
  67. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
  68. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
  69. package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
  70. package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
  71. package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
  72. package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
  73. package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
  74. package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
  75. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
  76. package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
  77. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
  78. package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
  79. package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
  80. package/dist/sync/v3/syncDurabilityStore.js +34 -6
  81. package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
  82. package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
  83. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
  84. package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
  85. package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
  86. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
  87. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
  88. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
  89. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
  90. package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-Cfvb9Nuv.js} +1 -1
  91. package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-0oR70eFG.js} +1 -1
  92. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-C33iH_r_.js} +1 -1
  93. package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-BAqxUtke.js} +1 -1
  94. package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-BudIbOR9.js} +2 -2
  95. package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-A8dFQyId.js} +1 -1
  96. package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-g7vtdsx1.js} +1 -1
  97. package/dist/ui/assets/PlanningModule-D-DeQs4F.js +1 -0
  98. package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-BWmfGi0Z.js} +1 -1
  99. package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-BA_lQmS9.js} +1 -1
  100. package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-DAlH4YIP.js} +1 -1
  101. package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-DmeCf6pJ.js} +2 -2
  102. package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-p08eDOaa.js} +1 -1
  103. package/dist/ui/assets/index-GHJgcW3j.js +7 -0
  104. package/dist/ui/index.html +1 -1
  105. package/package.json +3 -1
  106. package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
  107. package/dist/ui/assets/index-CTsDBaef.js +0 -7
@@ -2033,6 +2033,7 @@ export class TaskforceCore {
2033
2033
  provider TEXT NOT NULL,
2034
2034
  client_name TEXT NOT NULL,
2035
2035
  redirect_uris_json TEXT NOT NULL,
2036
+ application_type TEXT NOT NULL DEFAULT 'web',
2036
2037
  product_client_id TEXT,
2037
2038
  status TEXT NOT NULL DEFAULT 'active',
2038
2039
  created_at TEXT NOT NULL,
@@ -2385,6 +2386,9 @@ export class TaskforceCore {
2385
2386
  if (!mcpOauthClientColumns.some((c) => c.name === 'product_client_id')) {
2386
2387
  this.db.exec(`ALTER TABLE mcp_oauth_clients ADD COLUMN product_client_id TEXT`);
2387
2388
  }
2389
+ if (!mcpOauthClientColumns.some((c) => c.name === 'application_type')) {
2390
+ this.db.exec(`ALTER TABLE mcp_oauth_clients ADD COLUMN application_type TEXT NOT NULL DEFAULT 'web'`);
2391
+ }
2388
2392
  const mcpOauthGrantColumns = this.db.prepare(`PRAGMA table_info(mcp_oauth_connector_grants)`).all();
2389
2393
  if (!mcpOauthGrantColumns.some((c) => c.name === 'product_client_id')) {
2390
2394
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN product_client_id TEXT`);
@@ -2405,6 +2409,7 @@ export class TaskforceCore {
2405
2409
  else {
2406
2410
  try {
2407
2411
  this.db.exec(`ALTER TABLE mcp_oauth_clients ADD COLUMN IF NOT EXISTS product_client_id TEXT`);
2412
+ this.db.exec(`ALTER TABLE mcp_oauth_clients ADD COLUMN IF NOT EXISTS application_type TEXT NOT NULL DEFAULT 'web'`);
2408
2413
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS product_client_id TEXT`);
2409
2414
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS ai_profile_id TEXT`);
2410
2415
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS ai_profile_token TEXT`);
@@ -7749,7 +7754,7 @@ export class TaskforceCore {
7749
7754
  deletedAt: session.deletedAt,
7750
7755
  });
7751
7756
  }
7752
- upsertDocumentReviewCommentForSync(comment) {
7757
+ upsertDocumentReviewCommentForSync(comment, options) {
7753
7758
  const deps = this.getDocumentReviewServiceDeps();
7754
7759
  return deps.commentStore.upsertForSync({
7755
7760
  id: comment.id,
@@ -7769,7 +7774,7 @@ export class TaskforceCore {
7769
7774
  createdAt: comment.createdAt,
7770
7775
  updatedAt: comment.updatedAt,
7771
7776
  deletedAt: comment.deletedAt,
7772
- });
7777
+ }, options);
7773
7778
  }
7774
7779
  upsertAnnotatedAttachmentSessionForSync(session) {
7775
7780
  const deps = this.getAnnotatedAttachmentServiceDeps();
@@ -9975,7 +9980,7 @@ export class TaskforceCore {
9975
9980
  return this.getWorkstream(workstreamId, normalizedWorkspaceId) || updated;
9976
9981
  })();
9977
9982
  }
9978
- installDurableTaskCreateAuthoritativeForSync(task, workspaceId) {
9983
+ installDurableTaskCreateAuthoritativeForSync(task, workspaceId, options) {
9979
9984
  const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
9980
9985
  const taskId = String(task?.id || '').trim();
9981
9986
  const createdAt = String(task?.createdAt || '').trim();
@@ -9993,6 +9998,31 @@ export class TaskforceCore {
9993
9998
  return this.db.transaction(() => {
9994
9999
  const existing = this.getTask(taskId, normalizedWorkspaceId);
9995
10000
  if (!existing) {
10001
+ const deleted = options?.preserveDeletedSuccessor
10002
+ ? this.getDeletedTask(taskId, normalizedWorkspaceId)
10003
+ : null;
10004
+ if (deleted) {
10005
+ this.relocateTaskReferenceConflict(normalizedWorkspaceId, referenceNumber, taskId, updatedAt);
10006
+ if (this.reserveTaskReferenceNumber(normalizedWorkspaceId, referenceNumber, taskId) !== referenceNumber) {
10007
+ throw new TaskforceRuleError('Authoritative task reference is already in use.', 409, 'WORKSPACE_SYNC_AUTHORITATIVE_RESPONSE_INVALID');
10008
+ }
10009
+ const authoritativeSnapshot = {
10010
+ ...deleted.taskSnapshot,
10011
+ referenceNumber,
10012
+ localReferenceNumber: null,
10013
+ referenceLabel: deriveTaskReferenceLabel({ referenceNumber, localReferenceNumber: null }),
10014
+ createdAt,
10015
+ createdBy: task.createdBy,
10016
+ };
10017
+ const updated = this.db.prepare(`
10018
+ UPDATE deleted_tasks SET task_snapshot_json = ?
10019
+ WHERE tenant_id = ? AND workspace_id = ? AND task_id = ?
10020
+ `).run(JSON.stringify(authoritativeSnapshot), this.tenantId, normalizedWorkspaceId, taskId);
10021
+ if (Number(updated.changes || 0) !== 1) {
10022
+ throw new TaskforceRuleError('Captured deleted task history is missing.', 409, 'WORKSPACE_SYNC_AUTHORITATIVE_RESPONSE_INVALID');
10023
+ }
10024
+ return this.getDeletedTask(taskId, normalizedWorkspaceId).taskSnapshot;
10025
+ }
9996
10026
  throw new TaskforceRuleError('Task not found.', 404, 'TASK_NOT_FOUND');
9997
10027
  }
9998
10028
  const mutableCreateFields = [
@@ -661,6 +661,7 @@ export interface McpOAuthClientRecord {
661
661
  id: string;
662
662
  provider: McpOAuthProvider;
663
663
  clientName: string;
664
+ applicationType: 'native' | 'web';
664
665
  productClientId?: string | null;
665
666
  redirectUris: string[];
666
667
  status: McpOAuthClientStatus;
@@ -77,6 +77,8 @@ export declare class DocumentReviewCommentStore {
77
77
  threadStatusUpdatedAt?: string | null;
78
78
  resolvedAt?: string | null;
79
79
  resolvedByActorId?: string | null;
80
+ }, options?: {
81
+ allowAuthoritativeThreadReconciliation?: boolean;
80
82
  }): DocumentReviewCommentSyncRecord;
81
83
  softDelete(commentIdRaw: string, workspaceIdRaw: string, deletedAtRaw?: string, deletedByActorIdRaw?: string | null): boolean;
82
84
  replaceForSession(input: {
@@ -235,7 +235,7 @@ export class DocumentReviewCommentStore {
235
235
  : (input.threadStatus === undefined ? existing.threadStatus : input.threadStatus), input.threadStatusUpdatedAt === undefined ? existing.threadStatusUpdatedAt : normalizeNullable(input.threadStatusUpdatedAt), input.resolvedAt === undefined ? existing.resolvedAt : normalizeNullable(input.resolvedAt), input.resolvedByActorId === undefined ? existing.resolvedByActorId : normalizeNullable(input.resolvedByActorId), normalizeId(input.updatedAt) || new Date().toISOString(), input.deletedAt === undefined ? existing.deletedAt : normalizeNullable(input.deletedAt), this.tenantId, normalizeWorkspaceId(input.workspaceId), normalizeId(input.id));
236
236
  return this.get(input.id, input.workspaceId, { includeDeleted: true });
237
237
  }
238
- upsertForSync(input) {
238
+ upsertForSync(input, options) {
239
239
  const existing = this.get(input.id, input.workspaceId, { includeDeleted: true });
240
240
  if (!existing) {
241
241
  return this.create(input);
@@ -248,7 +248,20 @@ export class DocumentReviewCommentStore {
248
248
  ? undefined
249
249
  : normalizeNullable(input.parentCommentId);
250
250
  if (incomingParentCommentId !== undefined && incomingParentCommentId !== existing.parentCommentId) {
251
- throw new Error(`Document review comment ${existing.id} cannot move between threads.`);
251
+ if (options?.allowAuthoritativeThreadReconciliation !== true) {
252
+ throw new Error(`Document review comment ${existing.id} cannot move between threads.`);
253
+ }
254
+ const incomingParent = incomingParentCommentId
255
+ ? this.get(incomingParentCommentId, input.workspaceId, { includeDeleted: true })
256
+ : null;
257
+ if (existing.parentCommentId
258
+ || !incomingParent
259
+ || incomingParent.deletedAt
260
+ || incomingParent.sessionId !== existing.sessionId
261
+ || incomingParent.parentCommentId
262
+ || incomingParent.id === existing.id) {
263
+ throw new Error(`Document review comment ${existing.id} cannot reconcile to an invalid thread root.`);
264
+ }
252
265
  }
253
266
  const incomingThreadStatusUpdatedAt = input.threadStatusUpdatedAt === undefined
254
267
  ? undefined
@@ -283,13 +296,19 @@ export class DocumentReviewCommentStore {
283
296
  AND id = ?
284
297
  `).run(normalizeId(input.sessionId) || existing.sessionId, normalizeId(input.body), input.anchor ? JSON.stringify(input.anchor) : null, normalizeNullable(input.authorActorId), normalizeNullable(input.updatedByActorId), input.deletedByActorId === undefined ? existing.deletedByActorId : normalizeNullable(input.deletedByActorId), nextParentCommentId, nextParentCommentId
285
298
  ? null
286
- : (shouldApplyThreadLifecycle ? input.threadStatus : existing.threadStatus), shouldApplyThreadLifecycle
287
- ? incomingThreadStatusUpdatedAt
288
- : existing.threadStatusUpdatedAt, shouldApplyThreadLifecycle
289
- ? normalizeNullable(input.resolvedAt)
290
- : existing.resolvedAt, shouldApplyThreadLifecycle
291
- ? normalizeNullable(input.resolvedByActorId)
292
- : existing.resolvedByActorId, normalizePositiveInteger(input.order) ?? existing.order, normalizeId(input.createdAt) || existing.createdAt, normalizeId(input.updatedAt) || existing.updatedAt, existing.deletedAt || (input.deletedAt === undefined
299
+ : (shouldApplyThreadLifecycle ? input.threadStatus : existing.threadStatus), nextParentCommentId
300
+ ? null
301
+ : shouldApplyThreadLifecycle
302
+ ? incomingThreadStatusUpdatedAt
303
+ : existing.threadStatusUpdatedAt, nextParentCommentId
304
+ ? null
305
+ : shouldApplyThreadLifecycle
306
+ ? normalizeNullable(input.resolvedAt)
307
+ : existing.resolvedAt, nextParentCommentId
308
+ ? null
309
+ : shouldApplyThreadLifecycle
310
+ ? normalizeNullable(input.resolvedByActorId)
311
+ : existing.resolvedByActorId, normalizePositiveInteger(input.order) ?? existing.order, normalizeId(input.createdAt) || existing.createdAt, normalizeId(input.updatedAt) || existing.updatedAt, existing.deletedAt || (input.deletedAt === undefined
293
312
  ? null
294
313
  : normalizeNullable(input.deletedAt)), this.tenantId, normalizeWorkspaceId(input.workspaceId), normalizeId(input.id));
295
314
  return this.get(input.id, input.workspaceId, { includeDeleted: true });
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { type McpServerFactory } from '@modelcontextprotocol/server';
3
+ export declare function createModernMcpHttpHandler(factory: McpServerFactory, onerror?: (error: Error) => void): import("@modelcontextprotocol/server").McpHttpHandler;
3
4
  export declare function isModernMcpHttpRequest(req: IncomingMessage, parsedBody: unknown): Promise<boolean>;
4
5
  export declare function handleModernMcpHttpRequest(input: {
5
6
  req: IncomingMessage;
@@ -1,15 +1,18 @@
1
1
  import { toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
2
2
  import { createMcpHandler, isLegacyRequest, } from '@modelcontextprotocol/server';
3
+ export function createModernMcpHttpHandler(factory, onerror) {
4
+ return createMcpHandler(factory, {
5
+ legacy: 'reject',
6
+ responseMode: 'auto',
7
+ onerror,
8
+ });
9
+ }
3
10
  export async function isModernMcpHttpRequest(req, parsedBody) {
4
11
  const request = await toWebRequest(req, parsedBody);
5
12
  return !(await isLegacyRequest(request, parsedBody));
6
13
  }
7
14
  export async function handleModernMcpHttpRequest(input) {
8
- const handler = createMcpHandler(input.factory, {
9
- legacy: 'reject',
10
- responseMode: 'auto',
11
- onerror: input.onerror,
12
- });
15
+ const handler = createModernMcpHttpHandler(input.factory, input.onerror);
13
16
  try {
14
17
  await toNodeHandler(handler, { onerror: input.onerror })(input.req, input.res, input.parsedBody);
15
18
  }
@@ -119,7 +119,10 @@ class ToolProfileHiddenError extends Error {
119
119
  suggestedTool;
120
120
  constructor(name, activeProfile) {
121
121
  const requiredProfiles = getMcpToolRequiredProfiles(name);
122
- const suggestedTool = getMcpToolSuccessor(name);
122
+ const configuredSuccessor = getMcpToolSuccessor(name);
123
+ const suggestedTool = configuredSuccessor && isMcpToolVisibleInProfile(configuredSuccessor.name, activeProfile)
124
+ ? configuredSuccessor
125
+ : null;
123
126
  const suffix = suggestedTool ? ` Use ${suggestedTool.name} instead.` : ` Request one of these profiles to use it: ${requiredProfiles.join(', ')}.`;
124
127
  super(`Tool ${name} is not available in the ${activeProfile} MCP tool profile.${suffix}`);
125
128
  this.activeProfile = activeProfile;
@@ -1884,6 +1887,9 @@ export function createTaskforceMcpServer(options = {}) {
1884
1887
  } : {}),
1885
1888
  title: task?.title || null,
1886
1889
  status: task?.status || null,
1890
+ isArchived: Boolean(task?.isArchived),
1891
+ completedAt: task?.completedAt || null,
1892
+ canceledReason: task?.canceledReason || null,
1887
1893
  category: task?.category || null,
1888
1894
  type: task?.type || null,
1889
1895
  priority: task?.priority ?? null,
@@ -5327,6 +5333,7 @@ export function createTaskforceMcpServer(options = {}) {
5327
5333
  const avatarSuggestion = resolved.created
5328
5334
  && !configuredClientBlocksGeneration
5329
5335
  && isAiProfileAvatarGenerationEligible(resolved.profile)
5336
+ && isMcpToolVisibleInProfile('generate_profile_avatar', activeToolProfile)
5330
5337
  ? {
5331
5338
  avatarSuggestion: {
5332
5339
  nextTool: 'generate_profile_avatar',
@@ -7825,7 +7832,7 @@ export function createTaskforceMcpServer(options = {}) {
7825
7832
  const { id: inputId, status, reason } = args;
7826
7833
  const { id, task } = resolveTaskByIdentifierOrThrow(inputId);
7827
7834
  const isCancelled = status === 'cancelled';
7828
- taskLifecycleCommandService.archive(task, { status, reason });
7835
+ const result = taskLifecycleCommandService.archive(task, { status, reason });
7829
7836
  recordMcpWriteAudit('archive_task', { taskId: id, status, cancelled: isCancelled });
7830
7837
  await notifyResourceMutation({
7831
7838
  taskIds: [id],
@@ -7833,7 +7840,15 @@ export function createTaskforceMcpServer(options = {}) {
7833
7840
  includeStats: true,
7834
7841
  });
7835
7842
  return {
7836
- content: [{ type: "text", text: `Task ${formatTaskIdentifier(task)} archived as ${status}` }],
7843
+ content: [{ type: "text", text: JSON.stringify({
7844
+ task: buildTaskWriteSummary(result.task),
7845
+ archive: {
7846
+ archived: true,
7847
+ status: result.status,
7848
+ cancelled: result.cancelled,
7849
+ reason: isCancelled ? String(reason || '').trim() || null : null,
7850
+ },
7851
+ }, null, 2) }],
7837
7852
  };
7838
7853
  }
7839
7854
  case "get_config": {
@@ -8056,8 +8071,9 @@ export function createTaskforceMcpServer(options = {}) {
8056
8071
  case "unarchive_task": {
8057
8072
  const { id: inputId } = args;
8058
8073
  const { id, task: existingTask } = resolveTaskByIdentifierOrThrow(inputId, { notFoundMessage: buildTaskLookupFailureMessage(String(inputId || '').trim()) });
8074
+ let result;
8059
8075
  try {
8060
- taskLifecycleCommandService.unarchive(existingTask);
8076
+ result = taskLifecycleCommandService.unarchive(existingTask);
8061
8077
  }
8062
8078
  catch (error) {
8063
8079
  if (error instanceof TaskLifecycleCommandError && error.code === 'TASK_NOT_FOUND') {
@@ -8072,7 +8088,10 @@ export function createTaskforceMcpServer(options = {}) {
8072
8088
  includeStats: true,
8073
8089
  });
8074
8090
  return {
8075
- content: [{ type: "text", text: `Task ${formatTaskIdentifier(existingTask)} unarchived` }],
8091
+ content: [{ type: "text", text: JSON.stringify({
8092
+ task: buildTaskWriteSummary(result.task),
8093
+ unarchive: { unarchived: true },
8094
+ }, null, 2) }],
8076
8095
  };
8077
8096
  }
8078
8097
  case "bulk_archive": {
@@ -617,7 +617,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
617
617
  },
618
618
  }));
619
619
  register('archive_task', (context) => ({
620
- description: context.describeTool('Move a task to history by archiving it. Use status=\'done\' for completed work or status=\'cancelled\' for abandoned work. active tasks may still use status=done for final lookover without being archived.'),
620
+ description: context.describeTool('Move a task to history by archiving it. Use status=\'done\' for completed work or status=\'cancelled\' for abandoned work. Returns compact task and archive state so callers can verify the final status and isArchived=true.'),
621
621
  inputSchema: {
622
622
  properties: {
623
623
  id: stringProperty('Task ID or task reference such as T-123.'),
@@ -628,7 +628,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
628
628
  },
629
629
  }));
630
630
  register('unarchive_task', (context) => ({
631
- description: context.describeTool('Restore an archived task to the active task list.'),
631
+ description: context.describeTool('Restore an archived task to the active task list. Returns compact task and unarchive state so callers can verify isArchived=false.'),
632
632
  inputSchema: {
633
633
  properties: {
634
634
  id: stringProperty('Task ID or task reference such as T-123.'),
@@ -18,6 +18,7 @@ const MANAGED_AGENT_ALLOWED_MUTATING_TOOLS = new Set([
18
18
  'add_workstream_comment',
19
19
  'complete_workflow_step',
20
20
  'replace_task_checklist',
21
+ 'review_document',
21
22
  ]);
22
23
  export function normalizeMcpToolProfile(value, fallback) {
23
24
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
@@ -45,8 +45,8 @@ export const MCP_TOOL_REGISTRY = Object.freeze({
45
45
  add_workstream_comment: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
46
46
  archive_workstream: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
47
47
  unarchive_workstream: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
48
- update_task_workstream: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
49
- update_workstream_initiative: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
48
+ update_task_workstream: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true, { name: 'update_task' }),
49
+ update_workstream_initiative: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true, { name: 'update_workstream' }),
50
50
  get_task_checklist: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
51
51
  add_task_checklist_item: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
52
52
  update_task_checklist_item: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
@@ -58,19 +58,19 @@ export const MCP_TOOL_REGISTRY = Object.freeze({
58
58
  get_initiative_attachments: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
59
59
  save_initiative_attachment: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
60
60
  delete_initiative_attachment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
61
- search_images: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
62
- get_image: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
61
+ search_images: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
62
+ get_image: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
63
63
  search_documents: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
64
64
  get_document: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
65
65
  update_document: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
66
66
  link_document: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
67
67
  rehome_document: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
68
68
  unlink_document: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
69
- get_image_annotations: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
69
+ get_image_annotations: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
70
70
  create_annotated_attachment_session: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
71
71
  update_annotated_attachment_session: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
72
72
  delete_annotated_attachment_session: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
73
- review_image: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
73
+ review_image: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
74
74
  add_annotated_attachment_marker: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true, { name: 'review_image' }),
75
75
  bulk_add_annotated_attachment_markers: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true, { name: 'review_image' }),
76
76
  update_annotated_attachment_marker: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
@@ -82,14 +82,14 @@ export const MCP_TOOL_REGISTRY = Object.freeze({
82
82
  review_document: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
83
83
  add_document_review_comment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true, { name: 'review_document' }),
84
84
  update_document_review_thread: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
85
- update_document_review_comment: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
85
+ update_document_review_comment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
86
86
  delete_document_review_comment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
87
87
  get_schedule_board: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
88
88
  update_schedule: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
89
89
  bulk_carryover: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
90
90
  set_schedule_preferences: fullTool({ kind: 'session-only' }, true),
91
91
  resolve_profile: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
92
- generate_profile_avatar: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
92
+ generate_profile_avatar: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
93
93
  create_task: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
94
94
  start_task: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
95
95
  reopen_task: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
@@ -3,5 +3,6 @@ export interface TaskforceBuildInfo {
3
3
  gitSha: string | null;
4
4
  buildTime: string | null;
5
5
  deployId: string | null;
6
+ environmentId: string | null;
6
7
  }
7
8
  export declare function resolveBuildInfo(env?: NodeJS.ProcessEnv): TaskforceBuildInfo;
@@ -88,6 +88,9 @@ export function resolveBuildInfo(env = process.env) {
88
88
  env.CF_PAGES_URL,
89
89
  env.GITHUB_RUN_ID
90
90
  ]);
91
+ const environmentId = resolveFirstNonEmpty([
92
+ env.RAILWAY_ENVIRONMENT_ID,
93
+ ]);
91
94
  const version = explicitVersion
92
95
  ? explicitVersion
93
96
  : appendBuildMetadata(packageVersion, [deployId, gitSha, buildTimeToken(buildTime)]);
@@ -95,6 +98,7 @@ export function resolveBuildInfo(env = process.env) {
95
98
  version,
96
99
  gitSha,
97
100
  buildTime,
98
- deployId
101
+ deployId,
102
+ environmentId,
99
103
  };
100
104
  }
@@ -0,0 +1,68 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import type { RequestPerformanceDiagnostics } from '../storage/postgresRequestDiagnostics.js';
3
+ export declare const TASKFORCE_CAPACITY_TRACE_HEADER = "x-taskforce-capacity-trace";
4
+ export type CloudMcpRequestClass = 'initialize' | 'discover' | 'tools_list' | 'read' | 'write' | 'other';
5
+ export type CloudMcpOutcome = 'success' | 'rejected' | 'client_error' | 'server_error' | 'timeout_or_abort';
6
+ export type CloudMcpRejectionClass = 'rate_limited' | 'auth_or_scope' | 'protocol' | 'other_client';
7
+ type EventLoopSampler = {
8
+ enable(): void;
9
+ disable?(): void;
10
+ reset(): void;
11
+ percentile(percentile: number): number;
12
+ readonly max: number;
13
+ };
14
+ type CapacityTelemetryOptions = {
15
+ emit?: (payload: Record<string, unknown>) => void;
16
+ eventLoop?: EventLoopSampler;
17
+ now?: () => number;
18
+ cpuUsage?: typeof process.cpuUsage;
19
+ memoryUsage?: typeof process.memoryUsage;
20
+ windowMs?: number;
21
+ maxSamples?: number;
22
+ };
23
+ export declare class CloudMcpCapacityTelemetry {
24
+ private readonly emit;
25
+ private readonly eventLoop;
26
+ private readonly now;
27
+ private readonly cpuUsage;
28
+ private readonly memoryUsage;
29
+ private readonly windowMs;
30
+ private readonly maxSamples;
31
+ private activeRequests;
32
+ private maxActiveRequests;
33
+ private samples;
34
+ private completedRequests;
35
+ private requestClassCounts;
36
+ private outcomeCounts;
37
+ private rejectionCounts;
38
+ private statusClassCounts;
39
+ private windowStartedAt;
40
+ private windowCpuStartedAt;
41
+ private readonly flushTimer;
42
+ constructor(options?: CapacityTelemetryOptions);
43
+ begin(requestClass?: CloudMcpRequestClass): {
44
+ classify: (nextClass: CloudMcpRequestClass) => void;
45
+ setTraceHeaders: (res: ServerResponse, postgres?: RequestPerformanceDiagnostics | null) => void;
46
+ finish: (input: {
47
+ statusCode: number;
48
+ aborted?: boolean;
49
+ postgres?: RequestPerformanceDiagnostics | null;
50
+ }) => void;
51
+ };
52
+ snapshot(): Record<string, unknown>;
53
+ flush(): Record<string, unknown> | null;
54
+ shutdown(): void;
55
+ private flushIfDue;
56
+ private minimumPoolIdle;
57
+ }
58
+ export declare function isCloudMcpCapacityTelemetryEnabled(): boolean;
59
+ export declare function beginCloudMcpCapacityRequest(requestClass?: CloudMcpRequestClass): {
60
+ classify: (nextClass: CloudMcpRequestClass) => void;
61
+ setTraceHeaders: (res: ServerResponse, postgres?: RequestPerformanceDiagnostics | null) => void;
62
+ finish: (input: {
63
+ statusCode: number;
64
+ aborted?: boolean;
65
+ postgres?: RequestPerformanceDiagnostics | null;
66
+ }) => void;
67
+ };
68
+ export {};