@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
|
@@ -52,6 +52,7 @@ export interface SaveTaskAttachmentInput {
|
|
|
52
52
|
sizeBytes?: unknown;
|
|
53
53
|
expectedSizeBytes?: unknown;
|
|
54
54
|
expectedSha256?: unknown;
|
|
55
|
+
displayName?: unknown;
|
|
55
56
|
caption?: unknown;
|
|
56
57
|
overwrite?: unknown;
|
|
57
58
|
overwriteAssetId?: unknown;
|
|
@@ -69,6 +70,7 @@ export type SaveTaskAttachmentResult = {
|
|
|
69
70
|
expiresAt: string;
|
|
70
71
|
uploadUrl: string;
|
|
71
72
|
uploadedPath: string;
|
|
73
|
+
displayName?: string;
|
|
72
74
|
caption?: string;
|
|
73
75
|
overwrite: boolean;
|
|
74
76
|
expectedSizeBytes?: unknown;
|
|
@@ -20,7 +20,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
20
20
|
return {
|
|
21
21
|
buildAttachmentResponse,
|
|
22
22
|
async save(input) {
|
|
23
|
-
const { id: inputId, filename, content, sourcePath, uploadedPath, uploadDraftId, mimeType, sizeBytes, expectedSizeBytes, expectedSha256, caption, overwrite = false, overwriteAssetId, } = input;
|
|
23
|
+
const { id: inputId, filename, content, sourcePath, uploadedPath, uploadDraftId, mimeType, sizeBytes, expectedSizeBytes, expectedSha256, displayName, caption, overwrite = false, overwriteAssetId, } = input;
|
|
24
24
|
const { id, task } = deps.resolveTask(inputId);
|
|
25
25
|
const durableAttachmentLinksEnabled = deps.durableAttachmentLinksEnabled(id);
|
|
26
26
|
if (!filename || typeof filename !== 'string')
|
|
@@ -38,6 +38,9 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
38
38
|
? caption.trim()
|
|
39
39
|
: undefined;
|
|
40
40
|
const safeFilename = deps.sanitizeFilename(filename);
|
|
41
|
+
const safeDisplayName = typeof displayName === 'string' && displayName.trim().length > 0
|
|
42
|
+
? displayName.trim()
|
|
43
|
+
: safeFilename;
|
|
41
44
|
const stableAttachmentAssetId = deps.stableRequestId('operation', `task-attachment-asset:${id}:${safeFilename}`)?.replace(/^operation-/, 'asset-') || `asset-${randomUUID().replace(/-/g, '')}`;
|
|
42
45
|
const safeMimeType = typeof mimeType === 'string' && mimeType.trim().length > 0
|
|
43
46
|
? mimeType.trim().toLowerCase()
|
|
@@ -53,7 +56,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
53
56
|
const overwriteTitle = overwriteTarget
|
|
54
57
|
? deps.workspaceAssetStore?.getDocument?.(overwriteTarget.asset.assetId, deps.activeWorkspaceId)?.title
|
|
55
58
|
: null;
|
|
56
|
-
const canonicalName = safeCaption || overwriteTitle || safeFilename;
|
|
59
|
+
const canonicalName = safeCaption || overwriteTitle || overwriteTarget?.asset.logicalName || safeFilename;
|
|
57
60
|
const persistedCaption = isMarkdownDocument
|
|
58
61
|
? canonicalName
|
|
59
62
|
: (safeCaption || overwriteTarget?.attachment.caption || safeFilename);
|
|
@@ -63,7 +66,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
63
66
|
: `/api/taskforce/context/${encodeURIComponent(storageKey)}`),
|
|
64
67
|
fsPath: asset?.storageKey || overwriteTarget?.attachment.fsPath || storageKey,
|
|
65
68
|
caption: isMarkdownDocument ? canonicalName : persistedCaption,
|
|
66
|
-
displayName:
|
|
69
|
+
displayName: safeDisplayName,
|
|
67
70
|
originalFilename: safeFilename,
|
|
68
71
|
...(asset?.assetId ? { assetId: asset.assetId } : {}),
|
|
69
72
|
timestamp: now,
|
|
@@ -110,6 +113,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
110
113
|
expiresAt,
|
|
111
114
|
uploadUrl: objectStorageClient.createSignedPutUrl(stagedAsset.relativePath, uploadExpiresInSeconds, safeMimeType),
|
|
112
115
|
uploadedPath: stagedAsset.relativePath,
|
|
116
|
+
...(safeDisplayName !== safeFilename ? { displayName: safeDisplayName } : {}),
|
|
113
117
|
...(safeCaption ? { caption: safeCaption } : {}),
|
|
114
118
|
overwrite: overwrite === true,
|
|
115
119
|
...(expectedSizeBytes !== undefined ? { expectedSizeBytes } : {}),
|
|
@@ -218,7 +222,8 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
218
222
|
const overwriteTitle = overwriteTarget
|
|
219
223
|
? deps.workspaceAssetStore?.getDocument?.(overwriteTarget.asset.assetId, deps.activeWorkspaceId)?.title
|
|
220
224
|
: null;
|
|
221
|
-
const canonicalName = safeCaption || overwriteTitle ||
|
|
225
|
+
const canonicalName = safeCaption || overwriteTitle || overwriteTarget?.asset.logicalName
|
|
226
|
+
|| existingUploadedAsset?.logicalName || safeFilename;
|
|
222
227
|
const persistedCaption = isMarkdownDocument
|
|
223
228
|
? canonicalName
|
|
224
229
|
: (safeCaption || overwriteTarget?.attachment.caption || safeFilename);
|
|
@@ -234,7 +239,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
234
239
|
assetId: overwriteTarget?.asset.assetId || existingUploadedAsset?.assetId || embeddedAssetId || `asset-${randomUUID().replace(/-/g, '')}`,
|
|
235
240
|
workspaceId: deps.activeWorkspaceId,
|
|
236
241
|
kind,
|
|
237
|
-
logicalName:
|
|
242
|
+
logicalName: canonicalName,
|
|
238
243
|
originalFilename: safeFilename,
|
|
239
244
|
mimeType: resolvedMimeType,
|
|
240
245
|
storageProvider: 'r2',
|
|
@@ -269,7 +274,7 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
269
274
|
assetId: asset.assetId,
|
|
270
275
|
taskId: id,
|
|
271
276
|
role: 'attachment',
|
|
272
|
-
displayName: kind === 'document' ? null :
|
|
277
|
+
displayName: kind === 'document' ? null : safeDisplayName,
|
|
273
278
|
updatedAt: now,
|
|
274
279
|
deletedAt: null,
|
|
275
280
|
});
|
|
@@ -324,13 +329,14 @@ export function createTaskAttachmentCommandAdapter(deps) {
|
|
|
324
329
|
const overwriteTitle = overwriteTarget
|
|
325
330
|
? deps.workspaceAssetStore?.getDocument?.(overwriteTarget.asset.assetId, deps.activeWorkspaceId)?.title
|
|
326
331
|
: null;
|
|
327
|
-
const canonicalName = safeCaption || overwriteTitle || safeFilename;
|
|
332
|
+
const canonicalName = safeCaption || overwriteTitle || overwriteTarget?.asset.logicalName || safeFilename;
|
|
328
333
|
const persistedCaption = isMarkdownDocument
|
|
329
334
|
? canonicalName
|
|
330
335
|
: (safeCaption || overwriteTarget?.attachment.caption || safeFilename);
|
|
331
336
|
asset = await deps.canonicalAssetHelpers.ensureCanonicalTaskAttachment(relativePath, id, persistedCaption, now, 'attachment', {
|
|
332
337
|
originalFilename: safeFilename,
|
|
333
|
-
logicalName:
|
|
338
|
+
logicalName: canonicalName,
|
|
339
|
+
displayName: safeDisplayName,
|
|
334
340
|
...(!overwriteTarget ? { assetId: stableAttachmentAssetId } : {}),
|
|
335
341
|
...(overwriteTarget
|
|
336
342
|
? {
|
|
@@ -112,14 +112,14 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
112
112
|
},
|
|
113
113
|
}));
|
|
114
114
|
register('list_tasks', (context) => ({
|
|
115
|
-
description: context.describeTool('List active tasks with
|
|
115
|
+
description: context.describeTool('List active tasks with filters and bounded pagination. Use workstreamId with an ID or WS-123 and openOnly=true to exclude done/cancelled. Use format=json for programmatic use. Returns compact summaries; use get_task for details. When a task URL is returned, preserve it when presenting the task.'),
|
|
116
116
|
inputSchema: {
|
|
117
117
|
properties: {
|
|
118
118
|
format: stringProperty('Toggle output format (default: markdown).', ['markdown', 'json']),
|
|
119
119
|
category: stringProperty('Filter by category.'),
|
|
120
120
|
status: stringProperty('Filter by status.', ['task', 'on-hold', 'in-progress', 'review', 'done', 'cancelled']),
|
|
121
121
|
openOnly: booleanProperty('When true, only include open tasks (task, on-hold, in-progress, review).'),
|
|
122
|
-
assignee: stringProperty(
|
|
122
|
+
assignee: stringProperty(context.assigneeHint),
|
|
123
123
|
priority: numberProperty('Filter by priority level.'),
|
|
124
124
|
type: stringProperty('Filter by task type.', context.typeEnum),
|
|
125
125
|
workstreamId: stringProperty('Filter by workstream ID or workstream reference such as WS-123.'),
|
|
@@ -131,7 +131,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
131
131
|
},
|
|
132
132
|
}));
|
|
133
133
|
register('search_tasks', (context) => ({
|
|
134
|
-
description: context.describeTool('Search active and archived tasks by keyword
|
|
134
|
+
description: context.describeTool('Search active and archived tasks by keyword, ID, T-* reference, or provisional LT-* reference. Use scope to narrow results. Prefer this over list_tasks when looking for a specific task by name. When results include links, preserve those links when presenting tasks.'),
|
|
135
135
|
inputSchema: {
|
|
136
136
|
properties: {
|
|
137
137
|
query: stringProperty('Search term or exact task reference such as T-123 or LT-123.'),
|
|
@@ -150,6 +150,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
150
150
|
}));
|
|
151
151
|
register('link_task_relationship', (context) => ({
|
|
152
152
|
description: context.describeTool('Create an idempotent structured relationship between two tasks. blocks(A,B) is the inverse view of depends_on(B,A). Rejects self-links, missing tasks, and dependency/supersession cycles.'),
|
|
153
|
+
annotations: { idempotentHint: true },
|
|
153
154
|
inputSchema: { properties: {
|
|
154
155
|
sourceTaskId: stringProperty('Source task ID or reference.'),
|
|
155
156
|
targetTaskId: stringProperty('Target task ID or reference.'),
|
|
@@ -159,6 +160,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
159
160
|
}));
|
|
160
161
|
register('unlink_task_relationship', (context) => ({
|
|
161
162
|
description: context.describeTool('Idempotently remove a structured task relationship. The relationship is tombstoned so deletion converges across sync.'),
|
|
163
|
+
annotations: { idempotentHint: true },
|
|
162
164
|
inputSchema: { properties: {
|
|
163
165
|
sourceTaskId: stringProperty('Source task ID or reference.'),
|
|
164
166
|
targetTaskId: stringProperty('Target task ID or reference.'),
|
|
@@ -167,11 +169,11 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
167
169
|
}, required: ['sourceTaskId', 'targetTaskId', 'type'] },
|
|
168
170
|
}));
|
|
169
171
|
register('get_task', (context) => ({
|
|
170
|
-
description: context.describeTool('Get one task by ID or T-123 reference. The default compact response returns scalar
|
|
172
|
+
description: context.describeTool('Get one task by ID or T-123 reference. The default compact response returns scalar fields and collection counts. Use detail=full for comments and attachment/checklist metadata, or detail=history for chronology. Use get_task_attachments for attachment bodies; use D-123/I-123 tools for documents/images. When url is returned, preserve it in any task reference you present.'),
|
|
171
173
|
inputSchema: {
|
|
172
174
|
properties: {
|
|
173
175
|
id: stringProperty('Task ID or task reference such as T-123.'),
|
|
174
|
-
detail: stringProperty('Response detail
|
|
176
|
+
detail: stringProperty('Response detail: compact, full, or chronological history.', ['compact', 'full', 'history']),
|
|
175
177
|
},
|
|
176
178
|
required: ['id'],
|
|
177
179
|
},
|
|
@@ -185,24 +187,24 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
185
187
|
},
|
|
186
188
|
}));
|
|
187
189
|
register('get_initiative', (context) => ({
|
|
188
|
-
description: context.describeTool('Get one initiative by ID or IN-123
|
|
190
|
+
description: context.describeTool('Get one initiative by ID or IN-123. Compact returns counts; detail=planning adds attachments, paginated workstreams, and historical task counts; detail=history returns bounded chronology; full returns the compatible rich response.'),
|
|
189
191
|
inputSchema: {
|
|
190
192
|
properties: {
|
|
191
193
|
id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
|
|
192
|
-
detail: stringProperty('Response detail
|
|
193
|
-
limit: { type: 'integer', minimum: 1, maximum: 100, description: '
|
|
194
|
-
offset: { type: 'integer', minimum: 0, description: '
|
|
194
|
+
detail: stringProperty('Response detail: compact, planning, history, or full.', ['compact', 'planning', 'history', 'full']),
|
|
195
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Planning/history page size (default 25; max 100).' },
|
|
196
|
+
offset: { type: 'integer', minimum: 0, description: 'Planning/history page offset (default 0).' },
|
|
195
197
|
},
|
|
196
198
|
required: ['id'],
|
|
197
199
|
},
|
|
198
200
|
}));
|
|
199
201
|
register('create_initiative', (context) => ({
|
|
200
|
-
description: context.describeTool('Create a new initiative.
|
|
202
|
+
description: context.describeTool('Create a new initiative. Pass idempotencyKey when retrying after an ambiguous timeout; reuse it only with the exact same payload.'),
|
|
201
203
|
inputSchema: {
|
|
202
204
|
properties: {
|
|
203
205
|
title: stringProperty('Initiative title.'),
|
|
204
206
|
description: stringProperty('Optional initiative description.'),
|
|
205
|
-
ownerId: stringProperty(
|
|
207
|
+
ownerId: stringProperty(context.assigneeHint),
|
|
206
208
|
order: numberProperty('Optional sort order among initiatives.'),
|
|
207
209
|
attachments: arrayProperty('Optional attachment descriptors to store on the initiative. Prefer save_initiative_attachment for generated content or files.', {
|
|
208
210
|
type: 'object',
|
|
@@ -216,6 +218,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
216
218
|
},
|
|
217
219
|
required: ['path'],
|
|
218
220
|
}),
|
|
221
|
+
idempotencyKey: { type: 'string', minLength: 1, maxLength: 128, description: 'Optional opaque retry key scoped to the active workspace, actor, and tool.' },
|
|
219
222
|
},
|
|
220
223
|
required: ['title'],
|
|
221
224
|
},
|
|
@@ -227,9 +230,9 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
227
230
|
id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
|
|
228
231
|
title: stringProperty('Updated initiative title.'),
|
|
229
232
|
description: stringProperty('Updated initiative description.'),
|
|
230
|
-
ownerId: stringProperty(
|
|
233
|
+
ownerId: stringProperty(context.assigneeHint),
|
|
231
234
|
order: numberProperty('Optional sort order among initiatives.'),
|
|
232
|
-
attachments: arrayProperty('Replacement attachment descriptors for the initiative. Prefer save_initiative_attachment
|
|
235
|
+
attachments: arrayProperty('Replacement attachment descriptors for the initiative. Prefer save_initiative_attachment for generated content or files. Removing managed attachments requires delete_initiative_attachment in the extended profile.', {
|
|
233
236
|
type: 'object',
|
|
234
237
|
properties: {
|
|
235
238
|
path: { type: 'string' },
|
|
@@ -246,11 +249,12 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
246
249
|
},
|
|
247
250
|
}));
|
|
248
251
|
register('add_initiative_comment', (context) => ({
|
|
249
|
-
description: context.describeTool('Add a comment to an initiative.
|
|
252
|
+
description: context.describeTool('Add a comment to an initiative. Pass idempotencyKey when retrying after an ambiguous timeout; reuse it only with the exact same target and text.'),
|
|
250
253
|
inputSchema: {
|
|
251
254
|
properties: {
|
|
252
255
|
id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
|
|
253
256
|
text: stringProperty('Comment text.'),
|
|
257
|
+
idempotencyKey: { type: 'string', minLength: 1, maxLength: 128, description: 'Optional opaque retry key scoped to the active workspace, actor, and tool.' },
|
|
254
258
|
},
|
|
255
259
|
required: ['id', 'text'],
|
|
256
260
|
},
|
|
@@ -282,24 +286,24 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
282
286
|
},
|
|
283
287
|
}));
|
|
284
288
|
register('get_workstream', (context) => ({
|
|
285
|
-
description: context.describeTool('Get one workstream by ID or WS-123
|
|
289
|
+
description: context.describeTool('Get one workstream by ID or WS-123. Compact returns a summary and task count; detail=planning adds attachments and historical counts; detail=history returns bounded chronology; full returns the compatible rich response. Use list_tasks with workstreamId for active tasks.'),
|
|
286
290
|
inputSchema: {
|
|
287
291
|
properties: {
|
|
288
292
|
id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
|
|
289
|
-
detail: stringProperty('Response detail
|
|
290
|
-
limit: { type: 'integer', minimum: 1, maximum: 100, description: '
|
|
291
|
-
offset: { type: 'integer', minimum: 0, description: '
|
|
293
|
+
detail: stringProperty('Response detail: compact, planning, history, or full.', ['compact', 'planning', 'history', 'full']),
|
|
294
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, description: 'History page size (default 25; max 100).' },
|
|
295
|
+
offset: { type: 'integer', minimum: 0, description: 'History page offset (default 0).' },
|
|
292
296
|
},
|
|
293
297
|
required: ['id'],
|
|
294
298
|
},
|
|
295
299
|
}));
|
|
296
300
|
register('create_workstream', (context) => ({
|
|
297
|
-
description: context.describeTool('Create a new workstream.
|
|
301
|
+
description: context.describeTool('Create a new workstream. Pass idempotencyKey when retrying after an ambiguous timeout; reuse it only with the exact same payload.'),
|
|
298
302
|
inputSchema: {
|
|
299
303
|
properties: {
|
|
300
304
|
title: stringProperty('Workstream title.'),
|
|
301
305
|
description: stringProperty('Optional workstream description.'),
|
|
302
|
-
ownerId: stringProperty(
|
|
306
|
+
ownerId: stringProperty(context.assigneeHint),
|
|
303
307
|
initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123.'),
|
|
304
308
|
order: numberProperty('Optional sort order among workstreams.'),
|
|
305
309
|
attachments: arrayProperty('Optional attachment descriptors to store on the workstream. Prefer save_workstream_attachment for generated content or files.', {
|
|
@@ -314,6 +318,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
314
318
|
},
|
|
315
319
|
required: ['path'],
|
|
316
320
|
}),
|
|
321
|
+
idempotencyKey: { type: 'string', minLength: 1, maxLength: 128, description: 'Optional opaque retry key scoped to the active workspace, actor, and tool.' },
|
|
317
322
|
},
|
|
318
323
|
required: ['title'],
|
|
319
324
|
},
|
|
@@ -325,10 +330,10 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
325
330
|
id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
|
|
326
331
|
title: stringProperty('Updated workstream title.'),
|
|
327
332
|
description: stringProperty('Updated workstream description.'),
|
|
328
|
-
ownerId: stringProperty(
|
|
333
|
+
ownerId: stringProperty(context.assigneeHint),
|
|
329
334
|
initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123. Pass an empty string to unlink.'),
|
|
330
335
|
order: numberProperty('Optional sort order among workstreams.'),
|
|
331
|
-
attachments: arrayProperty('Replacement attachment descriptors for the workstream. Prefer save_workstream_attachment
|
|
336
|
+
attachments: arrayProperty('Replacement attachment descriptors for the workstream. Prefer save_workstream_attachment for generated content or files. Removing managed attachments requires delete_workstream_attachment in the extended profile.', {
|
|
332
337
|
type: 'object',
|
|
333
338
|
properties: {
|
|
334
339
|
path: { type: 'string' },
|
|
@@ -345,11 +350,12 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
345
350
|
},
|
|
346
351
|
}));
|
|
347
352
|
register('add_workstream_comment', (context) => ({
|
|
348
|
-
description: context.describeTool('Add a comment to a workstream.
|
|
353
|
+
description: context.describeTool('Add a comment to a workstream. Pass idempotencyKey when retrying after an ambiguous timeout; reuse it only with the exact same target and text.'),
|
|
349
354
|
inputSchema: {
|
|
350
355
|
properties: {
|
|
351
356
|
id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
|
|
352
357
|
text: stringProperty('Comment text.'),
|
|
358
|
+
idempotencyKey: { type: 'string', minLength: 1, maxLength: 128, description: 'Optional opaque retry key scoped to the active workspace, actor, and tool.' },
|
|
353
359
|
},
|
|
354
360
|
required: ['id', 'text'],
|
|
355
361
|
},
|
|
@@ -374,6 +380,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
374
380
|
}));
|
|
375
381
|
register('update_task_workstream', (context) => ({
|
|
376
382
|
description: context.describeTool('Set or clear the workstream for one task. Pass workstreamId to assign or reassign; pass an empty string or null to unlink the task from its current workstream.'),
|
|
383
|
+
annotations: { idempotentHint: true },
|
|
377
384
|
inputSchema: {
|
|
378
385
|
properties: {
|
|
379
386
|
taskId: stringProperty('Task ID or task reference such as T-123.'),
|
|
@@ -447,13 +454,13 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
447
454
|
inputSchema: {
|
|
448
455
|
properties: {
|
|
449
456
|
taskId: stringProperty('Task ID or task reference such as T-123.'),
|
|
450
|
-
id: stringProperty('Alias for taskId.
|
|
451
|
-
date: nullableStringProperty('
|
|
452
|
-
scheduledDate: nullableStringProperty('Alias for date.
|
|
457
|
+
id: stringProperty('Alias for taskId.'),
|
|
458
|
+
date: nullableStringProperty('YYYY-MM-DD; null or empty with taskId unschedules.'),
|
|
459
|
+
scheduledDate: nullableStringProperty('Alias for date.'),
|
|
453
460
|
order: numberProperty('Optional position within that day.'),
|
|
454
|
-
orderInDay: numberProperty('Alias for order.
|
|
455
|
-
taskIds: arrayProperty('Ordered task IDs or
|
|
456
|
-
moves: arrayProperty('Advanced reorder moves
|
|
461
|
+
orderInDay: numberProperty('Alias for order.'),
|
|
462
|
+
taskIds: arrayProperty('Ordered task IDs or references replacing one day.', { type: 'string' }),
|
|
463
|
+
moves: arrayProperty('Advanced reorder moves using task, date, and order aliases.', {
|
|
457
464
|
type: 'object',
|
|
458
465
|
properties: {
|
|
459
466
|
id: { type: 'string' },
|
|
@@ -513,7 +520,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
513
520
|
},
|
|
514
521
|
}));
|
|
515
522
|
register('create_task', (context) => ({
|
|
516
|
-
description: context.describeTool('Create a
|
|
523
|
+
description: context.describeTool('Create a task. For retries, the same idempotencyKey and payload return the original; changed payloads conflict. Taxonomy enums are current; use get_config for metadata or after validation errors.'),
|
|
517
524
|
inputSchema: {
|
|
518
525
|
properties: {
|
|
519
526
|
title: stringProperty('Task title.'),
|
|
@@ -524,8 +531,8 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
524
531
|
description: 'Optional opaque retry key scoped to the active workspace and AI profile.',
|
|
525
532
|
},
|
|
526
533
|
description: stringProperty('Optional task description.'),
|
|
527
|
-
assignee: stringProperty(
|
|
528
|
-
workstreamId: stringProperty('
|
|
534
|
+
assignee: stringProperty(context.assigneeHint),
|
|
535
|
+
workstreamId: stringProperty('Workstream ID or WS-123; use to create the task directly inside a workstream. Search list_workstreams if needed.'),
|
|
529
536
|
category: stringProperty('Task category value.', context.categoryEnum),
|
|
530
537
|
type: stringProperty('Task type value.', context.typeEnum),
|
|
531
538
|
priority: numberProperty(context.priorityDesc),
|
|
@@ -548,6 +555,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
548
555
|
}));
|
|
549
556
|
register('reopen_task', (context) => ({
|
|
550
557
|
description: context.describeTool('Safely resume reviewed work in one call. Reopens archived done tasks or active/archived review tasks to in-progress, preserves an existing assignee, and claims only unassigned work. Cancelled tasks are rejected. Already in-progress tasks are idempotent no-ops.'),
|
|
558
|
+
annotations: { idempotentHint: true },
|
|
551
559
|
inputSchema: {
|
|
552
560
|
properties: {
|
|
553
561
|
id: stringProperty('Task ID or task reference such as T-123.'),
|
|
@@ -562,7 +570,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
562
570
|
id: stringProperty('Task ID or task reference such as T-123.'),
|
|
563
571
|
title: stringProperty('Updated task title.'),
|
|
564
572
|
description: stringProperty('Updated task description.'),
|
|
565
|
-
assignee: stringProperty(
|
|
573
|
+
assignee: stringProperty(context.assigneeHint),
|
|
566
574
|
workstreamId: stringProperty('Updated workstream ID or workstream reference such as WS-123. Pass an empty string to unlink the task. Call list_workstreams with query first if you only know the workstream name.'),
|
|
567
575
|
category: stringProperty('Updated category value.', context.categoryEnum),
|
|
568
576
|
type: stringProperty('Updated task type value.', context.typeEnum),
|
|
@@ -576,7 +584,7 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
576
584
|
},
|
|
577
585
|
}));
|
|
578
586
|
register('finish_task', (context) => ({
|
|
579
|
-
description: context.describeTool('
|
|
587
|
+
description: context.describeTool('Default end-of-task handoff. Use outcome=\'done\' for completed work or outcome=\'review\' for validation. Both remain active and unarchived; use archive_task for history. Archived tasks require reopen_task first.'),
|
|
580
588
|
inputSchema: {
|
|
581
589
|
properties: {
|
|
582
590
|
id: stringProperty('Task ID or task reference such as T-123.'),
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -52,10 +52,10 @@ export const MCP_TOOL_REGISTRY = Object.freeze({
|
|
|
52
52
|
replace_task_checklist: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
|
|
53
53
|
get_task_attachments: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
|
|
54
54
|
get_workstream_attachments: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
|
|
55
|
-
save_workstream_attachment:
|
|
55
|
+
save_workstream_attachment: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
|
|
56
56
|
delete_workstream_attachment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
|
|
57
57
|
get_initiative_attachments: extendedTool({ kind: 'scope', scope: 'tasks:read' }, false),
|
|
58
|
-
save_initiative_attachment:
|
|
58
|
+
save_initiative_attachment: defaultTool({ kind: 'scope', scope: 'tasks:write' }, true),
|
|
59
59
|
delete_initiative_attachment: extendedTool({ kind: 'scope', scope: 'tasks:write' }, true),
|
|
60
60
|
search_images: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
|
|
61
61
|
get_image: defaultTool({ kind: 'scope', scope: 'tasks:read' }, false),
|
|
@@ -1160,6 +1160,22 @@ export function ensureWorkspaceAssetsSchema(db) {
|
|
|
1160
1160
|
ON workspace_asset_links(tenant_id, workspace_id, asset_id, deleted_at, updated_at);
|
|
1161
1161
|
CREATE INDEX IF NOT EXISTS idx_workspace_asset_links_workspace_updated
|
|
1162
1162
|
ON workspace_asset_links(tenant_id, workspace_id, updated_at, asset_id);
|
|
1163
|
+
|
|
1164
|
+
CREATE TABLE IF NOT EXISTS workspace_asset_upload_drafts (
|
|
1165
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
1166
|
+
workspace_id TEXT NOT NULL,
|
|
1167
|
+
draft_id TEXT NOT NULL,
|
|
1168
|
+
asset_id TEXT NOT NULL,
|
|
1169
|
+
storage_key TEXT NOT NULL,
|
|
1170
|
+
purpose TEXT NOT NULL,
|
|
1171
|
+
created_by_user_id TEXT NOT NULL,
|
|
1172
|
+
created_at TEXT NOT NULL,
|
|
1173
|
+
retired_at TEXT,
|
|
1174
|
+
PRIMARY KEY (tenant_id, workspace_id, draft_id, asset_id)
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_asset_upload_drafts_expiry
|
|
1178
|
+
ON workspace_asset_upload_drafts(tenant_id, workspace_id, retired_at, created_at);
|
|
1163
1179
|
`);
|
|
1164
1180
|
const linkColumns = db.prepare(`PRAGMA table_info(workspace_asset_links)`).all();
|
|
1165
1181
|
const hasDisplayNameColumn = linkColumns.some((c) => c.name === 'display_name');
|
|
@@ -2801,6 +2817,9 @@ export function ensureDocumentReviewSessionsSchema(db) {
|
|
|
2801
2817
|
thread_status_updated_at TEXT,
|
|
2802
2818
|
resolved_at TEXT,
|
|
2803
2819
|
resolved_by_actor_id TEXT,
|
|
2820
|
+
idempotency_key TEXT,
|
|
2821
|
+
idempotency_fingerprint TEXT,
|
|
2822
|
+
idempotency_actor_scope TEXT NOT NULL DEFAULT '',
|
|
2804
2823
|
order_index INTEGER NOT NULL DEFAULT 0,
|
|
2805
2824
|
created_at TEXT NOT NULL,
|
|
2806
2825
|
updated_at TEXT NOT NULL,
|
|
@@ -2830,6 +2849,22 @@ export function ensureDocumentReviewSessionsSchema(db) {
|
|
|
2830
2849
|
if (!commentColumnNames.has('deleted_by_actor_id')) {
|
|
2831
2850
|
addCommentColumn('deleted_by_actor_id');
|
|
2832
2851
|
}
|
|
2852
|
+
if (!commentColumnNames.has('idempotency_key')) {
|
|
2853
|
+
addCommentColumn('idempotency_key');
|
|
2854
|
+
}
|
|
2855
|
+
if (!commentColumnNames.has('idempotency_fingerprint')) {
|
|
2856
|
+
addCommentColumn('idempotency_fingerprint');
|
|
2857
|
+
}
|
|
2858
|
+
if (!commentColumnNames.has('idempotency_actor_scope')) {
|
|
2859
|
+
const ifNotExists = db.provider === 'postgres' ? ' IF NOT EXISTS' : '';
|
|
2860
|
+
db.exec(`ALTER TABLE document_review_comments ADD COLUMN${ifNotExists} idempotency_actor_scope TEXT NOT NULL DEFAULT ''`);
|
|
2861
|
+
}
|
|
2862
|
+
db.exec(`
|
|
2863
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_document_review_comments_idempotency
|
|
2864
|
+
ON document_review_comments (
|
|
2865
|
+
tenant_id, workspace_id, idempotency_actor_scope, idempotency_key
|
|
2866
|
+
)
|
|
2867
|
+
`);
|
|
2833
2868
|
if (!commentColumnNames.has('parent_comment_id')) {
|
|
2834
2869
|
addCommentColumn('parent_comment_id');
|
|
2835
2870
|
}
|
|
@@ -12,13 +12,13 @@ export declare const APP_GATE_DEFINITIONS: {
|
|
|
12
12
|
readonly removalMilestone: "Document Manager GA";
|
|
13
13
|
};
|
|
14
14
|
readonly 'workflow_manager.workspace_access': {
|
|
15
|
-
readonly enabled:
|
|
15
|
+
readonly enabled: false;
|
|
16
16
|
readonly description: "Workflow Manager workspace readiness gate.";
|
|
17
17
|
readonly owner: "taskforce";
|
|
18
18
|
readonly removalMilestone: "Workflow Manager GA";
|
|
19
19
|
};
|
|
20
20
|
readonly 'taskforce_agents.workspace_access': {
|
|
21
|
-
readonly enabled:
|
|
21
|
+
readonly enabled: false;
|
|
22
22
|
readonly description: "Taskforce Agents workspace readiness gate.";
|
|
23
23
|
readonly owner: "taskforce";
|
|
24
24
|
readonly removalMilestone: "Taskforce Agents workspace GA";
|
|
@@ -12,13 +12,13 @@ export const APP_GATE_DEFINITIONS = {
|
|
|
12
12
|
removalMilestone: 'Document Manager GA'
|
|
13
13
|
},
|
|
14
14
|
'workflow_manager.workspace_access': {
|
|
15
|
-
enabled:
|
|
15
|
+
enabled: false,
|
|
16
16
|
description: 'Workflow Manager workspace readiness gate.',
|
|
17
17
|
owner: 'taskforce',
|
|
18
18
|
removalMilestone: 'Workflow Manager GA'
|
|
19
19
|
},
|
|
20
20
|
'taskforce_agents.workspace_access': {
|
|
21
|
-
enabled:
|
|
21
|
+
enabled: false,
|
|
22
22
|
description: 'Taskforce Agents workspace readiness gate.',
|
|
23
23
|
owner: 'taskforce',
|
|
24
24
|
removalMilestone: 'Taskforce Agents workspace GA'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AccountAccessStatus } from '../core/types.js';
|
|
2
|
+
export interface AppAccessDeniedResponse {
|
|
3
|
+
statusCode: 403;
|
|
4
|
+
body: {
|
|
5
|
+
error: string;
|
|
6
|
+
code: string;
|
|
7
|
+
gate: AccountAccessStatus['gate'];
|
|
8
|
+
canAccessPlans: boolean;
|
|
9
|
+
planSelectionRequired: boolean;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveAppAccessDeniedResponse(accountAccess: AccountAccessStatus): AppAccessDeniedResponse | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function resolveAppAccessDeniedResponse(accountAccess) {
|
|
2
|
+
if (accountAccess.canAccessApp)
|
|
3
|
+
return null;
|
|
4
|
+
const code = accountAccess.gate === 'plan_selection_required'
|
|
5
|
+
? 'PLAN_SELECTION_REQUIRED'
|
|
6
|
+
: accountAccess.gate === 'checkout_pending'
|
|
7
|
+
? 'CHECKOUT_PENDING'
|
|
8
|
+
: accountAccess.gate === 'misconfigured'
|
|
9
|
+
? 'ACCOUNT_ACCESS_MISCONFIGURED'
|
|
10
|
+
: 'ACCOUNT_ENTITLEMENT_REQUIRED';
|
|
11
|
+
return {
|
|
12
|
+
statusCode: 403,
|
|
13
|
+
body: {
|
|
14
|
+
error: accountAccess.message || 'This account cannot access the app.',
|
|
15
|
+
code,
|
|
16
|
+
gate: accountAccess.gate,
|
|
17
|
+
canAccessPlans: accountAccess.canAccessPlans,
|
|
18
|
+
planSelectionRequired: accountAccess.planSelectionRequired
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TaskforceCore } from '../core/Taskforce.js';
|
|
2
|
+
import type { RequestAccessContext } from './auth.js';
|
|
3
|
+
export interface AuthorizedRequestAccess extends RequestAccessContext {
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
accountDisabled?: boolean;
|
|
6
|
+
workspaceDisabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type WorkspaceAccessCore = Pick<TaskforceCore, 'resolveUserAccess' | 'getSystemRole'>;
|
|
9
|
+
export declare function resolveAuthorizedWorkspaceAccess(core: WorkspaceAccessCore, access: AuthorizedRequestAccess, requestedWorkspaceId: string): AuthorizedRequestAccess;
|
|
10
|
+
export declare function isWorkspaceAccessRecoveryRoute(pathname: string): boolean;
|
|
11
|
+
export declare function resolveDisabledAccessError(access: Pick<AuthorizedRequestAccess, 'disabled' | 'accountDisabled' | 'workspaceDisabled'>, pathname: string): string | null;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function resolveAuthorizedWorkspaceAccess(core, access, requestedWorkspaceId) {
|
|
2
|
+
if (!access.authenticated || !access.userId || access.userId === 'anonymous')
|
|
3
|
+
return access;
|
|
4
|
+
const trimmedRequestedWorkspaceId = String(requestedWorkspaceId || '').trim();
|
|
5
|
+
const candidateWorkspaceIds = [
|
|
6
|
+
trimmedRequestedWorkspaceId,
|
|
7
|
+
String(access.workspaceId || '').trim()
|
|
8
|
+
].filter((workspaceId, index, values) => Boolean(workspaceId) && values.indexOf(workspaceId) === index);
|
|
9
|
+
for (const workspaceId of candidateWorkspaceIds) {
|
|
10
|
+
const resolved = core.resolveUserAccess(access.userId, workspaceId, access.role);
|
|
11
|
+
if (!resolved)
|
|
12
|
+
continue;
|
|
13
|
+
return {
|
|
14
|
+
...access,
|
|
15
|
+
role: resolved.role,
|
|
16
|
+
workspaceId: resolved.workspaceId,
|
|
17
|
+
userId: resolved.userId,
|
|
18
|
+
systemRole: core.getSystemRole(resolved.userId),
|
|
19
|
+
disabled: resolved.disabled,
|
|
20
|
+
accountDisabled: resolved.accountDisabled ?? resolved.disabled,
|
|
21
|
+
workspaceDisabled: resolved.workspaceDisabled ?? false
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return access;
|
|
25
|
+
}
|
|
26
|
+
export function isWorkspaceAccessRecoveryRoute(pathname) {
|
|
27
|
+
return pathname === '/api/taskforce/workspaces'
|
|
28
|
+
|| pathname === '/api/taskforce/session/workspace';
|
|
29
|
+
}
|
|
30
|
+
export function resolveDisabledAccessError(access, pathname) {
|
|
31
|
+
if (pathname.startsWith('/api/taskforce/auth/'))
|
|
32
|
+
return null;
|
|
33
|
+
if (access.accountDisabled ?? access.disabled) {
|
|
34
|
+
return 'Forbidden: user account is disabled.';
|
|
35
|
+
}
|
|
36
|
+
if (access.workspaceDisabled && !isWorkspaceAccessRecoveryRoute(pathname)) {
|
|
37
|
+
return 'Forbidden: workspace access is not active for this user.';
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TaskforceCore } from '../core/Taskforce.js';
|
|
2
|
+
import { type AuthorizedRequestAccess } from './authorizedWorkspaceAccess.js';
|
|
3
|
+
export type CloudCollectionReadOperation = 'tasks' | 'archive' | 'deleted' | 'planning-bootstrap';
|
|
4
|
+
export interface CloudCollectionReadRequest {
|
|
5
|
+
operation: CloudCollectionReadOperation;
|
|
6
|
+
pathname: string;
|
|
7
|
+
requestedWorkspaceId: string;
|
|
8
|
+
access: AuthorizedRequestAccess;
|
|
9
|
+
performanceTrace: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CloudCollectionReadResponse {
|
|
12
|
+
statusCode: number;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
body: string;
|
|
15
|
+
workspaceId: string;
|
|
16
|
+
performance?: {
|
|
17
|
+
durationMs: number;
|
|
18
|
+
details: Record<string, number>;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
type CloudCollectionReadCore = Pick<TaskforceCore, 'resolveUserAccess' | 'getSystemRole' | 'getAccountAccessStatus' | 'listTasks' | 'listArchive' | 'listDeletedTasksReadOnly' | 'listPlanningBootstrap'>;
|
|
22
|
+
export declare function resolveCloudCollectionReadOperation(method: string | undefined, pathname: string): CloudCollectionReadOperation | null;
|
|
23
|
+
export declare function executeCloudCollectionRead(core: CloudCollectionReadCore, request: CloudCollectionReadRequest): CloudCollectionReadResponse;
|
|
24
|
+
export declare function cloudCollectionReadRoute(operation: CloudCollectionReadOperation): string;
|
|
25
|
+
export {};
|