@taskforcehq/taskforce 0.3.301 → 0.3.302
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 +26 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +13 -24
- package/dist/TaskforceCore.test.js +167 -152
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.js +9 -8
- package/dist/components/views/StandaloneLayout.js +466 -204
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +49 -0
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +241 -78
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +5 -0
- package/dist/hooks/useTaskforce.js +63 -15
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +41 -19
- package/dist/mcp/runtime.test.js +49 -2
- package/dist/migrations/taskSchemaMigrations.js +53 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +15 -1
- package/dist/server/index.test.js +14 -1
- package/dist/server/routes/admin.js +25 -22
- package/dist/server/routes/auth.d.ts +5 -0
- package/dist/server/routes/auth.js +350 -16
- package/dist/server/routes/billing.js +11 -1
- package/dist/server/routes/billing.test.js +32 -2
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +11 -3
- package/dist/server/routes/sync.integration.test.js +130 -0
- package/dist/server/routes/sync.js +226 -1924
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +26 -19
- package/dist/server/routes.test.js +352 -6
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +88 -0
- package/dist/sync/contentSyncState.js +743 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +28 -0
- package/dist/sync/workspacePullFeed.js +301 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +166 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-JG5jc3he.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-iys3zfca.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-DJvLJI3A.js} +1 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-BguHeQaV.js} +1 -1
- package/dist/ui/assets/index-MXeWoebC.css +1 -0
- package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/billingStatusCache.d.ts +21 -0
- package/dist/utils/billingStatusCache.js +70 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +2 -1
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
|
@@ -58,6 +58,7 @@ import { parseJsonBody } from '../routes.js';
|
|
|
58
58
|
import { resolveErrorStatusCode, requestHeader, requestRuntimeMode } from './shared.js';
|
|
59
59
|
import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
|
|
60
60
|
import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
|
|
61
|
+
import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
|
|
61
62
|
// === SECTION: Registration ===
|
|
62
63
|
export function registerTaskRoutes(deps) {
|
|
63
64
|
const { addRoute, core, context, requestWorkspaceId, ensureAppAccess, resolveEffectiveObjectStorageConfig, buildBatchSummary, CLOUD_SAFE_CONFIG_PATHS, } = deps;
|
|
@@ -343,7 +344,8 @@ export function registerTaskRoutes(deps) {
|
|
|
343
344
|
const result = core.bulkCreateTasks([taskInput], {
|
|
344
345
|
atomic: true,
|
|
345
346
|
validateOnly: true,
|
|
346
|
-
workspaceId
|
|
347
|
+
workspaceId,
|
|
348
|
+
provisionalLocalReference: shouldUseProvisionalTaskReferences(requestRuntimeMode(req))
|
|
347
349
|
});
|
|
348
350
|
const hasErrors = result.errors.length > 0;
|
|
349
351
|
const failureStatus = hasErrors
|
|
@@ -361,7 +363,11 @@ export function registerTaskRoutes(deps) {
|
|
|
361
363
|
}
|
|
362
364
|
try {
|
|
363
365
|
const workspaceId = requestWorkspaceId(req);
|
|
364
|
-
const task = core.createTask(taskInput, {
|
|
366
|
+
const task = core.createTask(taskInput, {
|
|
367
|
+
workspaceId,
|
|
368
|
+
actorRef: 'user',
|
|
369
|
+
provisionalLocalReference: shouldUseProvisionalTaskReferences(requestRuntimeMode(req))
|
|
370
|
+
});
|
|
365
371
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
366
372
|
res.end(JSON.stringify(task));
|
|
367
373
|
}
|
|
@@ -386,7 +392,14 @@ export function registerTaskRoutes(deps) {
|
|
|
386
392
|
const body = await parseJsonBody(req);
|
|
387
393
|
try {
|
|
388
394
|
const workspaceId = requestWorkspaceId(req);
|
|
389
|
-
const
|
|
395
|
+
const saveSource = body?.saveSource === 'autosave' ? 'autosave' : 'manual';
|
|
396
|
+
const updates = body && typeof body === 'object'
|
|
397
|
+
? Object.fromEntries(Object.entries(body).filter(([key]) => key !== 'saveSource'))
|
|
398
|
+
: body;
|
|
399
|
+
const updated = core.updateTask(params.id, updates, workspaceId, {
|
|
400
|
+
actorRef: 'user',
|
|
401
|
+
skipTaskEvents: saveSource === 'autosave'
|
|
402
|
+
});
|
|
390
403
|
if (!updated) {
|
|
391
404
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
392
405
|
res.end(JSON.stringify({ error: 'Task not found' }));
|
|
@@ -869,7 +882,8 @@ export function registerTaskRoutes(deps) {
|
|
|
869
882
|
const result = core.bulkCreateTasks(tasks, {
|
|
870
883
|
atomic: atomic !== false,
|
|
871
884
|
validateOnly: validate_only === true,
|
|
872
|
-
workspaceId
|
|
885
|
+
workspaceId,
|
|
886
|
+
provisionalLocalReference: shouldUseProvisionalTaskReferences(requestRuntimeMode(req))
|
|
873
887
|
});
|
|
874
888
|
const hasErrors = result.errors.length > 0;
|
|
875
889
|
const succeeded = validate_only ? (hasErrors ? 0 : result.total) : result.created.length;
|
package/dist/server/routes.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createHash, randomUUID } from 'crypto';
|
|
|
10
10
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
11
11
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
12
12
|
import { TaskforceRuleError } from '../core/Taskforce.js';
|
|
13
|
+
import { parseAiProfileBootstrapHeaders } from '../mcp/aiProfileBootstrap.js';
|
|
13
14
|
import { createTaskforceMcpServer, isMcpWriteToolName } from '../mcp/runtime.js';
|
|
14
15
|
import { applyPrefixedRateLimitHeaders } from './rateLimit.js';
|
|
15
16
|
import { createSessionTokenCookie, resolveRequestAccess } from './auth.js';
|
|
@@ -69,6 +70,20 @@ function readPositiveInteger(value, fallback) {
|
|
|
69
70
|
return fallback;
|
|
70
71
|
return parsed;
|
|
71
72
|
}
|
|
73
|
+
function resolveBooleanSetting(value, fallback) {
|
|
74
|
+
if (typeof value === 'boolean')
|
|
75
|
+
return value;
|
|
76
|
+
if (typeof value === 'number')
|
|
77
|
+
return value !== 0;
|
|
78
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
79
|
+
if (!normalized)
|
|
80
|
+
return fallback;
|
|
81
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
82
|
+
return true;
|
|
83
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
84
|
+
return false;
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
72
87
|
// Convert route pattern like '/api/taskforce/tasks/:id' to regex
|
|
73
88
|
function createRoutePattern(path) {
|
|
74
89
|
const keys = [];
|
|
@@ -332,10 +347,10 @@ export function createRoutes(core, context = {}) {
|
|
|
332
347
|
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
333
348
|
const signupSettings = core.getSignupMonetizationSettings();
|
|
334
349
|
return {
|
|
335
|
-
allowSelfRegistration: authSettings.allowSelfRegistration
|
|
336
|
-
requireVerifiedEmail: authSettings.requireVerifiedEmail
|
|
337
|
-
defaultNewUserBetaAccess: authSettings.defaultNewUserBetaAccess
|
|
338
|
-
authRateLimitEnabled: authSettings.authRateLimitEnabled
|
|
350
|
+
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
351
|
+
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
352
|
+
defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
|
|
353
|
+
authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
|
|
339
354
|
authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
|
|
340
355
|
? Math.floor(authRateLimitMaxRequestsRaw)
|
|
341
356
|
: 10,
|
|
@@ -1124,13 +1139,7 @@ export function createRoutes(core, context = {}) {
|
|
|
1124
1139
|
? authorization.slice(7).trim()
|
|
1125
1140
|
: '';
|
|
1126
1141
|
const requestedWorkspaceId = requestHeader(req, 'x-taskforce-workspace-id', '').trim();
|
|
1127
|
-
const
|
|
1128
|
-
const aiProfileName = requestHeader(req, 'x-taskforce-ai-name', '').trim() || null;
|
|
1129
|
-
const aiProfileDescription = requestHeader(req, 'x-taskforce-ai-description', '').trim() || null;
|
|
1130
|
-
const aiProfileRole = requestHeader(req, 'x-taskforce-ai-role', '').trim() || null;
|
|
1131
|
-
const aiProfileProvider = requestHeader(req, 'x-taskforce-ai-provider', '').trim() || null;
|
|
1132
|
-
const aiProfileModel = requestHeader(req, 'x-taskforce-ai-model', '').trim() || null;
|
|
1133
|
-
const aiProfileSurfaceType = requestHeader(req, 'x-taskforce-ai-surface-type', '').trim() || null;
|
|
1142
|
+
const bootstrap = parseAiProfileBootstrapHeaders(req.headers);
|
|
1134
1143
|
if (bearerToken) {
|
|
1135
1144
|
const tokenAuth = core.authenticateMcpAccessToken(bearerToken);
|
|
1136
1145
|
const oauthAuth = tokenAuth ? null : core.authenticateMcpOAuthAccessToken(bearerToken);
|
|
@@ -1178,13 +1187,7 @@ export function createRoutes(core, context = {}) {
|
|
|
1178
1187
|
authenticatedViaToken: tokenAuth || null,
|
|
1179
1188
|
authenticatedViaOAuth: oauthAuth || null,
|
|
1180
1189
|
userId: null,
|
|
1181
|
-
|
|
1182
|
-
aiProfileName,
|
|
1183
|
-
aiProfileDescription,
|
|
1184
|
-
aiProfileRole,
|
|
1185
|
-
aiProfileProvider,
|
|
1186
|
-
aiProfileModel,
|
|
1187
|
-
aiProfileSurfaceType,
|
|
1190
|
+
...bootstrap,
|
|
1188
1191
|
};
|
|
1189
1192
|
}
|
|
1190
1193
|
core.addMcpAuditLog({
|
|
@@ -1461,6 +1464,8 @@ export function createRoutes(core, context = {}) {
|
|
|
1461
1464
|
tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
|
|
1462
1465
|
aiProfileToken: auth.aiProfileToken,
|
|
1463
1466
|
aiProfileName: auth.aiProfileName,
|
|
1467
|
+
aiProfileIcon: auth.aiProfileIcon,
|
|
1468
|
+
aiProfileColor: auth.aiProfileColor,
|
|
1464
1469
|
aiProfileDescription: auth.aiProfileDescription,
|
|
1465
1470
|
aiProfileRole: auth.aiProfileRole,
|
|
1466
1471
|
aiProfileProvider: auth.aiProfileProvider,
|
|
@@ -1495,7 +1500,7 @@ export function createRoutes(core, context = {}) {
|
|
|
1495
1500
|
};
|
|
1496
1501
|
const resolveAdminRequestActor = makeResolveAdminRequestActor(core, context);
|
|
1497
1502
|
const auditAdminAction = makeAuditAdminAction(core, context);
|
|
1498
|
-
const auditAuthAction = makeAuditAuthAction(core, requestWorkspaceId);
|
|
1503
|
+
const auditAuthAction = makeAuditAuthAction(core, requestWorkspaceId, context);
|
|
1499
1504
|
const ensureAdminRole = makeEnsureAdminRole(core, context);
|
|
1500
1505
|
const ensureAuthenticatedUser = makeEnsureAuthenticatedUser();
|
|
1501
1506
|
const isCollaborationPolicyEnabled = () => {
|
|
@@ -1560,6 +1565,8 @@ export function createRoutes(core, context = {}) {
|
|
|
1560
1565
|
shouldExposeDevAuthTokens,
|
|
1561
1566
|
resolveRuntimeConfigBaseUrl,
|
|
1562
1567
|
resolvePublicAuthBaseUrl,
|
|
1568
|
+
resolveEffectiveObjectStorageConfig,
|
|
1569
|
+
getObjectStorageClient,
|
|
1563
1570
|
});
|
|
1564
1571
|
registerWorkspaceRoutes({
|
|
1565
1572
|
addRoute,
|
|
@@ -462,10 +462,22 @@ vi.mock('../core/Taskforce', () => {
|
|
|
462
462
|
}));
|
|
463
463
|
resolveUserIdentity = vi.fn().mockImplementation((userId) => {
|
|
464
464
|
if (userId === 'admin-user') {
|
|
465
|
-
return { userId, email: 'admin@example.com' };
|
|
465
|
+
return { userId, email: 'admin@example.com', avatarUrl: null };
|
|
466
466
|
}
|
|
467
467
|
return null;
|
|
468
468
|
});
|
|
469
|
+
updateUserProfile = vi.fn().mockImplementation((userId, input) => ({
|
|
470
|
+
userId,
|
|
471
|
+
email: `${userId}@example.com`,
|
|
472
|
+
displayName: input.displayName,
|
|
473
|
+
avatarUrl: input.avatarUrl === undefined ? null : input.avatarUrl
|
|
474
|
+
}));
|
|
475
|
+
createUserAvatarUploadDraft = vi.fn();
|
|
476
|
+
getUserAvatarUploadDraft = vi.fn().mockReturnValue(null);
|
|
477
|
+
markUserAvatarUploadDraftFinalized = vi.fn().mockReturnValue(null);
|
|
478
|
+
markUserAvatarUploadDraftConsumed = vi.fn().mockReturnValue(null);
|
|
479
|
+
discardUserAvatarUploadDraft = vi.fn().mockReturnValue(null);
|
|
480
|
+
cleanupStaleUserAvatarUploadDrafts = vi.fn().mockReturnValue([]);
|
|
469
481
|
resolvePreferredWorkspaceId = vi.fn().mockImplementation((options) => (options?.allowDefault ? 'default' : null));
|
|
470
482
|
resolvePreferredUserWorkspaceId = vi.fn().mockImplementation((_userId, options) => {
|
|
471
483
|
const preferred = String(options?.preferredWorkspaceId || '').trim();
|
|
@@ -863,6 +875,8 @@ describe('Server Routes', () => {
|
|
|
863
875
|
authorization: 'Bearer tfmcp_ai_header_token',
|
|
864
876
|
'x-taskforce-ai-name': 'Automation Codex',
|
|
865
877
|
'x-taskforce-ai-profile-token': 'tfp_automation_codex',
|
|
878
|
+
'x-taskforce-ai-icon': 'Code2',
|
|
879
|
+
'x-taskforce-ai-color': '#10a37f',
|
|
866
880
|
'x-taskforce-ai-description': 'OpenAI Codex coding agent connected through MCP.',
|
|
867
881
|
'x-taskforce-ai-role': 'Coding Agent',
|
|
868
882
|
'x-taskforce-ai-provider': 'OpenAI',
|
|
@@ -3511,7 +3525,11 @@ describe('Server Routes', () => {
|
|
|
3511
3525
|
const req = createMockReq('POST', '/api/taskforce/task', newTask);
|
|
3512
3526
|
const res = createMockRes();
|
|
3513
3527
|
await match?.handler(req, res, {});
|
|
3514
|
-
expect(core.createTask).toHaveBeenCalledWith(newTask, {
|
|
3528
|
+
expect(core.createTask).toHaveBeenCalledWith(newTask, {
|
|
3529
|
+
workspaceId: 'default',
|
|
3530
|
+
actorRef: 'user',
|
|
3531
|
+
provisionalLocalReference: true
|
|
3532
|
+
});
|
|
3515
3533
|
expect(res.writeHead).toHaveBeenCalledWith(201, expect.any(Object));
|
|
3516
3534
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify(createdTask));
|
|
3517
3535
|
});
|
|
@@ -3591,7 +3609,7 @@ describe('Server Routes', () => {
|
|
|
3591
3609
|
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
3592
3610
|
expect(payload?.config?.baseUrl).toBe('http://127.0.0.1:4271');
|
|
3593
3611
|
expect(payload?.config?.apiBaseUrl).toBe('http://127.0.0.1:4271');
|
|
3594
|
-
expect(payload?.config?.cloudAuthBaseUrl).toBe('
|
|
3612
|
+
expect(payload?.config?.cloudAuthBaseUrl).toBe('https://app.taskforcehq.ai');
|
|
3595
3613
|
expect(payload?.config?.wsBaseUrl).toBe('http://127.0.0.1:4271');
|
|
3596
3614
|
expect(payload?.config?.cloudMcpBaseUrl).toBe('https://mcp.taskforcehq.ai');
|
|
3597
3615
|
}
|
|
@@ -4172,6 +4190,44 @@ describe('Server Routes', () => {
|
|
|
4172
4190
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
4173
4191
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining('"success":true'));
|
|
4174
4192
|
});
|
|
4193
|
+
it('POST /api/taskforce/auth/login should treat string false as disabled verified-email enforcement', async () => {
|
|
4194
|
+
const authConfig = {
|
|
4195
|
+
runtimeMode: 'cloud',
|
|
4196
|
+
enabled: true,
|
|
4197
|
+
requiredForApi: true,
|
|
4198
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
4199
|
+
defaultRole: 'member',
|
|
4200
|
+
defaultWorkspaceId: 'default',
|
|
4201
|
+
sessionCookieName: 'taskforce_session',
|
|
4202
|
+
sessionSecret: 'test-secret',
|
|
4203
|
+
sessionTtlSeconds: 3600
|
|
4204
|
+
};
|
|
4205
|
+
core.getGlobalSettings.mockReturnValue({
|
|
4206
|
+
auth: {
|
|
4207
|
+
allowSelfRegistration: true,
|
|
4208
|
+
requireVerifiedEmail: 'false'
|
|
4209
|
+
}
|
|
4210
|
+
});
|
|
4211
|
+
routes = createRoutes(core, { authConfig });
|
|
4212
|
+
core.resolvePreferredUserWorkspaceId.mockReturnValueOnce(null);
|
|
4213
|
+
core.authenticatePasswordUserDetailed.mockReturnValueOnce({
|
|
4214
|
+
ok: true,
|
|
4215
|
+
access: { userId: 'u2', workspaceId: 'default', role: 'member' }
|
|
4216
|
+
});
|
|
4217
|
+
const match = matchRoute('POST', '/api/taskforce/auth/login', routes);
|
|
4218
|
+
const req = createMockReq('POST', '/api/taskforce/auth/login', {
|
|
4219
|
+
email: 'user@example.com',
|
|
4220
|
+
password: 'password123'
|
|
4221
|
+
});
|
|
4222
|
+
const res = createMockRes();
|
|
4223
|
+
res.setHeader = vi.fn();
|
|
4224
|
+
await match?.handler(req, res, {});
|
|
4225
|
+
expect(core.authenticatePasswordUserDetailed).toHaveBeenCalledWith(expect.objectContaining({
|
|
4226
|
+
email: 'user@example.com',
|
|
4227
|
+
requireVerifiedEmail: false
|
|
4228
|
+
}));
|
|
4229
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
4230
|
+
});
|
|
4175
4231
|
it('POST /api/taskforce/auth/login should re-scope default session workspace to accessible membership', async () => {
|
|
4176
4232
|
const authConfig = {
|
|
4177
4233
|
runtimeMode: 'cloud',
|
|
@@ -8673,6 +8729,215 @@ describe('Server Routes', () => {
|
|
|
8673
8729
|
expect(payload.betaAccess).toBe(false);
|
|
8674
8730
|
expect(core.getUserBetaAccess).toHaveBeenCalledWith('u-no-workspace');
|
|
8675
8731
|
});
|
|
8732
|
+
it('GET /api/taskforce/auth/session should expose avatarUrl from the resolved profile', async () => {
|
|
8733
|
+
const authConfig = {
|
|
8734
|
+
runtimeMode: 'cloud',
|
|
8735
|
+
enabled: true,
|
|
8736
|
+
requiredForApi: true,
|
|
8737
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
8738
|
+
defaultRole: 'member',
|
|
8739
|
+
defaultWorkspaceId: 'default',
|
|
8740
|
+
sessionCookieName: 'taskforce_session',
|
|
8741
|
+
sessionSecret: 'test-secret',
|
|
8742
|
+
sessionTtlSeconds: 3600
|
|
8743
|
+
};
|
|
8744
|
+
routes = createRoutes(core, { authConfig });
|
|
8745
|
+
core.resolveUserIdentity.mockReturnValueOnce({
|
|
8746
|
+
userId: 'u-avatar',
|
|
8747
|
+
email: 'avatar@example.com',
|
|
8748
|
+
displayName: 'Avatar User',
|
|
8749
|
+
avatarUrl: '/api/taskforce/context/users%2Fu-avatar%2Fprofile%2Favatar%2Fdraft.png'
|
|
8750
|
+
});
|
|
8751
|
+
const cookie = createSessionTokenCookie('session-avatar-auth', authConfig, {
|
|
8752
|
+
userId: 'u-avatar',
|
|
8753
|
+
role: 'member',
|
|
8754
|
+
workspaceId: 'workspace-1'
|
|
8755
|
+
});
|
|
8756
|
+
const match = matchRoute('GET', '/api/taskforce/auth/session', routes);
|
|
8757
|
+
const req = createMockReq('GET', '/api/taskforce/auth/session', undefined, { cookie });
|
|
8758
|
+
const res = createMockRes();
|
|
8759
|
+
await match?.handler(req, res, {});
|
|
8760
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8761
|
+
expect(payload.authenticated).toBe(true);
|
|
8762
|
+
expect(payload.avatarUrl).toBe('/api/taskforce/context/users%2Fu-avatar%2Fprofile%2Favatar%2Fdraft.png');
|
|
8763
|
+
});
|
|
8764
|
+
it('POST /api/taskforce/auth/profile should save avatar by validated draft id', async () => {
|
|
8765
|
+
const authConfig = {
|
|
8766
|
+
runtimeMode: 'cloud',
|
|
8767
|
+
enabled: true,
|
|
8768
|
+
requiredForApi: true,
|
|
8769
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
8770
|
+
defaultRole: 'member',
|
|
8771
|
+
defaultWorkspaceId: 'default',
|
|
8772
|
+
sessionCookieName: 'taskforce_session',
|
|
8773
|
+
sessionSecret: 'test-secret',
|
|
8774
|
+
sessionTtlSeconds: 3600
|
|
8775
|
+
};
|
|
8776
|
+
routes = createRoutes(core, { authConfig });
|
|
8777
|
+
core.getUserAvatarUploadDraft.mockReturnValueOnce({
|
|
8778
|
+
draftId: 'avatar-draft-1',
|
|
8779
|
+
userId: 'u1',
|
|
8780
|
+
storageKey: 'users/u1/profile/avatar/avatar-draft-1-headshot.png',
|
|
8781
|
+
finalizedAt: '2026-04-09T00:00:00.000Z',
|
|
8782
|
+
discardedAt: null
|
|
8783
|
+
});
|
|
8784
|
+
core.resolveUserIdentity.mockReturnValueOnce({
|
|
8785
|
+
userId: 'u1',
|
|
8786
|
+
email: 'u1@example.com',
|
|
8787
|
+
displayName: 'Before',
|
|
8788
|
+
avatarUrl: null
|
|
8789
|
+
});
|
|
8790
|
+
core.updateUserProfile.mockReturnValueOnce({
|
|
8791
|
+
userId: 'u1',
|
|
8792
|
+
email: 'u1@example.com',
|
|
8793
|
+
displayName: 'Updated Name',
|
|
8794
|
+
avatarUrl: '/api/taskforce/context/users%2Fu1%2Fprofile%2Favatar%2Favatar-draft-1-headshot.png'
|
|
8795
|
+
});
|
|
8796
|
+
const cookie = createSessionTokenCookie('session-profile-auth', authConfig, {
|
|
8797
|
+
userId: 'u1',
|
|
8798
|
+
role: 'member',
|
|
8799
|
+
workspaceId: 'workspace-1'
|
|
8800
|
+
});
|
|
8801
|
+
const match = matchRoute('POST', '/api/taskforce/auth/profile', routes);
|
|
8802
|
+
const req = createMockReq('POST', '/api/taskforce/auth/profile', {
|
|
8803
|
+
displayName: 'Updated Name',
|
|
8804
|
+
avatarDraftId: 'avatar-draft-1'
|
|
8805
|
+
}, { cookie });
|
|
8806
|
+
const res = createMockRes();
|
|
8807
|
+
await match?.handler(req, res, {});
|
|
8808
|
+
expect(core.updateUserProfile).toHaveBeenCalledWith('u1', {
|
|
8809
|
+
displayName: 'Updated Name',
|
|
8810
|
+
avatarUrl: '/api/taskforce/context/users%2Fu1%2Fprofile%2Favatar%2Favatar-draft-1-headshot.png'
|
|
8811
|
+
});
|
|
8812
|
+
expect(core.addAdminAuditLog).toHaveBeenCalledWith(expect.objectContaining({
|
|
8813
|
+
action: 'auth-profile-updated',
|
|
8814
|
+
actorUserId: 'u1',
|
|
8815
|
+
actorRole: 'member'
|
|
8816
|
+
}));
|
|
8817
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8818
|
+
expect(payload.profile.avatarUrl).toBe('/api/taskforce/context/users%2Fu1%2Fprofile%2Favatar%2Favatar-draft-1-headshot.png');
|
|
8819
|
+
});
|
|
8820
|
+
it('POST /api/taskforce/auth/profile/avatar/init should create a signed avatar upload draft', async () => {
|
|
8821
|
+
const authConfig = {
|
|
8822
|
+
runtimeMode: 'cloud',
|
|
8823
|
+
enabled: true,
|
|
8824
|
+
requiredForApi: true,
|
|
8825
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
8826
|
+
defaultRole: 'member',
|
|
8827
|
+
defaultWorkspaceId: 'default',
|
|
8828
|
+
sessionCookieName: 'taskforce_session',
|
|
8829
|
+
sessionSecret: 'test-secret',
|
|
8830
|
+
sessionTtlSeconds: 3600
|
|
8831
|
+
};
|
|
8832
|
+
const routesWithStorage = createRoutes(core, {
|
|
8833
|
+
authConfig,
|
|
8834
|
+
objectStorage: {
|
|
8835
|
+
provider: 'r2',
|
|
8836
|
+
r2: {
|
|
8837
|
+
accountId: 'acct-1',
|
|
8838
|
+
bucket: 'taskforce-docs',
|
|
8839
|
+
endpoint: 'https://example.r2.cloudflarestorage.com',
|
|
8840
|
+
accessKeyId: 'access-key-1',
|
|
8841
|
+
secretAccessKey: 'secret-key-1',
|
|
8842
|
+
keyPrefix: 'taskforce',
|
|
8843
|
+
signedUploadTtlSeconds: 600,
|
|
8844
|
+
signedDownloadTtlSeconds: 300
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
});
|
|
8848
|
+
const cookie = createSessionTokenCookie('session-avatar-init', authConfig, {
|
|
8849
|
+
userId: 'u1',
|
|
8850
|
+
role: 'member',
|
|
8851
|
+
workspaceId: 'workspace-1'
|
|
8852
|
+
});
|
|
8853
|
+
const match = matchRoute('POST', '/api/taskforce/auth/profile/avatar/init', routesWithStorage);
|
|
8854
|
+
const req = createMockReq('POST', '/api/taskforce/auth/profile/avatar/init', {
|
|
8855
|
+
originalName: 'headshot.png',
|
|
8856
|
+
mimeType: 'image/png',
|
|
8857
|
+
size: 1024
|
|
8858
|
+
}, { cookie });
|
|
8859
|
+
const res = createMockRes();
|
|
8860
|
+
await match?.handler(req, res, {});
|
|
8861
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8862
|
+
expect(payload.success).toBe(true);
|
|
8863
|
+
expect(payload.draftId).toMatch(/^avatar-draft-/);
|
|
8864
|
+
expect(payload.relativePath).toMatch(/^users\/u1\/profile\/avatar\/avatar-draft-[a-f0-9]{32}-headshot\.png$/);
|
|
8865
|
+
expect(payload.uploadUrl).toContain('X-Amz-Signature=');
|
|
8866
|
+
expect(core.createUserAvatarUploadDraft).toHaveBeenCalledWith(expect.objectContaining({
|
|
8867
|
+
userId: 'u1',
|
|
8868
|
+
draftId: payload.draftId,
|
|
8869
|
+
storageKey: payload.relativePath,
|
|
8870
|
+
mimeType: 'image/png'
|
|
8871
|
+
}));
|
|
8872
|
+
});
|
|
8873
|
+
it('POST /api/taskforce/auth/profile/avatar/finalize should validate and finalize the uploaded draft', async () => {
|
|
8874
|
+
const authConfig = {
|
|
8875
|
+
runtimeMode: 'cloud',
|
|
8876
|
+
enabled: true,
|
|
8877
|
+
requiredForApi: true,
|
|
8878
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
8879
|
+
defaultRole: 'member',
|
|
8880
|
+
defaultWorkspaceId: 'default',
|
|
8881
|
+
sessionCookieName: 'taskforce_session',
|
|
8882
|
+
sessionSecret: 'test-secret',
|
|
8883
|
+
sessionTtlSeconds: 3600
|
|
8884
|
+
};
|
|
8885
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
8886
|
+
ok: true,
|
|
8887
|
+
status: 200,
|
|
8888
|
+
statusText: 'OK',
|
|
8889
|
+
headers: { get: () => 'image/png' },
|
|
8890
|
+
arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer
|
|
8891
|
+
});
|
|
8892
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
8893
|
+
const routesWithStorage = createRoutes(core, {
|
|
8894
|
+
authConfig,
|
|
8895
|
+
objectStorage: {
|
|
8896
|
+
provider: 'r2',
|
|
8897
|
+
r2: {
|
|
8898
|
+
accountId: 'acct-1',
|
|
8899
|
+
bucket: 'taskforce-docs',
|
|
8900
|
+
endpoint: 'https://example.r2.cloudflarestorage.com',
|
|
8901
|
+
accessKeyId: 'access-key-1',
|
|
8902
|
+
secretAccessKey: 'secret-key-1',
|
|
8903
|
+
keyPrefix: 'taskforce',
|
|
8904
|
+
signedUploadTtlSeconds: 600,
|
|
8905
|
+
signedDownloadTtlSeconds: 300
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
});
|
|
8909
|
+
core.getUserAvatarUploadDraft.mockReturnValueOnce({
|
|
8910
|
+
draftId: 'avatar-draft-1',
|
|
8911
|
+
userId: 'u1',
|
|
8912
|
+
storageKey: 'users/u1/profile/avatar/avatar-draft-1-headshot.png',
|
|
8913
|
+
finalizedAt: null,
|
|
8914
|
+
discardedAt: null
|
|
8915
|
+
});
|
|
8916
|
+
core.markUserAvatarUploadDraftFinalized.mockReturnValueOnce({
|
|
8917
|
+
draftId: 'avatar-draft-1',
|
|
8918
|
+
userId: 'u1',
|
|
8919
|
+
storageKey: 'users/u1/profile/avatar/avatar-draft-1-headshot.png',
|
|
8920
|
+
finalizedAt: '2026-04-09T00:00:00.000Z',
|
|
8921
|
+
discardedAt: null
|
|
8922
|
+
});
|
|
8923
|
+
const cookie = createSessionTokenCookie('session-avatar-finalize', authConfig, {
|
|
8924
|
+
userId: 'u1',
|
|
8925
|
+
role: 'member',
|
|
8926
|
+
workspaceId: 'workspace-1'
|
|
8927
|
+
});
|
|
8928
|
+
const match = matchRoute('POST', '/api/taskforce/auth/profile/avatar/finalize', routesWithStorage);
|
|
8929
|
+
const req = createMockReq('POST', '/api/taskforce/auth/profile/avatar/finalize', {
|
|
8930
|
+
draftId: 'avatar-draft-1',
|
|
8931
|
+
relativePath: 'users/u1/profile/avatar/avatar-draft-1-headshot.png'
|
|
8932
|
+
}, { cookie });
|
|
8933
|
+
const res = createMockRes();
|
|
8934
|
+
await match?.handler(req, res, {});
|
|
8935
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8936
|
+
expect(payload.success).toBe(true);
|
|
8937
|
+
expect(payload.avatarUrl).toBe('/api/taskforce/context/users%2Fu1%2Fprofile%2Favatar%2Favatar-draft-1-headshot.png');
|
|
8938
|
+
expect(core.markUserAvatarUploadDraftFinalized).toHaveBeenCalledWith('u1', 'avatar-draft-1');
|
|
8939
|
+
vi.unstubAllGlobals();
|
|
8940
|
+
});
|
|
8676
8941
|
it('GET /api/taskforce/admin/system-users should require system admin role', async () => {
|
|
8677
8942
|
const match = matchRoute('GET', '/api/taskforce/admin/system-users', routes);
|
|
8678
8943
|
const req = createMockReq('GET', '/api/taskforce/admin/system-users', undefined, { 'x-taskforce-system-role': 'user' });
|
|
@@ -8701,6 +8966,54 @@ describe('Server Routes', () => {
|
|
|
8701
8966
|
expect(payload.planId).toBe('collab');
|
|
8702
8967
|
expect(payload.features['collaboration.invites'].allowed).toBe(true);
|
|
8703
8968
|
});
|
|
8969
|
+
it('GET /api/taskforce/account/team-management-access should return unified role and team plan access', async () => {
|
|
8970
|
+
core.resolveUserAccess.mockReturnValueOnce({
|
|
8971
|
+
userId: 'u1',
|
|
8972
|
+
workspaceId: 'default',
|
|
8973
|
+
role: 'owner',
|
|
8974
|
+
disabled: false,
|
|
8975
|
+
betaAccess: true
|
|
8976
|
+
});
|
|
8977
|
+
const match = matchRoute('GET', '/api/taskforce/account/team-management-access', routes);
|
|
8978
|
+
const req = createMockReq('GET', '/api/taskforce/account/team-management-access', undefined, {
|
|
8979
|
+
'x-taskforce-user-id': 'u1',
|
|
8980
|
+
'x-taskforce-workspace-id': 'workspace-1',
|
|
8981
|
+
'x-taskforce-role': 'owner'
|
|
8982
|
+
});
|
|
8983
|
+
const res = createMockRes();
|
|
8984
|
+
await match?.handler(req, res, {});
|
|
8985
|
+
expect(core.resolveUserAccess).toHaveBeenCalledWith('u1', 'default', 'member');
|
|
8986
|
+
expect(core.getAccountEntitlementsPayload).toHaveBeenCalledWith({ userId: 'u1', workspaceId: 'default' });
|
|
8987
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
8988
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
8989
|
+
expect(payload).toMatchObject({
|
|
8990
|
+
workspaceId: 'default',
|
|
8991
|
+
role: 'owner',
|
|
8992
|
+
canManageWorkspace: true,
|
|
8993
|
+
teamPlanMode: 'team',
|
|
8994
|
+
allowed: true
|
|
8995
|
+
});
|
|
8996
|
+
});
|
|
8997
|
+
it('GET /api/taskforce/account/team-management-access should fall back to the hinted workspace role when local access cannot resolve the cloud user id', async () => {
|
|
8998
|
+
core.resolveUserAccess.mockReturnValueOnce(null);
|
|
8999
|
+
const match = matchRoute('GET', '/api/taskforce/account/team-management-access', routes);
|
|
9000
|
+
const req = createMockReq('GET', '/api/taskforce/account/team-management-access?workspaceId=workspace-1', undefined, {
|
|
9001
|
+
'x-taskforce-user-id': 'u-cloud',
|
|
9002
|
+
'x-taskforce-workspace-role': 'owner'
|
|
9003
|
+
});
|
|
9004
|
+
const res = createMockRes();
|
|
9005
|
+
await match?.handler(req, res, {});
|
|
9006
|
+
expect(core.resolveUserAccess).toHaveBeenCalledWith('u-cloud', 'workspace-1', 'member');
|
|
9007
|
+
expect(core.getAccountEntitlementsPayload).toHaveBeenCalledWith({ userId: 'u-cloud', workspaceId: 'workspace-1' });
|
|
9008
|
+
const payload = JSON.parse(res.end.mock.calls[0][0]);
|
|
9009
|
+
expect(payload).toMatchObject({
|
|
9010
|
+
workspaceId: 'workspace-1',
|
|
9011
|
+
role: 'owner',
|
|
9012
|
+
canManageWorkspace: true,
|
|
9013
|
+
teamPlanMode: 'team',
|
|
9014
|
+
allowed: true
|
|
9015
|
+
});
|
|
9016
|
+
});
|
|
8704
9017
|
it('GET /api/taskforce/account/access-status should return pending plan-selection gate', async () => {
|
|
8705
9018
|
core.getAccountAccessStatus.mockReturnValueOnce({
|
|
8706
9019
|
gate: 'plan_selection_required',
|
|
@@ -10894,7 +11207,8 @@ describe('Server Routes', () => {
|
|
|
10894
11207
|
expect(core.bulkCreateTasks).toHaveBeenCalledWith([newTask], {
|
|
10895
11208
|
atomic: true,
|
|
10896
11209
|
validateOnly: true,
|
|
10897
|
-
workspaceId: 'default'
|
|
11210
|
+
workspaceId: 'default',
|
|
11211
|
+
provisionalLocalReference: true
|
|
10898
11212
|
});
|
|
10899
11213
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
10900
11214
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify({
|
|
@@ -10949,7 +11263,8 @@ describe('Server Routes', () => {
|
|
|
10949
11263
|
expect(core.bulkCreateTasks).toHaveBeenCalledWith(inputs, {
|
|
10950
11264
|
atomic: true,
|
|
10951
11265
|
validateOnly: false,
|
|
10952
|
-
workspaceId: 'default'
|
|
11266
|
+
workspaceId: 'default',
|
|
11267
|
+
provisionalLocalReference: true
|
|
10953
11268
|
});
|
|
10954
11269
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
10955
11270
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify({
|
|
@@ -11023,7 +11338,8 @@ describe('Server Routes', () => {
|
|
|
11023
11338
|
expect(core.bulkCreateTasks).toHaveBeenCalledWith(inputs, {
|
|
11024
11339
|
atomic: true,
|
|
11025
11340
|
validateOnly: true,
|
|
11026
|
-
workspaceId: 'default'
|
|
11341
|
+
workspaceId: 'default',
|
|
11342
|
+
provisionalLocalReference: true
|
|
11027
11343
|
});
|
|
11028
11344
|
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
11029
11345
|
expect(res.end).toHaveBeenCalledWith(JSON.stringify({
|
|
@@ -11044,6 +11360,36 @@ describe('Server Routes', () => {
|
|
|
11044
11360
|
errors: []
|
|
11045
11361
|
}));
|
|
11046
11362
|
});
|
|
11363
|
+
it('PATCH /api/taskforce/task/:id should pass autosave updates through as non-audited route metadata', async () => {
|
|
11364
|
+
const updatedTask = { id: '123', description: 'Draft body' };
|
|
11365
|
+
core.updateTask.mockReturnValue(updatedTask);
|
|
11366
|
+
const url = '/api/taskforce/task/123';
|
|
11367
|
+
const match = matchRoute('PATCH', url, routes);
|
|
11368
|
+
const req = createMockReq('PATCH', url, { description: 'Draft body', saveSource: 'autosave' });
|
|
11369
|
+
const res = createMockRes();
|
|
11370
|
+
await match?.handler(req, res, { id: '123' });
|
|
11371
|
+
expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Draft body' }, 'default', {
|
|
11372
|
+
actorRef: 'user',
|
|
11373
|
+
skipTaskEvents: true
|
|
11374
|
+
});
|
|
11375
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
11376
|
+
expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
|
|
11377
|
+
});
|
|
11378
|
+
it('PATCH /api/taskforce/task/:id should keep manual updates on the audited path', async () => {
|
|
11379
|
+
const updatedTask = { id: '123', description: 'Manual body' };
|
|
11380
|
+
core.updateTask.mockReturnValue(updatedTask);
|
|
11381
|
+
const url = '/api/taskforce/task/123';
|
|
11382
|
+
const match = matchRoute('PATCH', url, routes);
|
|
11383
|
+
const req = createMockReq('PATCH', url, { description: 'Manual body', saveSource: 'manual' });
|
|
11384
|
+
const res = createMockRes();
|
|
11385
|
+
await match?.handler(req, res, { id: '123' });
|
|
11386
|
+
expect(core.updateTask).toHaveBeenCalledWith('123', { description: 'Manual body' }, 'default', {
|
|
11387
|
+
actorRef: 'user',
|
|
11388
|
+
skipTaskEvents: false
|
|
11389
|
+
});
|
|
11390
|
+
expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
|
|
11391
|
+
expect(res.end).toHaveBeenCalledWith(JSON.stringify(updatedTask));
|
|
11392
|
+
});
|
|
11047
11393
|
it('PATCH /api/taskforce/task/:id should return rule-error status/message', async () => {
|
|
11048
11394
|
core.updateTask.mockImplementation(() => {
|
|
11049
11395
|
const err = new Error('Task update blocked by rule check');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
|
|
2
|
+
import type { AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
3
|
+
export declare function normalizeAiProfileSyncPayload(raw: unknown, workspaceId: string, normalizeSurfaceType: (value: unknown, context: {
|
|
4
|
+
source: string;
|
|
5
|
+
profileId?: string;
|
|
6
|
+
workspaceId?: string;
|
|
7
|
+
}) => AiProfileSurfaceType | null, source: string): WorkspaceSyncAiProfileSnapshot | null;
|
|
8
|
+
export declare function normalizeDocumentReviewSessionSyncPayload(raw: unknown, sourceWatermark?: string): WorkspaceSyncDocumentReviewSessionSnapshot | null;
|
|
9
|
+
export declare function normalizeDocumentReviewCommentSyncPayload(raw: unknown, options?: {
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
sourceWatermark?: string;
|
|
12
|
+
}): WorkspaceSyncDocumentReviewCommentSnapshot | null;
|
|
13
|
+
export declare function normalizeAnnotatedAttachmentSessionSyncPayload(raw: unknown, expectedWorkspaceId?: string, sourceWatermark?: string): WorkspaceSyncAnnotatedAttachmentSessionSnapshot | null;
|
|
14
|
+
export declare function normalizeDeleteSyncPayload(raw: unknown, idKey: 'sessionId' | 'commentId'): {
|
|
15
|
+
id: string;
|
|
16
|
+
deletedAt: string;
|
|
17
|
+
} | null;
|