@taskforcehq/taskforce 0.3.306 → 0.3.308
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/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.d.ts +20 -15
- package/dist/components/views/PlanComparisonPage.js +221 -82
- package/dist/components/views/PlanComparisonPage.test.js +268 -14
- package/dist/components/views/PlansPage.js +16 -10
- package/dist/components/views/PlansPage.test.js +4 -2
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +30 -0
- package/dist/core/GlobalSettingsService.test.d.ts +1 -0
- package/dist/core/GlobalSettingsService.test.js +45 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +20 -0
- package/dist/core/PlanEntitlementService.js +215 -19
- package/dist/core/PlanFeatureCatalog.test.js +55 -0
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +204 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +781 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +58 -15
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +328 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +5 -0
- package/dist/server/routes/authSupport.js +13 -1
- package/dist/server/routes/billing.d.ts +1 -0
- package/dist/server/routes/billing.js +116 -63
- package/dist/server/routes/billing.test.js +162 -11
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +972 -23
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/agent-logos/ChatGPT.png +0 -0
- package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
- package/dist/ui/agent-logos/antigravity.jpeg +0 -0
- package/dist/ui/agent-logos/claude-code.jpeg +0 -0
- package/dist/ui/agent-logos/codex.jpeg +0 -0
- package/dist/ui/agent-logos/cursor.png +0 -0
- package/dist/ui/agent-logos/openclaw.jpeg +0 -0
- package/dist/ui/agent-logos/windsurf.png +0 -0
- package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
- package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
- package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
- package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
- package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
- package/dist/ui/assets/index-MAHKvFqp.js +4 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/og-image.png +0 -0
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const AI_PROFILE_SURFACE_TYPES: readonly ["chat_app", "ide", "
|
|
1
|
+
export declare const AI_PROFILE_SURFACE_TYPES: readonly ["chat_app", "ide", "cli", "coding_tool", "agent"];
|
|
2
2
|
export type AiProfileSurfaceType = typeof AI_PROFILE_SURFACE_TYPES[number];
|
|
3
3
|
export type AiProfileSurfaceTypeSection = AiProfileSurfaceType | 'unclassified';
|
|
4
4
|
export declare const AI_PROFILE_SURFACE_TYPE_SECTION_ORDER: AiProfileSurfaceTypeSection[];
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
export const AI_PROFILE_SURFACE_TYPES = [
|
|
2
2
|
'chat_app',
|
|
3
3
|
'ide',
|
|
4
|
+
'cli',
|
|
4
5
|
'coding_tool',
|
|
5
|
-
'agent'
|
|
6
|
-
'other'
|
|
6
|
+
'agent'
|
|
7
7
|
];
|
|
8
8
|
export const AI_PROFILE_SURFACE_TYPE_SECTION_ORDER = [
|
|
9
9
|
'chat_app',
|
|
10
10
|
'ide',
|
|
11
|
+
'cli',
|
|
11
12
|
'coding_tool',
|
|
12
13
|
'agent',
|
|
13
|
-
'other',
|
|
14
14
|
'unclassified'
|
|
15
15
|
];
|
|
16
16
|
const AI_PROFILE_SURFACE_TYPE_LABELS = {
|
|
17
|
-
chat_app: 'Chat
|
|
17
|
+
chat_app: 'Chat App',
|
|
18
18
|
ide: 'IDE',
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
cli: 'CLI',
|
|
20
|
+
coding_tool: 'Coding Tool',
|
|
21
|
+
agent: 'Agent'
|
|
22
22
|
};
|
|
23
23
|
const AI_PROFILE_SURFACE_TYPE_SECTION_LABELS = {
|
|
24
24
|
chat_app: 'Chat Apps',
|
|
25
25
|
ide: 'IDEs',
|
|
26
|
+
cli: 'CLI',
|
|
26
27
|
coding_tool: 'Coding Tools',
|
|
27
28
|
agent: 'Agents',
|
|
28
|
-
other: 'Other',
|
|
29
29
|
unclassified: 'Unclassified'
|
|
30
30
|
};
|
|
31
31
|
export function isAiProfileSurfaceType(value) {
|
|
@@ -36,6 +36,8 @@ export function parseAiProfileSurfaceType(value) {
|
|
|
36
36
|
if (typeof value !== 'string')
|
|
37
37
|
return null;
|
|
38
38
|
const normalized = value.trim();
|
|
39
|
+
if (normalized === 'other')
|
|
40
|
+
return 'agent';
|
|
39
41
|
return isAiProfileSurfaceType(normalized) ? normalized : null;
|
|
40
42
|
}
|
|
41
43
|
export function formatAiProfileSurfaceTypeLabel(surfaceType) {
|
|
@@ -9,7 +9,8 @@ export function toPublicObjectStorageConfig(config) {
|
|
|
9
9
|
endpoint: config.r2.endpoint,
|
|
10
10
|
keyPrefix: config.r2.keyPrefix,
|
|
11
11
|
signedUploadTtlSeconds: config.r2.signedUploadTtlSeconds,
|
|
12
|
-
signedDownloadTtlSeconds: config.r2.signedDownloadTtlSeconds
|
|
12
|
+
signedDownloadTtlSeconds: config.r2.signedDownloadTtlSeconds,
|
|
13
|
+
...(typeof config.r2.requestTimeoutMs === 'number' ? { requestTimeoutMs: config.r2.requestTimeoutMs } : {})
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
}
|
|
@@ -8,6 +8,7 @@ export declare class ObjectStorageClient {
|
|
|
8
8
|
constructor(config: ResolvedObjectStorageConfig);
|
|
9
9
|
provider(): ObjectStorageProvider;
|
|
10
10
|
keyForRelativePath(relativePath: string): string;
|
|
11
|
+
private requestTimeoutMs;
|
|
11
12
|
putObject(relativePath: string, body: Buffer, contentType: string): Promise<void>;
|
|
12
13
|
getObject(relativePath: string): Promise<StoredObject | null>;
|
|
13
14
|
objectExists(relativePath: string): Promise<boolean>;
|
|
@@ -4,6 +4,7 @@ const AWS_SERVICE = 's3';
|
|
|
4
4
|
const AWS_REGION = 'auto';
|
|
5
5
|
const TRANSIENT_GET_OBJECT_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
6
6
|
const MAX_GET_OBJECT_ATTEMPTS = 3;
|
|
7
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
7
8
|
function sha256Hex(value) {
|
|
8
9
|
return createHash('sha256').update(value).digest('hex');
|
|
9
10
|
}
|
|
@@ -77,6 +78,33 @@ function shouldRetryGetObjectError(error) {
|
|
|
77
78
|
|| message.includes('ecanceled')
|
|
78
79
|
|| message.includes('und_err');
|
|
79
80
|
}
|
|
81
|
+
async function fetchWithTimeout(url, init, timeoutMsRaw) {
|
|
82
|
+
const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
|
|
83
|
+
? Math.floor(timeoutMsRaw)
|
|
84
|
+
: DEFAULT_REQUEST_TIMEOUT_MS;
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeout = setTimeout(() => {
|
|
87
|
+
controller.abort(new Error(`R2 request timed out after ${timeoutMs}ms`));
|
|
88
|
+
}, timeoutMs);
|
|
89
|
+
try {
|
|
90
|
+
return await fetch(url, {
|
|
91
|
+
...init,
|
|
92
|
+
signal: controller.signal
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (controller.signal.aborted) {
|
|
97
|
+
const reason = controller.signal.reason;
|
|
98
|
+
if (reason instanceof Error)
|
|
99
|
+
throw reason;
|
|
100
|
+
throw new Error(`R2 request timed out after ${timeoutMs}ms`);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
clearTimeout(timeout);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
80
108
|
function signRequest(params) {
|
|
81
109
|
const amzDate = createAmzDate(params.now);
|
|
82
110
|
const dateStamp = toDateStamp(amzDate);
|
|
@@ -117,6 +145,11 @@ export class ObjectStorageClient {
|
|
|
117
145
|
keyForRelativePath(relativePath) {
|
|
118
146
|
return buildObjectKey(this.config, relativePath);
|
|
119
147
|
}
|
|
148
|
+
requestTimeoutMs() {
|
|
149
|
+
return this.config.provider === 'r2'
|
|
150
|
+
? Number(this.config.r2?.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS)
|
|
151
|
+
: DEFAULT_REQUEST_TIMEOUT_MS;
|
|
152
|
+
}
|
|
120
153
|
async putObject(relativePath, body, contentType) {
|
|
121
154
|
if (this.config.provider !== 'r2' || !this.config.r2) {
|
|
122
155
|
throw new Error('R2 object storage is not configured.');
|
|
@@ -131,7 +164,7 @@ export class ObjectStorageClient {
|
|
|
131
164
|
secretAccessKey: this.config.r2.secretAccessKey,
|
|
132
165
|
payloadHash
|
|
133
166
|
});
|
|
134
|
-
const response = await
|
|
167
|
+
const response = await fetchWithTimeout(url, {
|
|
135
168
|
method: 'PUT',
|
|
136
169
|
headers: {
|
|
137
170
|
'authorization': signed.authorization,
|
|
@@ -140,7 +173,7 @@ export class ObjectStorageClient {
|
|
|
140
173
|
'content-type': contentType
|
|
141
174
|
},
|
|
142
175
|
body: new Uint8Array(body)
|
|
143
|
-
});
|
|
176
|
+
}, this.requestTimeoutMs());
|
|
144
177
|
if (!response.ok) {
|
|
145
178
|
const text = await response.text().catch(() => '');
|
|
146
179
|
throw new Error(`Failed to upload object (${response.status}): ${text || response.statusText}`);
|
|
@@ -163,14 +196,14 @@ export class ObjectStorageClient {
|
|
|
163
196
|
payloadHash
|
|
164
197
|
});
|
|
165
198
|
try {
|
|
166
|
-
const response = await
|
|
199
|
+
const response = await fetchWithTimeout(url, {
|
|
167
200
|
method: 'GET',
|
|
168
201
|
headers: {
|
|
169
202
|
'authorization': signed.authorization,
|
|
170
203
|
'x-amz-date': signed.amzDate,
|
|
171
204
|
'x-amz-content-sha256': payloadHash
|
|
172
205
|
}
|
|
173
|
-
});
|
|
206
|
+
}, this.requestTimeoutMs());
|
|
174
207
|
if (response.status === 404)
|
|
175
208
|
return null;
|
|
176
209
|
if (!response.ok) {
|
|
@@ -211,14 +244,14 @@ export class ObjectStorageClient {
|
|
|
211
244
|
secretAccessKey: this.config.r2.secretAccessKey,
|
|
212
245
|
payloadHash
|
|
213
246
|
});
|
|
214
|
-
const response = await
|
|
247
|
+
const response = await fetchWithTimeout(url, {
|
|
215
248
|
method: 'HEAD',
|
|
216
249
|
headers: {
|
|
217
250
|
'authorization': signed.authorization,
|
|
218
251
|
'x-amz-date': signed.amzDate,
|
|
219
252
|
'x-amz-content-sha256': payloadHash
|
|
220
253
|
}
|
|
221
|
-
});
|
|
254
|
+
}, this.requestTimeoutMs());
|
|
222
255
|
if (response.status === 404)
|
|
223
256
|
return false;
|
|
224
257
|
if (!response.ok) {
|
|
@@ -241,14 +274,14 @@ export class ObjectStorageClient {
|
|
|
241
274
|
secretAccessKey: this.config.r2.secretAccessKey,
|
|
242
275
|
payloadHash
|
|
243
276
|
});
|
|
244
|
-
const response = await
|
|
277
|
+
const response = await fetchWithTimeout(url, {
|
|
245
278
|
method: 'DELETE',
|
|
246
279
|
headers: {
|
|
247
280
|
'authorization': signed.authorization,
|
|
248
281
|
'x-amz-date': signed.amzDate,
|
|
249
282
|
'x-amz-content-sha256': payloadHash
|
|
250
283
|
}
|
|
251
|
-
});
|
|
284
|
+
}, this.requestTimeoutMs());
|
|
252
285
|
if (response.status === 404)
|
|
253
286
|
return false;
|
|
254
287
|
if (!response.ok) {
|
|
@@ -10,9 +10,34 @@ const TEST_CONFIG = {
|
|
|
10
10
|
secretAccessKey: 'secret-key-1',
|
|
11
11
|
keyPrefix: '',
|
|
12
12
|
signedUploadTtlSeconds: 600,
|
|
13
|
-
signedDownloadTtlSeconds: 300
|
|
13
|
+
signedDownloadTtlSeconds: 300,
|
|
14
|
+
requestTimeoutMs: 15000
|
|
14
15
|
}
|
|
15
16
|
};
|
|
17
|
+
describe('ObjectStorageClient.putObject', () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.unstubAllGlobals();
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
it('aborts stalled uploads before MCP tool calls can time out', async () => {
|
|
23
|
+
const fetchMock = vi.fn((_input, init) => (new Promise((_resolve, reject) => {
|
|
24
|
+
const signal = init?.signal;
|
|
25
|
+
signal?.addEventListener('abort', () => {
|
|
26
|
+
reject(signal.reason);
|
|
27
|
+
}, { once: true });
|
|
28
|
+
})));
|
|
29
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
30
|
+
const client = new ObjectStorageClient({
|
|
31
|
+
...TEST_CONFIG,
|
|
32
|
+
r2: {
|
|
33
|
+
...TEST_CONFIG.r2,
|
|
34
|
+
requestTimeoutMs: 1
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
await expect(client.putObject('workspaces/workspace-1/assets/documents/doc-1.md', Buffer.from('# plan', 'utf8'), 'text/markdown')).rejects.toThrow('R2 request timed out after 1ms');
|
|
38
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
16
41
|
describe('ObjectStorageClient.getObject', () => {
|
|
17
42
|
afterEach(() => {
|
|
18
43
|
vi.unstubAllGlobals();
|
|
@@ -31,6 +31,7 @@ export function resolveObjectStorageConfigFromEnv(env = process.env) {
|
|
|
31
31
|
: '';
|
|
32
32
|
const signedUploadTtlSeconds = readPositiveInteger(env.R2_SIGNED_UPLOAD_TTL_SECONDS, 600);
|
|
33
33
|
const signedDownloadTtlSeconds = readPositiveInteger(env.R2_SIGNED_DOWNLOAD_TTL_SECONDS, 300);
|
|
34
|
+
const requestTimeoutMs = readPositiveInteger(env.R2_REQUEST_TIMEOUT_MS, 15000);
|
|
34
35
|
return {
|
|
35
36
|
provider: 'r2',
|
|
36
37
|
r2: {
|
|
@@ -41,7 +42,8 @@ export function resolveObjectStorageConfigFromEnv(env = process.env) {
|
|
|
41
42
|
secretAccessKey,
|
|
42
43
|
keyPrefix,
|
|
43
44
|
signedUploadTtlSeconds,
|
|
44
|
-
signedDownloadTtlSeconds
|
|
45
|
+
signedDownloadTtlSeconds,
|
|
46
|
+
requestTimeoutMs
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
}
|
|
@@ -20,7 +20,8 @@ describe('resolveObjectStorageConfigFromEnv', () => {
|
|
|
20
20
|
secretAccessKey: 'secret-key-1',
|
|
21
21
|
keyPrefix: '',
|
|
22
22
|
signedUploadTtlSeconds: 600,
|
|
23
|
-
signedDownloadTtlSeconds: 300
|
|
23
|
+
signedDownloadTtlSeconds: 300,
|
|
24
|
+
requestTimeoutMs: 15000
|
|
24
25
|
}
|
|
25
26
|
});
|
|
26
27
|
});
|
|
@@ -35,4 +36,15 @@ describe('resolveObjectStorageConfigFromEnv', () => {
|
|
|
35
36
|
});
|
|
36
37
|
expect(config.r2?.keyPrefix).toBe('taskforce-assets');
|
|
37
38
|
});
|
|
39
|
+
it('reads a bounded R2 request timeout', () => {
|
|
40
|
+
const config = resolveObjectStorageConfigFromEnv({
|
|
41
|
+
TASKFORCE_OBJECT_STORAGE_PROVIDER: 'r2',
|
|
42
|
+
R2_ACCOUNT_ID: 'acct-1',
|
|
43
|
+
R2_BUCKET: 'taskforce-dev-assets',
|
|
44
|
+
R2_ACCESS_KEY_ID: 'access-key-1',
|
|
45
|
+
R2_SECRET_ACCESS_KEY: 'secret-key-1',
|
|
46
|
+
R2_REQUEST_TIMEOUT_MS: '2500'
|
|
47
|
+
});
|
|
48
|
+
expect(config.r2?.requestTimeoutMs).toBe(2500);
|
|
49
|
+
});
|
|
38
50
|
});
|
|
@@ -39,6 +39,8 @@ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurface
|
|
|
39
39
|
color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
40
40
|
...(hasOwnSyncField(profile, 'avatarUrl') ? { avatarUrl: readSyncSameOriginAssetUrlField(profile, 'avatarUrl') ?? null } : {}),
|
|
41
41
|
...(hasOwnSyncField(profile, 'avatarSourceUrl') ? { avatarSourceUrl: readSyncSameOriginAssetUrlField(profile, 'avatarSourceUrl') ?? null } : {}),
|
|
42
|
+
...(hasOwnSyncField(profile, 'avatarRevision') ? { avatarRevision: toOptionalNonNegativeInteger(profile.avatarRevision) } : {}),
|
|
43
|
+
avatarUpdatedAt: readSyncNullableIsoStringField(profile, 'avatarUpdatedAt') ?? null,
|
|
42
44
|
description: readSyncNullableStringField(profile, 'description') ?? null,
|
|
43
45
|
role: readSyncNullableStringField(profile, 'role') ?? null,
|
|
44
46
|
provider: readSyncNullableStringField(profile, 'provider') ?? null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
|
|
2
|
-
import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot,
|
|
2
|
+
import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncEntityEventSnapshot } from './workspaceSyncModel.js';
|
|
3
3
|
type SyncCore = TaskforceCore & TaskforceSyncCapable;
|
|
4
4
|
interface CollaborationSyncStateDeps {
|
|
5
5
|
core: SyncCore;
|
|
@@ -7,11 +7,11 @@ interface CollaborationSyncStateDeps {
|
|
|
7
7
|
export declare function listWorkspaceSyncDocumentReviewSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
8
8
|
export declare function listWorkspaceSyncDocumentReviewCommentsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
9
9
|
export declare function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
10
|
-
export declare function listWorkspaceSyncTaskEventsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string):
|
|
10
|
+
export declare function listWorkspaceSyncTaskEventsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncEntityEventSnapshot[];
|
|
11
11
|
export declare function createCollaborationSyncState(deps: CollaborationSyncStateDeps): {
|
|
12
12
|
listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
13
13
|
listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
14
14
|
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
15
|
-
listWorkspaceSyncTaskEventsSnapshot: (workspaceId?: string) =>
|
|
15
|
+
listWorkspaceSyncTaskEventsSnapshot: (workspaceId?: string) => WorkspaceSyncEntityEventSnapshot[];
|
|
16
16
|
};
|
|
17
17
|
export {};
|
|
@@ -52,19 +52,26 @@ export function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, works
|
|
|
52
52
|
&& session.baseImageAssetId.length > 0));
|
|
53
53
|
}
|
|
54
54
|
export function listWorkspaceSyncTaskEventsSnapshot(deps, workspaceId = 'default') {
|
|
55
|
-
return deps.core.
|
|
56
|
-
.map((event) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
return deps.core.listEntityEventsForSync({ workspaceId })
|
|
56
|
+
.map((event) => {
|
|
57
|
+
const entityType = event.entityType === 'initiative' || event.entityType === 'workstream'
|
|
58
|
+
? event.entityType
|
|
59
|
+
: 'task';
|
|
60
|
+
return {
|
|
61
|
+
id: event.id,
|
|
62
|
+
workspaceId: event.workspaceId,
|
|
63
|
+
entityType,
|
|
64
|
+
entityId: String(event.entityId || ('taskId' in event ? event.taskId : '') || '').trim(),
|
|
65
|
+
taskId: entityType === 'task' && 'taskId' in event ? event.taskId : undefined,
|
|
66
|
+
action: event.action,
|
|
67
|
+
actor: event.actor,
|
|
68
|
+
actorType: event.actorType,
|
|
69
|
+
details: event.details,
|
|
70
|
+
createdAt: event.createdAt,
|
|
71
|
+
};
|
|
72
|
+
})
|
|
66
73
|
.filter((event) => (event.id.length > 0
|
|
67
|
-
&& event.
|
|
74
|
+
&& event.entityId.length > 0
|
|
68
75
|
&& event.workspaceId.length > 0
|
|
69
76
|
&& event.action.length > 0
|
|
70
77
|
&& event.createdAt.length > 0));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkspaceAssetLinkRole } from '../storage/workspaceAssetStore.js';
|
|
1
|
+
import type { WorkspaceAssetLinkRole, WorkspaceAssetLinkTargetType } from '../storage/workspaceAssetStore.js';
|
|
2
2
|
export interface DocumentSyncPayload {
|
|
3
3
|
path: string;
|
|
4
4
|
updatedAt: string;
|
|
@@ -8,6 +8,8 @@ export interface DocumentSyncPayload {
|
|
|
8
8
|
referenceNumber?: number | null;
|
|
9
9
|
version?: number | null;
|
|
10
10
|
taskId?: string | null;
|
|
11
|
+
linkTargetType?: WorkspaceAssetLinkTargetType | null;
|
|
12
|
+
linkTargetId?: string | null;
|
|
11
13
|
logicalName?: string | null;
|
|
12
14
|
caption?: string | null;
|
|
13
15
|
originalFilename?: string | null;
|
|
@@ -27,6 +29,8 @@ export interface BinaryAssetSyncPayload {
|
|
|
27
29
|
mimeType: string;
|
|
28
30
|
referenceNumber?: number | null;
|
|
29
31
|
taskId?: string | null;
|
|
32
|
+
linkTargetType?: WorkspaceAssetLinkTargetType | null;
|
|
33
|
+
linkTargetId?: string | null;
|
|
30
34
|
logicalName?: string | null;
|
|
31
35
|
caption?: string | null;
|
|
32
36
|
originalFilename?: string | null;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
function toOptionalLinkTargetType(value) {
|
|
2
|
+
return value === 'initiative' || value === 'workstream' || value === 'task'
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
1
6
|
function toOptionalTrimmedString(value) {
|
|
2
7
|
if (typeof value !== 'string')
|
|
3
8
|
return undefined;
|
|
@@ -32,6 +37,8 @@ export function normalizeDocumentSyncPayload(raw, normalizePath) {
|
|
|
32
37
|
referenceNumber: toOptionalPositiveInteger(source.referenceNumber),
|
|
33
38
|
version: toOptionalPositiveInteger(source.version),
|
|
34
39
|
taskId: toNullableTrimmedString(source.taskId),
|
|
40
|
+
linkTargetType: toOptionalLinkTargetType(source.linkTargetType) ?? null,
|
|
41
|
+
linkTargetId: toNullableTrimmedString(source.linkTargetId),
|
|
35
42
|
logicalName: toNullableTrimmedString(source.logicalName),
|
|
36
43
|
caption: toNullableTrimmedString(source.caption),
|
|
37
44
|
originalFilename: toNullableTrimmedString(source.originalFilename),
|
|
@@ -67,6 +74,8 @@ export function normalizeBinaryAssetSyncPayload(raw, normalizePath) {
|
|
|
67
74
|
mimeType: toOptionalTrimmedString(source.mimeType) || 'application/octet-stream',
|
|
68
75
|
referenceNumber: toOptionalPositiveInteger(source.referenceNumber),
|
|
69
76
|
taskId: toNullableTrimmedString(source.taskId),
|
|
77
|
+
linkTargetType: toOptionalLinkTargetType(source.linkTargetType) ?? null,
|
|
78
|
+
linkTargetId: toNullableTrimmedString(source.linkTargetId),
|
|
70
79
|
logicalName: toNullableTrimmedString(source.logicalName),
|
|
71
80
|
caption: toNullableTrimmedString(source.caption),
|
|
72
81
|
originalFilename: toNullableTrimmedString(source.originalFilename),
|
|
@@ -18,6 +18,8 @@ describe('contentSyncPayload', () => {
|
|
|
18
18
|
referenceNumber: '7',
|
|
19
19
|
version: '2',
|
|
20
20
|
taskId: ' task-1 ',
|
|
21
|
+
linkTargetType: 'task',
|
|
22
|
+
linkTargetId: ' task-1 ',
|
|
21
23
|
logicalName: ' Spec ',
|
|
22
24
|
caption: ' Draft ',
|
|
23
25
|
originalFilename: ' spec.md ',
|
|
@@ -32,6 +34,8 @@ describe('contentSyncPayload', () => {
|
|
|
32
34
|
referenceNumber: 7,
|
|
33
35
|
version: 2,
|
|
34
36
|
taskId: 'task-1',
|
|
37
|
+
linkTargetType: 'task',
|
|
38
|
+
linkTargetId: 'task-1',
|
|
35
39
|
logicalName: 'Spec',
|
|
36
40
|
caption: 'Draft',
|
|
37
41
|
originalFilename: 'spec.md',
|
|
@@ -47,6 +51,8 @@ describe('contentSyncPayload', () => {
|
|
|
47
51
|
mimeType: ' image/png ',
|
|
48
52
|
referenceNumber: '3',
|
|
49
53
|
taskId: ' task-2 ',
|
|
54
|
+
linkTargetType: 'task',
|
|
55
|
+
linkTargetId: ' task-2 ',
|
|
50
56
|
logicalName: ' Hero ',
|
|
51
57
|
caption: ' Screenshot ',
|
|
52
58
|
originalFilename: ' hero.png ',
|
|
@@ -61,6 +67,8 @@ describe('contentSyncPayload', () => {
|
|
|
61
67
|
mimeType: 'image/png',
|
|
62
68
|
referenceNumber: 3,
|
|
63
69
|
taskId: 'task-2',
|
|
70
|
+
linkTargetType: 'task',
|
|
71
|
+
linkTargetId: 'task-2',
|
|
64
72
|
logicalName: 'Hero',
|
|
65
73
|
caption: 'Screenshot',
|
|
66
74
|
originalFilename: 'hero.png',
|
|
@@ -72,6 +80,37 @@ describe('contentSyncPayload', () => {
|
|
|
72
80
|
assetId: undefined,
|
|
73
81
|
});
|
|
74
82
|
});
|
|
83
|
+
it('normalizes planning link targets for document and asset payloads', () => {
|
|
84
|
+
expect(normalizeDocumentSyncPayload({
|
|
85
|
+
path: 'docs/plan.md',
|
|
86
|
+
updatedAt: '2026-04-10T17:00:00.000Z',
|
|
87
|
+
content: 'hello',
|
|
88
|
+
linkTargetType: 'initiative',
|
|
89
|
+
linkTargetId: ' initiative-1 ',
|
|
90
|
+
caption: ' Plan ',
|
|
91
|
+
}, normalizeDocumentPath)).toEqual(expect.objectContaining({
|
|
92
|
+
path: 'docs/plan.md',
|
|
93
|
+
linkTargetType: 'initiative',
|
|
94
|
+
linkTargetId: 'initiative-1',
|
|
95
|
+
taskId: null,
|
|
96
|
+
caption: 'Plan',
|
|
97
|
+
}));
|
|
98
|
+
expect(normalizeBinaryAssetSyncPayload({
|
|
99
|
+
path: 'workspaces/a/assets/files/file.bin',
|
|
100
|
+
updatedAt: '2026-04-10T17:00:00.000Z',
|
|
101
|
+
contentBase64: 'YWJj',
|
|
102
|
+
kind: 'file',
|
|
103
|
+
mimeType: 'application/octet-stream',
|
|
104
|
+
linkTargetType: 'workstream',
|
|
105
|
+
linkTargetId: ' workstream-1 ',
|
|
106
|
+
}, normalizeAssetPath)).toEqual(expect.objectContaining({
|
|
107
|
+
path: 'workspaces/a/assets/files/file.bin',
|
|
108
|
+
kind: 'file',
|
|
109
|
+
linkTargetType: 'workstream',
|
|
110
|
+
linkTargetId: 'workstream-1',
|
|
111
|
+
taskId: null,
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
75
114
|
it('serializes push-side document and asset payloads without route-local parsing', () => {
|
|
76
115
|
expect(serializeDocumentSyncPayload({
|
|
77
116
|
path: 'docs/plan.md',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
|
|
2
2
|
import type { ObjectStorageClient } from '../storage/objectStorageClient.js';
|
|
3
|
-
import type { WorkspaceAssetEntry, WorkspaceAssetLinkEntry, WorkspaceAssetLinkRole, WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
3
|
+
import type { WorkspaceAssetEntry, WorkspaceAssetLinkEntry, WorkspaceAssetLinkRole, WorkspaceAssetLinkTargetType, WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
4
4
|
import type { BinaryAssetDeleteSyncPayload, BinaryAssetSyncPayload, DocumentDeleteSyncPayload, DocumentSyncPayload } from './contentSyncPayload.js';
|
|
5
5
|
type SyncCore = TaskforceCore & TaskforceSyncCapable;
|
|
6
6
|
type RecordSyncDiagnostic = (workspaceId: string, eventType: 'pull' | 'push' | 'apply', errorMessage: string, details?: Record<string, unknown>) => void;
|
|
@@ -34,6 +34,8 @@ export declare function reconcileWorkspaceSyncLinks(deps: ContentSyncStateDeps,
|
|
|
34
34
|
workspaceId: string;
|
|
35
35
|
assetId: string;
|
|
36
36
|
taskId?: string | null;
|
|
37
|
+
linkTargetType?: WorkspaceAssetLinkTargetType | null;
|
|
38
|
+
linkTargetId?: string | null;
|
|
37
39
|
role?: WorkspaceAssetLinkRole | null;
|
|
38
40
|
displayName?: string | null;
|
|
39
41
|
updatedAt?: string | null;
|
|
@@ -70,6 +72,8 @@ export declare function createContentSyncState(deps: ContentSyncStateDeps): {
|
|
|
70
72
|
workspaceId: string;
|
|
71
73
|
assetId: string;
|
|
72
74
|
taskId?: string | null;
|
|
75
|
+
linkTargetType?: WorkspaceAssetLinkTargetType | null;
|
|
76
|
+
linkTargetId?: string | null;
|
|
73
77
|
role?: WorkspaceAssetLinkRole | null;
|
|
74
78
|
displayName?: string | null;
|
|
75
79
|
updatedAt?: string | null;
|