@taskforcehq/taskforce 0.3.303 → 0.3.304
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 +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +174 -476
- package/dist/mcp/runtime.test.js +81 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +12 -10
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -55,10 +55,18 @@ import { getRuntimeCapabilities } from '../../runtime/capabilities.js';
|
|
|
55
55
|
import { resolveBuildInfo } from '../buildInfo.js';
|
|
56
56
|
import { resolveRealtimeSyncFlag } from '../../sync/realtimeFeature.js';
|
|
57
57
|
import { parseJsonBody } from '../routes.js';
|
|
58
|
+
import { resolveRequestAccess } from '../auth.js';
|
|
58
59
|
import { resolveErrorStatusCode, requestHeader, requestRuntimeMode } from './shared.js';
|
|
59
60
|
import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
|
|
60
61
|
import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
|
|
61
62
|
import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
|
|
63
|
+
function createUiStateRequestId() {
|
|
64
|
+
return `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
65
|
+
}
|
|
66
|
+
function attachUiStateDebugHeaders(res, requestId) {
|
|
67
|
+
res.setHeader('x-taskforce-ui-state-route', 'tasks');
|
|
68
|
+
res.setHeader('x-taskforce-ui-state-request-id', requestId);
|
|
69
|
+
}
|
|
62
70
|
// === SECTION: Registration ===
|
|
63
71
|
export function registerTaskRoutes(deps) {
|
|
64
72
|
const { addRoute, core, context, requestWorkspaceId, ensureAppAccess, resolveEffectiveObjectStorageConfig, buildBatchSummary, CLOUD_SAFE_CONFIG_PATHS, } = deps;
|
|
@@ -155,16 +163,31 @@ export function registerTaskRoutes(deps) {
|
|
|
155
163
|
|| ((isLocalRuntime && allowCreate && !explicitWorkspaceId) ? '' : headerWorkspaceId)).trim() || 'default';
|
|
156
164
|
try {
|
|
157
165
|
const workspaceExists = Boolean(core.getWorkspaceProfile(incomingWorkspaceId));
|
|
166
|
+
const authConfig = context.authConfig;
|
|
167
|
+
const requestAccess = authConfig?.enabled
|
|
168
|
+
? resolveRequestAccess(req, authConfig)
|
|
169
|
+
: null;
|
|
158
170
|
const shouldCreateWorkspace = Boolean(name
|
|
159
171
|
&& (incomingWorkspaceId === 'default' || !workspaceExists)
|
|
160
172
|
&& (isLocalRuntime || allowCreate));
|
|
161
173
|
const workspace = shouldCreateWorkspace
|
|
162
|
-
?
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
? (!isLocalRuntime
|
|
175
|
+
&& requestAccess?.authenticated
|
|
176
|
+
&& requestAccess.userId
|
|
177
|
+
&& requestAccess.userId !== 'anonymous'
|
|
178
|
+
? core.createWorkspaceForUser({
|
|
179
|
+
userId: requestAccess.userId,
|
|
180
|
+
preferredWorkspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
|
|
181
|
+
name,
|
|
182
|
+
description,
|
|
183
|
+
role: 'owner'
|
|
184
|
+
})
|
|
185
|
+
: core.createWorkspace({
|
|
186
|
+
workspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
|
|
187
|
+
name,
|
|
188
|
+
description,
|
|
189
|
+
slug
|
|
190
|
+
}))
|
|
168
191
|
: core.updateWorkspaceProfile(incomingWorkspaceId, { name, description, slug });
|
|
169
192
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
170
193
|
res.end(JSON.stringify({ success: true, workspace }));
|
|
@@ -179,6 +202,9 @@ export function registerTaskRoutes(deps) {
|
|
|
179
202
|
addRoute('GET', '/api/taskforce/ui-state', async (req, res) => {
|
|
180
203
|
if (!ensureAppAccess(req, res))
|
|
181
204
|
return;
|
|
205
|
+
const requestId = createUiStateRequestId();
|
|
206
|
+
const startedAt = Date.now();
|
|
207
|
+
attachUiStateDebugHeaders(res, requestId);
|
|
182
208
|
try {
|
|
183
209
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
184
210
|
const requestedWorkspaceId = String(url.searchParams.get('workspaceId') || '').trim();
|
|
@@ -194,6 +220,12 @@ export function registerTaskRoutes(deps) {
|
|
|
194
220
|
}
|
|
195
221
|
catch (error) {
|
|
196
222
|
const statusCode = resolveErrorStatusCode(error, 400);
|
|
223
|
+
console.warn('[Taskforce][ui-state] GET failed', {
|
|
224
|
+
requestId,
|
|
225
|
+
durationMs: Date.now() - startedAt,
|
|
226
|
+
error: String(error?.message || 'Failed to read UI state'),
|
|
227
|
+
path: req.url || '/api/taskforce/ui-state'
|
|
228
|
+
});
|
|
197
229
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
198
230
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to read UI state') }));
|
|
199
231
|
}
|
|
@@ -202,6 +234,9 @@ export function registerTaskRoutes(deps) {
|
|
|
202
234
|
addRoute('POST', '/api/taskforce/ui-state', async (req, res) => {
|
|
203
235
|
if (!ensureAppAccess(req, res))
|
|
204
236
|
return;
|
|
237
|
+
const requestId = createUiStateRequestId();
|
|
238
|
+
const startedAt = Date.now();
|
|
239
|
+
attachUiStateDebugHeaders(res, requestId);
|
|
205
240
|
const body = await parseJsonBody(req);
|
|
206
241
|
try {
|
|
207
242
|
const requestedWorkspaceId = String(body?.workspaceId || '').trim();
|
|
@@ -222,6 +257,13 @@ export function registerTaskRoutes(deps) {
|
|
|
222
257
|
}
|
|
223
258
|
catch (error) {
|
|
224
259
|
const statusCode = resolveErrorStatusCode(error, 400);
|
|
260
|
+
console.warn('[Taskforce][ui-state] POST failed', {
|
|
261
|
+
requestId,
|
|
262
|
+
durationMs: Date.now() - startedAt,
|
|
263
|
+
error: String(error?.message || 'Failed to merge UI state'),
|
|
264
|
+
path: req.url || '/api/taskforce/ui-state',
|
|
265
|
+
stateKey: String(body?.stateKey || body?.key || '').trim() || 'default'
|
|
266
|
+
});
|
|
225
267
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
226
268
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to merge UI state') }));
|
|
227
269
|
}
|
|
@@ -398,7 +440,7 @@ export function registerTaskRoutes(deps) {
|
|
|
398
440
|
: body;
|
|
399
441
|
const updated = core.updateTask(params.id, updates, workspaceId, {
|
|
400
442
|
actorRef: 'user',
|
|
401
|
-
|
|
443
|
+
saveSource
|
|
402
444
|
});
|
|
403
445
|
if (!updated) {
|
|
404
446
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
package/dist/server/routes.js
CHANGED
|
@@ -34,7 +34,7 @@ import { registerResourceRoutes } from './routes/resources.js';
|
|
|
34
34
|
import { registerDocumentRoutes } from './routes/documents.js';
|
|
35
35
|
import { registerDocumentReviewRoutes } from './routes/documentReviews.js';
|
|
36
36
|
import { registerAnnotatedAttachmentRoutes } from './routes/annotatedAttachments.js';
|
|
37
|
-
import { requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
|
|
37
|
+
import { buildCloudMcpRuntimeAuth, requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
|
|
38
38
|
import { resolveDeploymentConfig } from '../config/deployment.js';
|
|
39
39
|
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../config/cloudEnvironment.js';
|
|
40
40
|
import { getCanonicalDocumentName } from '../utils/documentNames.js';
|
|
@@ -372,7 +372,7 @@ export function createRoutes(core, context = {}) {
|
|
|
372
372
|
? 'Verify your Taskforce account'
|
|
373
373
|
: 'Reset your Taskforce password';
|
|
374
374
|
const text = kind === 'verify-email'
|
|
375
|
-
? `Welcome to Taskforce.\n\
|
|
375
|
+
? `Welcome to Taskforce.\n\nYou're almost in. Enter this verification token on the verification screen to activate your account:\n\n${token}\n\nPrefer the faster route? Open the link below to verify your email automatically:\n${link}\n\nIf you didn't create a Taskforce account, you can safely ignore this email.`
|
|
376
376
|
: `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
|
|
377
377
|
const result = await sendEmail({
|
|
378
378
|
to: email,
|
|
@@ -1458,24 +1458,13 @@ export function createRoutes(core, context = {}) {
|
|
|
1458
1458
|
serverName: 'taskforce-cloud',
|
|
1459
1459
|
logStartup: false,
|
|
1460
1460
|
runtimeMode: 'cloud',
|
|
1461
|
-
auth: {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
aiProfileToken: auth.aiProfileToken,
|
|
1466
|
-
aiProfileName: auth.aiProfileName,
|
|
1467
|
-
aiProfileIcon: auth.aiProfileIcon,
|
|
1468
|
-
aiProfileColor: auth.aiProfileColor,
|
|
1469
|
-
aiProfileDescription: auth.aiProfileDescription,
|
|
1470
|
-
aiProfileRole: auth.aiProfileRole,
|
|
1471
|
-
aiProfileProvider: auth.aiProfileProvider,
|
|
1472
|
-
aiProfileModel: auth.aiProfileModel,
|
|
1473
|
-
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
1474
|
-
actorType: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
|
|
1461
|
+
auth: buildCloudMcpRuntimeAuth({
|
|
1462
|
+
req,
|
|
1463
|
+
auth,
|
|
1464
|
+
requestHeader,
|
|
1475
1465
|
requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
},
|
|
1466
|
+
authScopes,
|
|
1467
|
+
}),
|
|
1479
1468
|
});
|
|
1480
1469
|
const transport = new StreamableHTTPServerTransport({
|
|
1481
1470
|
sessionIdGenerator: undefined,
|
|
@@ -10,6 +10,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
10
10
|
import * as mcpRuntime from '../mcp/runtime';
|
|
11
11
|
import { createDatabaseAdapter } from '../storage/createDatabaseAdapter.js';
|
|
12
12
|
import { ensureDocumentReferenceSchema, ensureTaskAssetsSchema, ensureWorkspaceAssetsSchema } from '../migrations/taskSchemaMigrations.js';
|
|
13
|
+
import { ObjectStorageClient } from '../storage/objectStorageClient.js';
|
|
13
14
|
import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
14
15
|
// Mock TaskforceCore
|
|
15
16
|
vi.mock('../core/Taskforce', () => {
|
|
@@ -159,10 +160,13 @@ vi.mock('../core/Taskforce', () => {
|
|
|
159
160
|
listDocumentReviewSessionsForSync = vi.fn().mockReturnValue([]);
|
|
160
161
|
listDocumentReviewCommentsForSync = vi.fn().mockReturnValue([]);
|
|
161
162
|
listAnnotatedAttachmentSessionsForSync = vi.fn().mockReturnValue([]);
|
|
163
|
+
listTaskEventsForSync = vi.fn().mockReturnValue([]);
|
|
164
|
+
listTaskEvents = vi.fn().mockReturnValue([]);
|
|
162
165
|
getDocumentReviewSession = vi.fn().mockReturnValue(null);
|
|
163
166
|
upsertAiProfileForSync = vi.fn();
|
|
164
167
|
upsertDocumentReviewSessionForSync = vi.fn();
|
|
165
168
|
upsertDocumentReviewCommentForSync = vi.fn();
|
|
169
|
+
upsertTaskEventForSync = vi.fn();
|
|
166
170
|
deleteDocumentReviewSessionForSync = vi.fn().mockReturnValue(true);
|
|
167
171
|
deleteDocumentReviewCommentForSync = vi.fn().mockReturnValue(true);
|
|
168
172
|
deleteAnnotatedAttachmentSessionForSync = vi.fn().mockReturnValue(true);
|
|
@@ -434,6 +438,7 @@ vi.mock('../core/Taskforce', () => {
|
|
|
434
438
|
]);
|
|
435
439
|
hasWorkspaceAdmin = vi.fn().mockReturnValue(true);
|
|
436
440
|
registerPasswordUser = vi.fn();
|
|
441
|
+
isEmailVerified = vi.fn().mockReturnValue(false);
|
|
437
442
|
requestEmailVerification = vi.fn().mockReturnValue({});
|
|
438
443
|
verifyEmailWithToken = vi.fn().mockReturnValue(false);
|
|
439
444
|
markEmailVerified = vi.fn().mockReturnValue(true);
|
|
@@ -892,6 +897,8 @@ describe('Server Routes', () => {
|
|
|
892
897
|
auth: expect.objectContaining({
|
|
893
898
|
aiProfileName: 'Automation Codex',
|
|
894
899
|
aiProfileToken: 'tfp_automation_codex',
|
|
900
|
+
aiProfileIcon: 'Code2',
|
|
901
|
+
aiProfileColor: '#10a37f',
|
|
895
902
|
aiProfileDescription: 'OpenAI Codex coding agent connected through MCP.',
|
|
896
903
|
aiProfileRole: 'Coding Agent',
|
|
897
904
|
aiProfileProvider: 'OpenAI',
|
|
@@ -1600,6 +1607,17 @@ describe('Server Routes', () => {
|
|
|
1600
1607
|
});
|
|
1601
1608
|
expect(core.updateUiState).not.toHaveBeenCalledWith('workspace-denied', expect.anything(), expect.anything());
|
|
1602
1609
|
});
|
|
1610
|
+
it('POST /api/taskforce/ui-state should stamp debug response headers', async () => {
|
|
1611
|
+
const match = matchRoute('POST', '/api/taskforce/ui-state', routes);
|
|
1612
|
+
const req = createMockReq('POST', '/api/taskforce/ui-state', {
|
|
1613
|
+
stateKey: 'app',
|
|
1614
|
+
patch: { activeWorkspaceModule: 'tasks' }
|
|
1615
|
+
});
|
|
1616
|
+
const res = createMockRes();
|
|
1617
|
+
await match?.handler(req, res, {});
|
|
1618
|
+
expect(res.setHeader).toHaveBeenCalledWith('x-taskforce-ui-state-route', 'tasks');
|
|
1619
|
+
expect(res.setHeader).toHaveBeenCalledWith('x-taskforce-ui-state-request-id', expect.stringMatching(/^ui-/));
|
|
1620
|
+
});
|
|
1603
1621
|
it('POST /api/taskforce/workspace-profile should persist workspace name and description', async () => {
|
|
1604
1622
|
const match = matchRoute('POST', '/api/taskforce/workspace-profile', routes);
|
|
1605
1623
|
const req = createMockReq('POST', '/api/taskforce/workspace-profile', { workspaceId: 'default', name: 'Acme Project', description: 'Customer onboarding' }, { 'x-taskforce-workspace-id': 'default' });
|
|
@@ -3821,7 +3839,7 @@ describe('Server Routes', () => {
|
|
|
3821
3839
|
}));
|
|
3822
3840
|
expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
|
|
3823
3841
|
});
|
|
3824
|
-
it('POST /api/taskforce/auth/register should
|
|
3842
|
+
it('POST /api/taskforce/auth/register should route duplicate unverified email into verification recovery', async () => {
|
|
3825
3843
|
const authConfig = {
|
|
3826
3844
|
runtimeMode: 'cloud',
|
|
3827
3845
|
enabled: true,
|
|
@@ -3835,6 +3853,8 @@ describe('Server Routes', () => {
|
|
|
3835
3853
|
};
|
|
3836
3854
|
routes = createRoutes(core, { authConfig });
|
|
3837
3855
|
core.hasWorkspaceAdmin.mockReturnValueOnce(true);
|
|
3856
|
+
core.isEmailVerified.mockReturnValueOnce(false);
|
|
3857
|
+
core.requestEmailVerification.mockReturnValueOnce({ token: 'verify-token-existing' });
|
|
3838
3858
|
const error = new Error('Email is already registered');
|
|
3839
3859
|
error.statusCode = 409;
|
|
3840
3860
|
error.code = 'EMAIL_ALREADY_EXISTS';
|
|
@@ -3849,6 +3869,49 @@ describe('Server Routes', () => {
|
|
|
3849
3869
|
});
|
|
3850
3870
|
const res = createMockRes();
|
|
3851
3871
|
await match?.handler(req, res, {});
|
|
3872
|
+
expect(core.isEmailVerified).toHaveBeenCalledWith('owner@example.com');
|
|
3873
|
+
expect(core.requestEmailVerification).toHaveBeenCalledWith({ email: 'owner@example.com' });
|
|
3874
|
+
expect(res.writeHead).toHaveBeenCalledWith(409, expect.any(Object));
|
|
3875
|
+
const responsePayload = JSON.parse(String(res.end.mock.calls.at(-1)?.[0] || '{}'));
|
|
3876
|
+
expect(responsePayload).toEqual(expect.objectContaining({
|
|
3877
|
+
success: false,
|
|
3878
|
+
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
3879
|
+
verificationRequired: true,
|
|
3880
|
+
emailSent: true,
|
|
3881
|
+
verificationToken: 'verify-token-existing'
|
|
3882
|
+
}));
|
|
3883
|
+
});
|
|
3884
|
+
it('POST /api/taskforce/auth/register should return conflict when verified email exists', async () => {
|
|
3885
|
+
const authConfig = {
|
|
3886
|
+
runtimeMode: 'cloud',
|
|
3887
|
+
enabled: true,
|
|
3888
|
+
requiredForApi: true,
|
|
3889
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
3890
|
+
defaultRole: 'member',
|
|
3891
|
+
defaultWorkspaceId: 'default',
|
|
3892
|
+
sessionCookieName: 'taskforce_session',
|
|
3893
|
+
sessionSecret: 'test-secret',
|
|
3894
|
+
sessionTtlSeconds: 3600
|
|
3895
|
+
};
|
|
3896
|
+
routes = createRoutes(core, { authConfig });
|
|
3897
|
+
core.hasWorkspaceAdmin.mockReturnValueOnce(true);
|
|
3898
|
+
core.isEmailVerified.mockReturnValueOnce(true);
|
|
3899
|
+
const error = new Error('Email is already registered');
|
|
3900
|
+
error.statusCode = 409;
|
|
3901
|
+
error.code = 'EMAIL_ALREADY_EXISTS';
|
|
3902
|
+
core.registerPasswordUser.mockImplementationOnce(() => {
|
|
3903
|
+
throw error;
|
|
3904
|
+
});
|
|
3905
|
+
const match = matchRoute('POST', '/api/taskforce/auth/register', routes);
|
|
3906
|
+
const req = createMockReq('POST', '/api/taskforce/auth/register', {
|
|
3907
|
+
email: 'owner@example.com',
|
|
3908
|
+
password: 'password123',
|
|
3909
|
+
workspaceId: 'workspace-1'
|
|
3910
|
+
});
|
|
3911
|
+
const res = createMockRes();
|
|
3912
|
+
await match?.handler(req, res, {});
|
|
3913
|
+
expect(core.isEmailVerified).toHaveBeenCalledWith('owner@example.com');
|
|
3914
|
+
expect(core.requestEmailVerification).not.toHaveBeenCalled();
|
|
3852
3915
|
expect(res.writeHead).toHaveBeenCalledWith(409, expect.any(Object));
|
|
3853
3916
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify({
|
|
3854
3917
|
success: false,
|
|
@@ -6531,6 +6594,77 @@ describe('Server Routes', () => {
|
|
|
6531
6594
|
});
|
|
6532
6595
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
6533
6596
|
});
|
|
6597
|
+
it('POST /api/taskforce/sync/workspace/apply-local should apply task event upserts after task snapshots', async () => {
|
|
6598
|
+
core.resolvePreferredWorkspaceId.mockReturnValue('workspace-local');
|
|
6599
|
+
core.getTask.mockImplementation((taskId) => (taskId === 'task-sync-activity-1'
|
|
6600
|
+
? {
|
|
6601
|
+
id: 'task-sync-activity-1',
|
|
6602
|
+
title: 'Synced task',
|
|
6603
|
+
status: 'done',
|
|
6604
|
+
category: 'general',
|
|
6605
|
+
type: 'task',
|
|
6606
|
+
createdAt: '2026-03-24T10:00:00.000Z',
|
|
6607
|
+
updatedAt: '2026-03-24T10:05:00.000Z',
|
|
6608
|
+
taskEvents: []
|
|
6609
|
+
}
|
|
6610
|
+
: null));
|
|
6611
|
+
routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
|
|
6612
|
+
const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
|
|
6613
|
+
const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
|
|
6614
|
+
changes: [
|
|
6615
|
+
{
|
|
6616
|
+
op: 'upsert',
|
|
6617
|
+
archived: false,
|
|
6618
|
+
task: {
|
|
6619
|
+
id: 'task-sync-activity-1',
|
|
6620
|
+
title: 'Synced task',
|
|
6621
|
+
status: 'done',
|
|
6622
|
+
category: 'general',
|
|
6623
|
+
type: 'task',
|
|
6624
|
+
updatedAt: '2026-03-24T10:05:00.000Z'
|
|
6625
|
+
}
|
|
6626
|
+
},
|
|
6627
|
+
{
|
|
6628
|
+
op: 'task-event-upsert',
|
|
6629
|
+
event: {
|
|
6630
|
+
id: 'task-event-sync-1',
|
|
6631
|
+
taskId: 'task-sync-activity-1',
|
|
6632
|
+
workspaceId: 'workspace-local',
|
|
6633
|
+
action: 'task-status-changed',
|
|
6634
|
+
actor: 'ai-profile-1',
|
|
6635
|
+
actorType: 'ai',
|
|
6636
|
+
createdAt: '2026-03-24T10:05:00.000Z',
|
|
6637
|
+
details: {
|
|
6638
|
+
changes: {
|
|
6639
|
+
status: {
|
|
6640
|
+
from: 'in-progress',
|
|
6641
|
+
to: 'done'
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
]
|
|
6648
|
+
}, {
|
|
6649
|
+
'x-taskforce-workspace-id': 'workspace-local'
|
|
6650
|
+
});
|
|
6651
|
+
const res = createMockRes();
|
|
6652
|
+
await match?.handler(req, res, {});
|
|
6653
|
+
expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith(expect.objectContaining({
|
|
6654
|
+
tasks: [expect.objectContaining({ id: 'task-sync-activity-1' })],
|
|
6655
|
+
archived: []
|
|
6656
|
+
}), 'workspace-local');
|
|
6657
|
+
expect(core.upsertTaskEventForSync).toHaveBeenCalledWith(expect.objectContaining({
|
|
6658
|
+
id: 'task-event-sync-1',
|
|
6659
|
+
taskId: 'task-sync-activity-1',
|
|
6660
|
+
workspaceId: 'workspace-local',
|
|
6661
|
+
action: 'task-status-changed',
|
|
6662
|
+
actor: 'ai-profile-1',
|
|
6663
|
+
actorType: 'ai',
|
|
6664
|
+
createdAt: '2026-03-24T10:05:00.000Z'
|
|
6665
|
+
}));
|
|
6666
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
6667
|
+
});
|
|
6534
6668
|
it('POST /api/taskforce/sync/workspace/apply-local should fail when document review session persistence fails', async () => {
|
|
6535
6669
|
core.resolvePreferredWorkspaceId.mockReturnValue('workspace-local');
|
|
6536
6670
|
const error = new Error('document review session write failed');
|
|
@@ -8771,6 +8905,73 @@ describe('Server Routes', () => {
|
|
|
8771
8905
|
expect(payload.authenticated).toBe(true);
|
|
8772
8906
|
expect(payload.avatarUrl).toBe('/api/taskforce/context/users%2Fu-avatar%2Fprofile%2Favatar%2Fdraft.png');
|
|
8773
8907
|
});
|
|
8908
|
+
it('GET /api/taskforce/auth/session should not wait for avatar cleanup before responding', async () => {
|
|
8909
|
+
const authConfig = {
|
|
8910
|
+
runtimeMode: 'cloud',
|
|
8911
|
+
enabled: true,
|
|
8912
|
+
requiredForApi: true,
|
|
8913
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
8914
|
+
defaultRole: 'member',
|
|
8915
|
+
defaultWorkspaceId: 'default',
|
|
8916
|
+
sessionCookieName: 'taskforce_session',
|
|
8917
|
+
sessionSecret: 'test-secret',
|
|
8918
|
+
sessionTtlSeconds: 3600
|
|
8919
|
+
};
|
|
8920
|
+
const deleteObjectCompletion = { resolve: null };
|
|
8921
|
+
const deleteObjectSpy = vi.spyOn(ObjectStorageClient.prototype, 'deleteObject').mockImplementation(() => new Promise((resolve) => {
|
|
8922
|
+
deleteObjectCompletion.resolve = resolve;
|
|
8923
|
+
}));
|
|
8924
|
+
routes = createRoutes(core, {
|
|
8925
|
+
authConfig,
|
|
8926
|
+
objectStorage: {
|
|
8927
|
+
provider: 'r2',
|
|
8928
|
+
r2: {
|
|
8929
|
+
accountId: 'test-account',
|
|
8930
|
+
bucket: 'taskforce-test',
|
|
8931
|
+
endpoint: 'https://example.com',
|
|
8932
|
+
accessKeyId: 'test-key',
|
|
8933
|
+
secretAccessKey: 'test-secret',
|
|
8934
|
+
keyPrefix: '',
|
|
8935
|
+
signedUploadTtlSeconds: 60,
|
|
8936
|
+
signedDownloadTtlSeconds: 60
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
});
|
|
8940
|
+
core.cleanupStaleUserAvatarUploadDrafts.mockReturnValueOnce([{
|
|
8941
|
+
draftId: 'avatar-draft-stale',
|
|
8942
|
+
userId: 'u-avatar',
|
|
8943
|
+
storageKey: 'users/u-avatar/profile/avatar/avatar-draft-stale.png',
|
|
8944
|
+
discardedAt: null,
|
|
8945
|
+
consumedAt: null,
|
|
8946
|
+
finalizedAt: null,
|
|
8947
|
+
createdAt: '2026-04-14T00:00:00.000Z',
|
|
8948
|
+
updatedAt: '2026-04-14T00:00:00.000Z'
|
|
8949
|
+
}]);
|
|
8950
|
+
const cookie = createSessionTokenCookie('session-avatar-cleanup', authConfig, {
|
|
8951
|
+
userId: 'u-avatar',
|
|
8952
|
+
role: 'member',
|
|
8953
|
+
workspaceId: 'workspace-1'
|
|
8954
|
+
});
|
|
8955
|
+
const match = matchRoute('GET', '/api/taskforce/auth/session', routes);
|
|
8956
|
+
const req = createMockReq('GET', '/api/taskforce/auth/session', undefined, { cookie });
|
|
8957
|
+
const res = createMockRes();
|
|
8958
|
+
try {
|
|
8959
|
+
const outcome = await Promise.race([
|
|
8960
|
+
match?.handler(req, res, {}).then(() => 'completed'),
|
|
8961
|
+
new Promise((resolve) => setTimeout(() => resolve('timeout'), 25))
|
|
8962
|
+
]);
|
|
8963
|
+
expect(outcome).toBe('completed');
|
|
8964
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8965
|
+
expect(payload.authenticated).toBe(true);
|
|
8966
|
+
expect(deleteObjectSpy).toHaveBeenCalledWith('users/u-avatar/profile/avatar/avatar-draft-stale.png');
|
|
8967
|
+
}
|
|
8968
|
+
finally {
|
|
8969
|
+
if (deleteObjectCompletion.resolve) {
|
|
8970
|
+
deleteObjectCompletion.resolve(true);
|
|
8971
|
+
}
|
|
8972
|
+
deleteObjectSpy.mockRestore();
|
|
8973
|
+
}
|
|
8974
|
+
});
|
|
8774
8975
|
it('POST /api/taskforce/auth/profile should save avatar by validated draft id', async () => {
|
|
8775
8976
|
const authConfig = {
|
|
8776
8977
|
runtimeMode: 'cloud',
|
|
@@ -10300,6 +10501,43 @@ describe('Server Routes', () => {
|
|
|
10300
10501
|
}
|
|
10301
10502
|
});
|
|
10302
10503
|
});
|
|
10504
|
+
it('POST /api/taskforce/admin/system-settings should preserve onboarding policy when onboarding fields are omitted', async () => {
|
|
10505
|
+
core.getOnboardingPolicy.mockReturnValue({
|
|
10506
|
+
mode: 'auto_grant_free',
|
|
10507
|
+
planVersionId: 'starter-v1'
|
|
10508
|
+
});
|
|
10509
|
+
const match = matchRoute('POST', '/api/taskforce/admin/system-settings', routes);
|
|
10510
|
+
const req = createMockReq('POST', '/api/taskforce/admin/system-settings', {
|
|
10511
|
+
allowSelfRegistration: false,
|
|
10512
|
+
requireVerifiedEmail: true,
|
|
10513
|
+
defaultNewUserBetaAccess: false,
|
|
10514
|
+
authRateLimitEnabled: true,
|
|
10515
|
+
authRateLimitMaxRequests: 10,
|
|
10516
|
+
authRateLimitWindowMs: 900000
|
|
10517
|
+
}, { 'x-taskforce-role': 'admin' });
|
|
10518
|
+
const res = createMockRes();
|
|
10519
|
+
await match?.handler(req, res, {});
|
|
10520
|
+
expect(core.validateOnboardingPolicy).toHaveBeenCalledWith({
|
|
10521
|
+
mode: 'auto_grant_free',
|
|
10522
|
+
planVersionId: 'starter-v1'
|
|
10523
|
+
});
|
|
10524
|
+
expect(core.updateGlobalSettings).toHaveBeenCalledWith({
|
|
10525
|
+
auth: {
|
|
10526
|
+
allowSelfRegistration: false,
|
|
10527
|
+
requireVerifiedEmail: true,
|
|
10528
|
+
defaultNewUserBetaAccess: false,
|
|
10529
|
+
authRateLimitEnabled: true,
|
|
10530
|
+
authRateLimitMaxRequests: 10,
|
|
10531
|
+
authRateLimitWindowMs: 900000
|
|
10532
|
+
},
|
|
10533
|
+
billing: {
|
|
10534
|
+
onboardingPolicy: {
|
|
10535
|
+
mode: 'auto_grant_free',
|
|
10536
|
+
planVersionId: 'starter-v1'
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
});
|
|
10540
|
+
});
|
|
10303
10541
|
it('GET /api/taskforce/admin/site-settings should expose effective website settings', async () => {
|
|
10304
10542
|
core.getGlobalSettings.mockReturnValue({
|
|
10305
10543
|
site: {
|
|
@@ -11571,7 +11809,7 @@ describe('Server Routes', () => {
|
|
|
11571
11809
|
errors: []
|
|
11572
11810
|
}));
|
|
11573
11811
|
});
|
|
11574
|
-
it('PATCH /api/taskforce/task/:id should pass autosave
|
|
11812
|
+
it('PATCH /api/taskforce/task/:id should pass autosave metadata through to core.updateTask', async () => {
|
|
11575
11813
|
const updatedTask = { id: '123', description: 'Draft body' };
|
|
11576
11814
|
core.updateTask.mockReturnValue(updatedTask);
|
|
11577
11815
|
const url = '/api/taskforce/task/123';
|
|
@@ -11581,12 +11819,12 @@ describe('Server Routes', () => {
|
|
|
11581
11819
|
await match?.handler(req, res, { id: '123' });
|
|
11582
11820
|
expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Draft body' }, 'default', {
|
|
11583
11821
|
actorRef: 'user',
|
|
11584
|
-
|
|
11822
|
+
saveSource: 'autosave'
|
|
11585
11823
|
});
|
|
11586
11824
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
11587
11825
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
|
|
11588
11826
|
});
|
|
11589
|
-
it('PATCH /api/taskforce/task/:id should
|
|
11827
|
+
it('PATCH /api/taskforce/task/:id should pass manual save metadata through to core.updateTask', async () => {
|
|
11590
11828
|
const updatedTask = { id: '123', description: 'Manual body' };
|
|
11591
11829
|
core.updateTask.mockReturnValue(updatedTask);
|
|
11592
11830
|
const url = '/api/taskforce/task/123';
|
|
@@ -11596,7 +11834,7 @@ describe('Server Routes', () => {
|
|
|
11596
11834
|
await match?.handler(req, res, { id: '123' });
|
|
11597
11835
|
expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Manual body' }, 'default', {
|
|
11598
11836
|
actorRef: 'user',
|
|
11599
|
-
|
|
11837
|
+
saveSource: 'manual'
|
|
11600
11838
|
});
|
|
11601
11839
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
11602
11840
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -39,6 +39,10 @@ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurface
|
|
|
39
39
|
username: String(profile.username || '').trim(),
|
|
40
40
|
icon: String(profile.icon || 'Bot').trim() || 'Bot',
|
|
41
41
|
color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
42
|
+
description: toNullableTrimmedString(profile.description),
|
|
43
|
+
role: toNullableTrimmedString(profile.role),
|
|
44
|
+
provider: toNullableTrimmedString(profile.provider),
|
|
45
|
+
model: toNullableTrimmedString(profile.model),
|
|
42
46
|
surfaceType: normalizeSurfaceType(profile.surfaceType, { source, profileId, workspaceId: profileWorkspaceId }),
|
|
43
47
|
providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
|
|
44
48
|
? profile.providerMetadata
|
|
@@ -15,6 +15,10 @@ describe('collaborationSyncPayload', () => {
|
|
|
15
15
|
username: ' agent ',
|
|
16
16
|
icon: ' Bot ',
|
|
17
17
|
color: ' #111111 ',
|
|
18
|
+
description: ' Product coding agent ',
|
|
19
|
+
role: ' Implementer ',
|
|
20
|
+
provider: ' OpenAI ',
|
|
21
|
+
model: ' gpt-5.4 ',
|
|
18
22
|
surfaceType: 'agent',
|
|
19
23
|
providerMetadata: { foo: 'bar' },
|
|
20
24
|
createdAt: '2026-04-10T10:00:00.000Z',
|
|
@@ -29,6 +33,10 @@ describe('collaborationSyncPayload', () => {
|
|
|
29
33
|
username: 'agent',
|
|
30
34
|
icon: 'Bot',
|
|
31
35
|
color: '#111111',
|
|
36
|
+
description: 'Product coding agent',
|
|
37
|
+
role: 'Implementer',
|
|
38
|
+
provider: 'OpenAI',
|
|
39
|
+
model: 'gpt-5.4',
|
|
32
40
|
surfaceType: 'agent',
|
|
33
41
|
providerMetadata: { foo: 'bar' },
|
|
34
42
|
createdAt: '2026-04-10T10:00:00.000Z',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
|
|
2
|
-
import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
|
|
2
|
+
import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncTaskEventSnapshot } from './workspaceSyncModel.js';
|
|
3
3
|
type SyncCore = TaskforceCore & TaskforceSyncCapable;
|
|
4
4
|
interface CollaborationSyncStateDeps {
|
|
5
5
|
core: SyncCore;
|
|
@@ -7,9 +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): WorkspaceSyncTaskEventSnapshot[];
|
|
10
11
|
export declare function createCollaborationSyncState(deps: CollaborationSyncStateDeps): {
|
|
11
12
|
listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
12
13
|
listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
13
14
|
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
15
|
+
listWorkspaceSyncTaskEventsSnapshot: (workspaceId?: string) => WorkspaceSyncTaskEventSnapshot[];
|
|
14
16
|
};
|
|
15
17
|
export {};
|
|
@@ -51,10 +51,29 @@ export function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, works
|
|
|
51
51
|
&& session.workspaceId.length > 0
|
|
52
52
|
&& session.baseImageAssetId.length > 0));
|
|
53
53
|
}
|
|
54
|
+
export function listWorkspaceSyncTaskEventsSnapshot(deps, workspaceId = 'default') {
|
|
55
|
+
return deps.core.listTaskEventsForSync({ workspaceId })
|
|
56
|
+
.map((event) => ({
|
|
57
|
+
id: event.id,
|
|
58
|
+
taskId: event.taskId,
|
|
59
|
+
workspaceId: event.workspaceId,
|
|
60
|
+
action: event.action,
|
|
61
|
+
actor: event.actor,
|
|
62
|
+
actorType: event.actorType,
|
|
63
|
+
details: event.details,
|
|
64
|
+
createdAt: event.createdAt,
|
|
65
|
+
}))
|
|
66
|
+
.filter((event) => (event.id.length > 0
|
|
67
|
+
&& event.taskId.length > 0
|
|
68
|
+
&& event.workspaceId.length > 0
|
|
69
|
+
&& event.action.length > 0
|
|
70
|
+
&& event.createdAt.length > 0));
|
|
71
|
+
}
|
|
54
72
|
export function createCollaborationSyncState(deps) {
|
|
55
73
|
return {
|
|
56
74
|
listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewSessionsSnapshot(deps, workspaceId),
|
|
57
75
|
listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewCommentsSnapshot(deps, workspaceId),
|
|
58
76
|
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, workspaceId),
|
|
77
|
+
listWorkspaceSyncTaskEventsSnapshot: (workspaceId = 'default') => listWorkspaceSyncTaskEventsSnapshot(deps, workspaceId),
|
|
59
78
|
};
|
|
60
79
|
}
|