@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
|
@@ -148,6 +148,14 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
148
148
|
sessionId: String(session.id || '').trim()
|
|
149
149
|
}))
|
|
150
150
|
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
|
|
151
|
+
const taskEventEntries = collaborationSyncState.listWorkspaceSyncTaskEventsSnapshot(workspaceId)
|
|
152
|
+
.map((event) => ({
|
|
153
|
+
kind: 'task-event',
|
|
154
|
+
watermark: String(event.createdAt || '').trim(),
|
|
155
|
+
sortId: `task-event:${String(event.id || '').trim()}`,
|
|
156
|
+
event
|
|
157
|
+
}))
|
|
158
|
+
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.event.id.length > 0 && entry.event.taskId.length > 0);
|
|
151
159
|
const assetIndex = await contentSyncState.listWorkspaceSyncBinaryAssetIndex(workspaceId);
|
|
152
160
|
const assetEntries = assetIndex
|
|
153
161
|
.map((asset) => ({
|
|
@@ -190,6 +198,7 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
190
198
|
...documentReviewCommentDeleteEntries,
|
|
191
199
|
...annotatedAttachmentSessionEntries,
|
|
192
200
|
...annotatedAttachmentSessionDeleteEntries,
|
|
201
|
+
...taskEventEntries,
|
|
193
202
|
...assetEntries,
|
|
194
203
|
...assetDeleteEntries
|
|
195
204
|
];
|
|
@@ -238,16 +247,11 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
238
247
|
}
|
|
239
248
|
}));
|
|
240
249
|
const hydrationMs = Date.now() - hydrationStartedAtMs;
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
break;
|
|
247
|
-
effectivePage.push(entry);
|
|
248
|
-
}
|
|
249
|
-
const hasMore = afterCursor.length > effectivePage.length;
|
|
250
|
-
const last = effectivePage.length > 0 ? effectivePage[effectivePage.length - 1] : null;
|
|
250
|
+
const skippedHydrationEntries = page.filter((entry) => ((entry.kind === 'document' && !hydratedDocuments.get(entry.path))
|
|
251
|
+
|| (entry.kind === 'asset' && !hydratedAssets.get(entry.path)))).length;
|
|
252
|
+
const effectivePage = page;
|
|
253
|
+
const hasMore = afterCursor.length > page.length;
|
|
254
|
+
const last = page.length > 0 ? page[page.length - 1] : null;
|
|
251
255
|
const nextCursor = encodeWorkspaceSyncCursor(last
|
|
252
256
|
? { u: last.watermark, id: String(last.sortId || '') }
|
|
253
257
|
: cursor);
|
|
@@ -280,55 +284,57 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
280
284
|
? [{ op: 'annotated-attachment-session-upsert', session: entry.session, watermark: entry.watermark }]
|
|
281
285
|
: entry.kind === 'annotated-attachment-session-delete'
|
|
282
286
|
? [{ op: 'annotated-attachment-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
283
|
-
: entry.kind === '
|
|
284
|
-
?
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
: entry.kind === '
|
|
308
|
-
?
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
:
|
|
287
|
+
: entry.kind === 'task-event'
|
|
288
|
+
? [{ op: 'task-event-upsert', event: entry.event, watermark: entry.watermark }]
|
|
289
|
+
: entry.kind === 'document'
|
|
290
|
+
? (() => {
|
|
291
|
+
const payload = hydratedDocuments.get(entry.path);
|
|
292
|
+
if (!payload)
|
|
293
|
+
return [];
|
|
294
|
+
return [{
|
|
295
|
+
op: 'document-upsert',
|
|
296
|
+
path: entry.path,
|
|
297
|
+
updatedAt: entry.watermark,
|
|
298
|
+
content: payload.content,
|
|
299
|
+
assetId: payload.assetId,
|
|
300
|
+
documentId: payload.documentId,
|
|
301
|
+
referenceNumber: payload.referenceNumber,
|
|
302
|
+
version: payload.version,
|
|
303
|
+
taskId: payload.taskId,
|
|
304
|
+
logicalName: payload.logicalName,
|
|
305
|
+
caption: payload.caption,
|
|
306
|
+
originalFilename: payload.originalFilename,
|
|
307
|
+
linkRole: payload.linkRole === 'reference' ? 'reference' : 'attachment',
|
|
308
|
+
watermark: entry.watermark
|
|
309
|
+
}];
|
|
310
|
+
})()
|
|
311
|
+
: entry.kind === 'document-delete'
|
|
312
|
+
? [{ op: 'document-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
313
|
+
: entry.kind === 'asset'
|
|
314
|
+
? (() => {
|
|
315
|
+
const payload = hydratedAssets.get(entry.path);
|
|
316
|
+
if (!payload)
|
|
317
|
+
return [];
|
|
318
|
+
return [{
|
|
319
|
+
op: 'asset-upsert',
|
|
320
|
+
path: entry.path,
|
|
321
|
+
updatedAt: entry.watermark,
|
|
322
|
+
contentBase64: payload.contentBase64,
|
|
323
|
+
assetId: payload.assetId,
|
|
324
|
+
kind: payload.kind,
|
|
325
|
+
mimeType: payload.mimeType,
|
|
326
|
+
referenceNumber: payload.referenceNumber,
|
|
327
|
+
taskId: payload.taskId,
|
|
328
|
+
logicalName: payload.logicalName,
|
|
329
|
+
caption: payload.caption,
|
|
330
|
+
originalFilename: payload.originalFilename,
|
|
331
|
+
linkRole: payload.linkRole,
|
|
332
|
+
watermark: entry.watermark
|
|
333
|
+
}];
|
|
334
|
+
})()
|
|
335
|
+
: entry.kind === 'asset-delete'
|
|
336
|
+
? [{ op: 'asset-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
337
|
+
: [{ op: 'upsert', task: entry.task, archived: Boolean(entry.archived), watermark: entry.watermark }]));
|
|
332
338
|
const encodeMs = Date.now() - encodeStartedAtMs;
|
|
333
339
|
return {
|
|
334
340
|
workspaceMembers,
|
|
@@ -338,6 +344,7 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
338
344
|
diagnostics: {
|
|
339
345
|
totalEntries: all.length,
|
|
340
346
|
pageEntries: page.length,
|
|
347
|
+
skippedHydrationEntries,
|
|
341
348
|
documentIndexCount: documentIndex.length,
|
|
342
349
|
assetIndexCount: assetIndex.length,
|
|
343
350
|
indexMs,
|
|
@@ -29,6 +29,7 @@ function createCollaborationSyncStateMock() {
|
|
|
29
29
|
listWorkspaceSyncDocumentReviewSessionsSnapshot: () => [],
|
|
30
30
|
listWorkspaceSyncDocumentReviewCommentsSnapshot: () => [],
|
|
31
31
|
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: () => [],
|
|
32
|
+
listWorkspaceSyncTaskEventsSnapshot: () => [],
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
function encodeCursor(cursor) {
|
|
@@ -234,9 +235,22 @@ describe('workspacePullFeed', () => {
|
|
|
234
235
|
expect(loadedDocs).toEqual(['docs/a.md']);
|
|
235
236
|
expect(loadedAssets).toEqual(['workspaces/ws/assets/images/a.png']);
|
|
236
237
|
});
|
|
237
|
-
it('
|
|
238
|
+
it('skips hydration misses without blocking later feed progress', async () => {
|
|
238
239
|
const core = createCoreMock();
|
|
239
240
|
const content = createContentSyncStateMock();
|
|
241
|
+
core.listTasksForSync = () => [
|
|
242
|
+
{
|
|
243
|
+
archived: false,
|
|
244
|
+
task: {
|
|
245
|
+
id: 'task-after-miss',
|
|
246
|
+
title: 'Task after miss',
|
|
247
|
+
status: 'task',
|
|
248
|
+
category: 'general',
|
|
249
|
+
type: 'task',
|
|
250
|
+
createdAt: '2026-04-12T10:00:00.000Z',
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
];
|
|
240
254
|
content.listWorkspaceSyncDocumentIndex = async () => [
|
|
241
255
|
{
|
|
242
256
|
path: 'docs/a.md',
|
|
@@ -260,7 +274,7 @@ describe('workspacePullFeed', () => {
|
|
|
260
274
|
core,
|
|
261
275
|
workspaceId: 'workspace-4',
|
|
262
276
|
cursor: null,
|
|
263
|
-
pageSize:
|
|
277
|
+
pageSize: 3,
|
|
264
278
|
encodeWorkspaceSyncCursor: encodeCursor,
|
|
265
279
|
contentSyncState: content,
|
|
266
280
|
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
@@ -270,9 +284,63 @@ describe('workspacePullFeed', () => {
|
|
|
270
284
|
op: 'document-upsert',
|
|
271
285
|
path: 'docs/a.md',
|
|
272
286
|
content: 'content:docs/a.md'
|
|
287
|
+
}),
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
op: 'upsert',
|
|
290
|
+
task: expect.objectContaining({
|
|
291
|
+
id: 'task-after-miss'
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
]);
|
|
295
|
+
expect(result.nextCursor).toBe('2026-04-12T10:00:00.000Z|task-after-miss');
|
|
296
|
+
expect(result.hasMore).toBe(false);
|
|
297
|
+
expect(result.diagnostics.skippedHydrationEntries).toBe(1);
|
|
298
|
+
});
|
|
299
|
+
it('includes task-event upserts in the pull feed ordered by event watermark', async () => {
|
|
300
|
+
const core = createCoreMock();
|
|
301
|
+
const collaboration = createCollaborationSyncStateMock();
|
|
302
|
+
collaboration.listWorkspaceSyncTaskEventsSnapshot = () => [
|
|
303
|
+
{
|
|
304
|
+
id: 'task-event-2',
|
|
305
|
+
taskId: 'task-2',
|
|
306
|
+
workspaceId: 'workspace-5',
|
|
307
|
+
action: 'task-archived',
|
|
308
|
+
actor: 'ai-profile-2',
|
|
309
|
+
actorType: 'ai',
|
|
310
|
+
createdAt: '2026-04-12T10:06:00.000Z',
|
|
311
|
+
details: {}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: 'task-event-1',
|
|
315
|
+
taskId: 'task-1',
|
|
316
|
+
workspaceId: 'workspace-5',
|
|
317
|
+
action: 'task-status-changed',
|
|
318
|
+
actor: 'ai-profile-1',
|
|
319
|
+
actorType: 'ai',
|
|
320
|
+
createdAt: '2026-04-12T10:05:00.000Z',
|
|
321
|
+
details: {}
|
|
322
|
+
}
|
|
323
|
+
];
|
|
324
|
+
const result = await buildWorkspacePullFeed({
|
|
325
|
+
core,
|
|
326
|
+
workspaceId: 'workspace-5',
|
|
327
|
+
cursor: null,
|
|
328
|
+
pageSize: 5,
|
|
329
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
330
|
+
contentSyncState: createContentSyncStateMock(),
|
|
331
|
+
collaborationSyncState: collaboration,
|
|
332
|
+
});
|
|
333
|
+
expect(result.changes).toEqual([
|
|
334
|
+
expect.objectContaining({
|
|
335
|
+
op: 'task-event-upsert',
|
|
336
|
+
watermark: '2026-04-12T10:05:00.000Z',
|
|
337
|
+
event: expect.objectContaining({ id: 'task-event-1' })
|
|
338
|
+
}),
|
|
339
|
+
expect.objectContaining({
|
|
340
|
+
op: 'task-event-upsert',
|
|
341
|
+
watermark: '2026-04-12T10:06:00.000Z',
|
|
342
|
+
event: expect.objectContaining({ id: 'task-event-2' })
|
|
273
343
|
})
|
|
274
344
|
]);
|
|
275
|
-
expect(result.nextCursor).toBe('2026-04-10T10:00:00.000Z|doc:docs/a.md');
|
|
276
|
-
expect(result.hasMore).toBe(true);
|
|
277
345
|
});
|
|
278
346
|
});
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { InitiativeItem, TaskItem, WorkstreamItem } from '../types';
|
|
1
|
+
import type { InitiativeItem, TaskEvent, TaskItem, WorkstreamItem } from '../types';
|
|
2
2
|
import type { DocumentReviewCommentRecord, DocumentReviewSessionStatus } from '../documentReviews/types.js';
|
|
3
3
|
import type { AnnotatedAttachmentAnnotation } from '../annotatedAttachments/types.js';
|
|
4
4
|
import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
|
|
5
5
|
import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
6
6
|
import { type TaskSyncPayload } from './taskSyncPayload.js';
|
|
7
7
|
export type WorkspaceSyncTaskSnapshot = TaskSyncPayload;
|
|
8
|
+
export interface WorkspaceSyncTaskEventSnapshot {
|
|
9
|
+
id: string;
|
|
10
|
+
taskId: string;
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
action: string;
|
|
13
|
+
actor: string;
|
|
14
|
+
actorType: 'system' | 'human' | 'ai';
|
|
15
|
+
details?: TaskEvent['details'];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
8
18
|
export interface WorkspaceSyncAiProfileSnapshot {
|
|
9
19
|
id: string;
|
|
10
20
|
workspaceId: string;
|
|
@@ -13,6 +23,10 @@ export interface WorkspaceSyncAiProfileSnapshot {
|
|
|
13
23
|
username: string;
|
|
14
24
|
icon: string;
|
|
15
25
|
color: string;
|
|
26
|
+
description?: string | null;
|
|
27
|
+
role?: string | null;
|
|
28
|
+
provider?: string | null;
|
|
29
|
+
model?: string | null;
|
|
16
30
|
surfaceType?: AiProfileSurfaceType | null;
|
|
17
31
|
providerMetadata?: Record<string, unknown> | null;
|
|
18
32
|
createdAt: string;
|
|
@@ -177,6 +191,9 @@ export type WorkspaceSyncChange = {
|
|
|
177
191
|
op: 'annotated-attachment-session-delete';
|
|
178
192
|
sessionId: string;
|
|
179
193
|
deletedAt?: string;
|
|
194
|
+
} | {
|
|
195
|
+
op: 'task-event-upsert';
|
|
196
|
+
event: WorkspaceSyncTaskEventSnapshot;
|
|
180
197
|
};
|
|
181
198
|
export interface WorkspaceSyncPayload {
|
|
182
199
|
changes: WorkspaceSyncChange[];
|
|
@@ -203,6 +220,7 @@ export interface WorkspaceSyncPayload {
|
|
|
203
220
|
currentAnnotatedAttachmentSessionIds: Set<string>;
|
|
204
221
|
deleteAnnotatedAttachmentSessionIds: Set<string>;
|
|
205
222
|
currentAnnotatedAttachmentSessionWatermarks: Map<string, string>;
|
|
223
|
+
currentTaskEventWatermarks: Map<string, string>;
|
|
206
224
|
/** Watermarks for every task upsert included in this payload (id → updatedAt|createdAt). */
|
|
207
225
|
pushedWatermarks: Map<string, string>;
|
|
208
226
|
}
|
|
@@ -250,6 +268,7 @@ export declare function buildWorkspaceSyncPayloadModel(input: {
|
|
|
250
268
|
documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
251
269
|
documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
252
270
|
annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
271
|
+
taskEvents?: WorkspaceSyncTaskEventSnapshot[];
|
|
253
272
|
lastPushedInitiativeIds?: Set<string>;
|
|
254
273
|
lastPushedInitiativeWatermarks?: Map<string, string>;
|
|
255
274
|
lastPushedWorkstreamIds?: Set<string>;
|
|
@@ -266,6 +285,7 @@ export declare function buildWorkspaceSyncPayloadModel(input: {
|
|
|
266
285
|
lastPushedDocumentReviewCommentWatermarks?: Map<string, string>;
|
|
267
286
|
lastPushedAnnotatedAttachmentSessionIds?: Set<string>;
|
|
268
287
|
lastPushedAnnotatedAttachmentSessionWatermarks?: Map<string, string>;
|
|
288
|
+
lastPushedTaskEventWatermarks?: Map<string, string>;
|
|
269
289
|
/**
|
|
270
290
|
* Watermark (updatedAt|createdAt) of each task as of its last successful push.
|
|
271
291
|
* When provided, only tasks whose watermark differs from this map are included
|
|
@@ -289,4 +309,5 @@ export declare function buildWorkspaceSyncSignatureModel(input: {
|
|
|
289
309
|
documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
290
310
|
documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
291
311
|
annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
312
|
+
taskEvents?: WorkspaceSyncTaskEventSnapshot[];
|
|
292
313
|
}): string;
|
|
@@ -3,6 +3,16 @@ import { serializeTaskForSync } from './taskSyncPayload.js';
|
|
|
3
3
|
export function toWorkspaceSyncTaskSnapshot(task) {
|
|
4
4
|
return serializeTaskForSync(task);
|
|
5
5
|
}
|
|
6
|
+
function sortTaskEventsForSync(taskEvents) {
|
|
7
|
+
return [...taskEvents].sort((left, right) => {
|
|
8
|
+
const leftCreatedAt = String(left?.createdAt || '').trim();
|
|
9
|
+
const rightCreatedAt = String(right?.createdAt || '').trim();
|
|
10
|
+
const watermarkDelta = leftCreatedAt.localeCompare(rightCreatedAt);
|
|
11
|
+
if (watermarkDelta !== 0)
|
|
12
|
+
return watermarkDelta;
|
|
13
|
+
return String(left?.id || '').trim().localeCompare(String(right?.id || '').trim());
|
|
14
|
+
});
|
|
15
|
+
}
|
|
6
16
|
function sortTasksForSync(tasks) {
|
|
7
17
|
return [...tasks].sort((left, right) => {
|
|
8
18
|
const leftId = String(left?.id || '').trim();
|
|
@@ -175,6 +185,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
175
185
|
username: String(profile.username || '').trim(),
|
|
176
186
|
icon: String(profile.icon || 'Bot').trim() || 'Bot',
|
|
177
187
|
color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
188
|
+
description: typeof profile.description === 'string' ? profile.description.trim() || null : null,
|
|
189
|
+
role: typeof profile.role === 'string' ? profile.role.trim() || null : null,
|
|
190
|
+
provider: typeof profile.provider === 'string' ? profile.provider.trim() || null : null,
|
|
191
|
+
model: typeof profile.model === 'string' ? profile.model.trim() || null : null,
|
|
178
192
|
surfaceType: parseAiProfileSurfaceType(profile.surfaceType),
|
|
179
193
|
providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
|
|
180
194
|
? profile.providerMetadata
|
|
@@ -391,6 +405,40 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
391
405
|
return true;
|
|
392
406
|
return previousAnnotatedAttachmentSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
|
|
393
407
|
});
|
|
408
|
+
const taskEvents = sortTaskEventsForSync(Array.isArray(input.taskEvents) ? input.taskEvents : []);
|
|
409
|
+
const currentTaskEventWatermarks = new Map(taskEvents
|
|
410
|
+
.map((event) => [String(event?.id || '').trim(), String(event?.createdAt || '').trim()])
|
|
411
|
+
.filter(([id]) => id.length > 0));
|
|
412
|
+
const previousTaskEventWatermarks = input.lastPushedTaskEventWatermarks;
|
|
413
|
+
const shouldSendAllTaskEvents = !previousTaskEventWatermarks || previousTaskEventWatermarks.size === 0;
|
|
414
|
+
const taskEventUpsertChanges = taskEvents
|
|
415
|
+
.map((event) => ({
|
|
416
|
+
op: 'task-event-upsert',
|
|
417
|
+
event: {
|
|
418
|
+
id: String(event.id || '').trim(),
|
|
419
|
+
taskId: String(event.taskId || '').trim(),
|
|
420
|
+
workspaceId: String(event.workspaceId || '').trim(),
|
|
421
|
+
action: String(event.action || '').trim(),
|
|
422
|
+
actor: String(event.actor || 'system').trim() || 'system',
|
|
423
|
+
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
424
|
+
? event.actorType
|
|
425
|
+
: 'system',
|
|
426
|
+
details: event.details && typeof event.details === 'object' && !Array.isArray(event.details)
|
|
427
|
+
? event.details
|
|
428
|
+
: {},
|
|
429
|
+
createdAt: String(event.createdAt || '').trim(),
|
|
430
|
+
}
|
|
431
|
+
}))
|
|
432
|
+
.filter((change) => (change.event.id.length > 0
|
|
433
|
+
&& change.event.taskId.length > 0
|
|
434
|
+
&& change.event.workspaceId.length > 0
|
|
435
|
+
&& change.event.action.length > 0
|
|
436
|
+
&& Number.isFinite(Date.parse(change.event.createdAt))))
|
|
437
|
+
.filter((change) => {
|
|
438
|
+
if (shouldSendAllTaskEvents)
|
|
439
|
+
return true;
|
|
440
|
+
return previousTaskEventWatermarks.get(change.event.id) !== change.event.createdAt;
|
|
441
|
+
});
|
|
394
442
|
const changes = [
|
|
395
443
|
...initiativeUpsertChanges,
|
|
396
444
|
...workstreamUpsertChanges,
|
|
@@ -438,6 +486,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
438
486
|
sessionId,
|
|
439
487
|
deletedAt: payloadBuiltAt
|
|
440
488
|
})),
|
|
489
|
+
...taskEventUpsertChanges,
|
|
441
490
|
...activeChanges,
|
|
442
491
|
...archivedChanges,
|
|
443
492
|
...Array.from(deleteTaskIds).map((taskId) => ({ op: 'delete', taskId }))
|
|
@@ -467,6 +516,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
467
516
|
currentAnnotatedAttachmentSessionIds,
|
|
468
517
|
deleteAnnotatedAttachmentSessionIds,
|
|
469
518
|
currentAnnotatedAttachmentSessionWatermarks,
|
|
519
|
+
currentTaskEventWatermarks,
|
|
470
520
|
pushedWatermarks
|
|
471
521
|
};
|
|
472
522
|
}
|
|
@@ -516,6 +566,21 @@ export function buildWorkspaceSyncSignatureModel(input) {
|
|
|
516
566
|
.filter((profile) => profile.id.length > 0)
|
|
517
567
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
518
568
|
: [],
|
|
569
|
+
taskEvents: Array.isArray(input.taskEvents)
|
|
570
|
+
? input.taskEvents
|
|
571
|
+
.map((event) => ({
|
|
572
|
+
id: String(event.id || '').trim(),
|
|
573
|
+
taskId: String(event.taskId || '').trim(),
|
|
574
|
+
action: String(event.action || '').trim(),
|
|
575
|
+
actor: String(event.actor || '').trim(),
|
|
576
|
+
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
577
|
+
? event.actorType
|
|
578
|
+
: 'system',
|
|
579
|
+
createdAt: String(event.createdAt || '').trim()
|
|
580
|
+
}))
|
|
581
|
+
.filter((event) => event.id.length > 0 && event.taskId.length > 0)
|
|
582
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
583
|
+
: [],
|
|
519
584
|
documents: Array.isArray(input.documents)
|
|
520
585
|
? input.documents
|
|
521
586
|
.map((doc) => ({
|
|
@@ -20,6 +20,82 @@ describe('workspaceSyncModel', () => {
|
|
|
20
20
|
expect.objectContaining({ op: 'document-delete', path: 'docs/remove.md' })
|
|
21
21
|
]));
|
|
22
22
|
});
|
|
23
|
+
it('emits task-event-upsert changes for new activity events and tracks event watermarks', () => {
|
|
24
|
+
const payload = buildWorkspaceSyncPayloadModel({
|
|
25
|
+
tasks: [],
|
|
26
|
+
archivedTasks: [],
|
|
27
|
+
taskEvents: [
|
|
28
|
+
{
|
|
29
|
+
id: 'task-event-1',
|
|
30
|
+
taskId: 'task-1',
|
|
31
|
+
workspaceId: 'workspace-a',
|
|
32
|
+
action: 'task-status-changed',
|
|
33
|
+
actor: 'ai-profile-1',
|
|
34
|
+
actorType: 'ai',
|
|
35
|
+
createdAt: '2026-04-13T10:05:00.000Z',
|
|
36
|
+
details: {
|
|
37
|
+
changes: {
|
|
38
|
+
status: {
|
|
39
|
+
from: 'in-progress',
|
|
40
|
+
to: 'done'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
lastPushedTaskIds: new Set(),
|
|
47
|
+
pendingDeletedTaskIds: new Set(),
|
|
48
|
+
lastPushedTaskEventWatermarks: new Map()
|
|
49
|
+
});
|
|
50
|
+
expect(payload.changes).toEqual(expect.arrayContaining([
|
|
51
|
+
expect.objectContaining({
|
|
52
|
+
op: 'task-event-upsert',
|
|
53
|
+
event: expect.objectContaining({
|
|
54
|
+
id: 'task-event-1',
|
|
55
|
+
taskId: 'task-1',
|
|
56
|
+
workspaceId: 'workspace-a',
|
|
57
|
+
action: 'task-status-changed',
|
|
58
|
+
createdAt: '2026-04-13T10:05:00.000Z'
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
]));
|
|
62
|
+
expect(payload.currentTaskEventWatermarks.get('task-event-1')).toBe('2026-04-13T10:05:00.000Z');
|
|
63
|
+
});
|
|
64
|
+
it('does not resend unchanged task events when prior task-event watermarks exist', () => {
|
|
65
|
+
const payload = buildWorkspaceSyncPayloadModel({
|
|
66
|
+
tasks: [],
|
|
67
|
+
archivedTasks: [],
|
|
68
|
+
taskEvents: [
|
|
69
|
+
{
|
|
70
|
+
id: 'task-event-keep',
|
|
71
|
+
taskId: 'task-1',
|
|
72
|
+
workspaceId: 'workspace-a',
|
|
73
|
+
action: 'task-status-changed',
|
|
74
|
+
actor: 'ai-profile-1',
|
|
75
|
+
actorType: 'ai',
|
|
76
|
+
createdAt: '2026-04-13T10:05:00.000Z'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'task-event-new',
|
|
80
|
+
taskId: 'task-2',
|
|
81
|
+
workspaceId: 'workspace-a',
|
|
82
|
+
action: 'task-archived',
|
|
83
|
+
actor: 'ai-profile-1',
|
|
84
|
+
actorType: 'ai',
|
|
85
|
+
createdAt: '2026-04-13T10:06:00.000Z'
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
lastPushedTaskIds: new Set(),
|
|
89
|
+
pendingDeletedTaskIds: new Set(),
|
|
90
|
+
lastPushedTaskEventWatermarks: new Map([
|
|
91
|
+
['task-event-keep', '2026-04-13T10:05:00.000Z']
|
|
92
|
+
])
|
|
93
|
+
});
|
|
94
|
+
const taskEventIds = payload.changes
|
|
95
|
+
.filter((change) => change.op === 'task-event-upsert')
|
|
96
|
+
.map((change) => change.event.id);
|
|
97
|
+
expect(taskEventIds).toEqual(['task-event-new']);
|
|
98
|
+
});
|
|
23
99
|
it('sends only changed/new document upserts when prior document watermarks exist', () => {
|
|
24
100
|
const payload = buildWorkspaceSyncPayloadModel({
|
|
25
101
|
tasks: [],
|
|
@@ -162,6 +238,10 @@ describe('workspaceSyncModel', () => {
|
|
|
162
238
|
username: 'codex',
|
|
163
239
|
icon: 'Bot',
|
|
164
240
|
color: '#0ea5e9',
|
|
241
|
+
description: 'Terminal-first coding agent',
|
|
242
|
+
role: 'Implementer',
|
|
243
|
+
provider: 'OpenAI',
|
|
244
|
+
model: 'gpt-5.4',
|
|
165
245
|
surfaceType: 'coding_tool',
|
|
166
246
|
createdAt: '2026-03-22T10:00:00.000Z',
|
|
167
247
|
updatedAt: '2026-03-22T10:05:00.000Z',
|
|
@@ -175,6 +255,10 @@ describe('workspaceSyncModel', () => {
|
|
|
175
255
|
profile: expect.objectContaining({
|
|
176
256
|
id: 'ai-profile-1',
|
|
177
257
|
name: 'Codex',
|
|
258
|
+
description: 'Terminal-first coding agent',
|
|
259
|
+
role: 'Implementer',
|
|
260
|
+
provider: 'OpenAI',
|
|
261
|
+
model: 'gpt-5.4',
|
|
178
262
|
surfaceType: 'coding_tool'
|
|
179
263
|
})
|
|
180
264
|
})
|
|
@@ -197,6 +281,10 @@ describe('workspaceSyncModel', () => {
|
|
|
197
281
|
username: 'codex',
|
|
198
282
|
icon: 'Bot',
|
|
199
283
|
color: '#0ea5e9',
|
|
284
|
+
description: 'Terminal-first coding agent',
|
|
285
|
+
role: 'Implementer',
|
|
286
|
+
provider: 'OpenAI',
|
|
287
|
+
model: 'gpt-5.4',
|
|
200
288
|
surfaceType: 'coding_tool',
|
|
201
289
|
createdAt: '2026-03-22T10:00:00.000Z',
|
|
202
290
|
updatedAt: '2026-03-22T10:05:00.000Z',
|
|
@@ -681,6 +769,37 @@ describe('workspaceSyncModel', () => {
|
|
|
681
769
|
});
|
|
682
770
|
expect(changedSignature).not.toEqual(baseSignature);
|
|
683
771
|
});
|
|
772
|
+
it('changes when task activity changes', () => {
|
|
773
|
+
const baseSignature = buildWorkspaceSyncSignatureModel({
|
|
774
|
+
workspaceId: 'ws-1',
|
|
775
|
+
tasks: [],
|
|
776
|
+
archivedTasks: [],
|
|
777
|
+
taskEvents: [{
|
|
778
|
+
id: 'task-event-1',
|
|
779
|
+
taskId: 'task-1',
|
|
780
|
+
workspaceId: 'ws-1',
|
|
781
|
+
action: 'task-status-changed',
|
|
782
|
+
actor: 'ai-profile-1',
|
|
783
|
+
actorType: 'ai',
|
|
784
|
+
createdAt: '2026-04-13T10:05:00.000Z'
|
|
785
|
+
}]
|
|
786
|
+
});
|
|
787
|
+
const changedSignature = buildWorkspaceSyncSignatureModel({
|
|
788
|
+
workspaceId: 'ws-1',
|
|
789
|
+
tasks: [],
|
|
790
|
+
archivedTasks: [],
|
|
791
|
+
taskEvents: [{
|
|
792
|
+
id: 'task-event-1',
|
|
793
|
+
taskId: 'task-1',
|
|
794
|
+
workspaceId: 'ws-1',
|
|
795
|
+
action: 'task-status-changed',
|
|
796
|
+
actor: 'ai-profile-1',
|
|
797
|
+
actorType: 'ai',
|
|
798
|
+
createdAt: '2026-04-13T10:06:00.000Z'
|
|
799
|
+
}]
|
|
800
|
+
});
|
|
801
|
+
expect(changedSignature).not.toEqual(baseSignature);
|
|
802
|
+
});
|
|
684
803
|
});
|
|
685
804
|
describe('dirty task watermarks', () => {
|
|
686
805
|
it('emits an upsert when a status-only server-confirmed update changes updatedAt', () => {
|
package/dist/types.d.ts
CHANGED
|
@@ -4,23 +4,15 @@
|
|
|
4
4
|
* A decoupled, reusable collaborative task manager.
|
|
5
5
|
*/
|
|
6
6
|
export type { TaskforceTheme } from './utils/theme';
|
|
7
|
+
export type { TaskActor } from './shared/taskActor.js';
|
|
7
8
|
import type { TaskforceTheme } from './utils/theme';
|
|
9
|
+
import type { TaskActor } from './shared/taskActor.js';
|
|
8
10
|
export interface Environment {
|
|
9
11
|
id: string;
|
|
10
12
|
name: string;
|
|
11
13
|
icon?: string;
|
|
12
14
|
directory: string;
|
|
13
15
|
}
|
|
14
|
-
export interface TaskActor {
|
|
15
|
-
id: string;
|
|
16
|
-
kind: 'human' | 'ai';
|
|
17
|
-
label: string;
|
|
18
|
-
username: string;
|
|
19
|
-
icon: string;
|
|
20
|
-
color: string;
|
|
21
|
-
isNamed: boolean;
|
|
22
|
-
meta?: Record<string, unknown>;
|
|
23
|
-
}
|
|
24
16
|
export interface Comment {
|
|
25
17
|
id: string;
|
|
26
18
|
author: 'user' | 'ai' | string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as n,j as e}from"./vendor-react-CKJs5o3c.js";import{p as T,u as C,v as w,w as S,t as s,x as M}from"./index-DXUdtH1B.js";import{h as L,B as A,k as I,aa as B,aI as H,aJ as D,b as R}from"./vendor-icons-I7ZwG1z_.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function F({workspaceId:h}){const[x,k]=n.useState([]),[j,y]=n.useState(!1),[c,P]=n.useState(null),[p,m]=n.useState(null),[d,g]=n.useState(null),N=n.useCallback(async()=>{if(h){y(!0);try{const a=await(await fetch(`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(h)}`,{credentials:"include"})).json().catch(()=>({})),t=Array.isArray(a?.assignees)?a.assignees.filter(i=>i.kind==="agent").map(i=>({id:String(i.value||""),name:String(i.label||i.value||"Unknown Agent"),username:String(i.username||i.value||""),icon:String(i.icon||"Bot"),color:String(i.color||"#6B7280"),kind:String(i.kind||"agent"),description:typeof i.description=="string"?i.description:null,role:typeof i.role=="string"?i.role:null,provider:typeof i.provider=="string"?i.provider:null,model:typeof i.model=="string"?i.model:null,surfaceType:T(i.surfaceType),createdAt:String(i.createdAt||""),updatedAt:String(i.updatedAt||i.createdAt||""),lastActiveAt:typeof i.lastActiveAt=="string"?i.lastActiveAt:null})):[];k(t)}finally{y(!1)}}},[h]);n.useEffect(()=>{N()},[N]);const f=n.useMemo(()=>{const r=new Map;for(const a of x){const t=C(a.surfaceType);r.has(t)||r.set(t,new Map);const i=r.get(t),v=a.name.toLowerCase();i.has(v)||i.set(v,[]),i.get(v).push(a)}return w.map(a=>({section:a,label:S(a),groups:Array.from(r.get(a)?.entries()||[]).map(([t,i])=>({groupId:`${a}:${t}`,section:a,sectionLabel:S(a),profiles:i,primaryProfile:i[0]}))})).filter(a=>a.groups.length>0)},[x]),o=n.useMemo(()=>f.flatMap(r=>r.groups),[f]),l=n.useMemo(()=>o.find(r=>r.groupId===d)||o[0]||null,[o,d]);n.useEffect(()=>{if(!o.length){d!==null&&g(null);return}(!d||!o.some(r=>r.groupId===d))&&g(o[0].groupId)},[o,d]);const b=async(r,a)=>{m(null);try{const t=await fetch("/api/taskforce/workspace/ai-profiles/merge",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepId:r,mergeId:a})}),i=await t.json().catch(()=>({}));if(!t.ok){m({type:"error",message:String(i?.error||"Failed to merge AI profiles.")});return}P(null),m({type:"success",message:"AI profiles merged."}),await N()}catch{m({type:"error",message:"Failed to merge AI profiles."})}},u=r=>{const a=String(r||"").trim();if(!a)return"Unknown";const t=Date.parse(a);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):a};return e.jsx("div",{className:`${s.standalonePage} ${s.standaloneContent}`,style:{overflow:"auto"},children:e.jsx("div",{className:s.settingsContent,style:{maxWidth:1080,margin:"0 auto",width:"100%"},children:e.jsx("div",{className:s.settingGroup,children:e.jsxs("div",{children:[e.jsx("h4",{className:s.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${s.settingsHint} ${s.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost."}),j&&e.jsxs("div",{className:s.settingsHint,children:[e.jsx(L,{size:13,className:s.spinner})," Loading profiles…"]}),!j&&x.length===0&&e.jsx("div",{className:s.settingsHint,children:"No AI profiles registered yet."}),!j&&f.length>0&&e.jsxs("div",{className:s.aiProfilesExplorer,children:[e.jsx("div",{className:s.aiProfilesListPane,children:e.jsx("div",{className:s.aiProfilesList,children:f.map(r=>e.jsxs("div",{className:s.aiProfilesSection,children:[e.jsx("div",{className:s.aiProfilesSectionHeader,children:r.label}),r.groups.map(a=>{const t=l?.groupId===a.groupId,i=a.primaryProfile;return e.jsxs("button",{type:"button",className:`${s.aiProfileGroup} ${a.profiles.length>1?s.aiProfileGroupDuplicate:""} ${t?s.aiProfileGroupSelected:""}`,onClick:()=>g(a.groupId),"aria-pressed":t,children:[e.jsxs("div",{className:s.aiProfileGroupHeader,children:[e.jsx(A,{size:13,style:{color:i.color}}),e.jsx("span",{className:s.aiProfileName,children:i.name}),a.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDuplicateBadge,children:[e.jsx(I,{size:11})," ",a.profiles.length," duplicates"]})]}),e.jsxs("div",{className:s.settingsHint,style:{marginTop:6},children:["@",i.username]}),(i.surfaceType||i.role||i.provider||i.model)&&e.jsxs("div",{className:s.aiProfileMetaRow,children:[i.surfaceType&&e.jsx("span",{className:s.aiProfileSurfaceBadge,children:M(i.surfaceType)}),i.role&&e.jsx("span",{children:i.role}),i.provider&&e.jsx("span",{children:i.provider}),i.model&&e.jsx("span",{children:i.model})]}),i.description&&e.jsx("div",{className:s.settingsHint,style:{marginTop:4},children:i.description})]},a.groupId)})]},r.section))})}),l&&e.jsx("div",{className:s.aiProfileDetailPane,children:e.jsxs("div",{className:s.aiProfileDetailCard,children:[e.jsxs("div",{className:s.aiProfileDetailHero,children:[e.jsx("div",{className:s.aiProfileDetailIcon,style:{color:l.primaryProfile.color},children:e.jsx(A,{size:18})}),e.jsxs("div",{className:s.aiProfileDetailHeading,children:[e.jsx("div",{className:s.aiProfileDetailTitleRow,children:e.jsx("h5",{className:s.aiProfileDetailTitle,children:l.primaryProfile.name})}),e.jsxs("div",{className:s.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:s.aiProfileDetailHandle,children:["@",l.primaryProfile.username]}),e.jsx("span",{className:s.aiProfileDetailSectionBadge,children:l.sectionLabel}),l.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDetailLinkedCount,children:[l.profiles.length," linked"]})]})]})]}),l.primaryProfile.description&&e.jsx("div",{className:s.aiProfileDetailDescription,children:l.primaryProfile.description}),e.jsxs("div",{className:s.aiProfileDetailGrid,children:[e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(B,{size:12})," Role"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:l.primaryProfile.role||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(H,{size:12})," Provider"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:[l.primaryProfile.provider,l.primaryProfile.model].filter(Boolean).join(" · ")||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Last active"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.lastActiveAt)})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Updated"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.updatedAt)})]})]}),e.jsxs("div",{className:s.aiProfileInstanceSection,children:[e.jsxs("div",{className:s.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:s.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:s.aiProfileInstanceList,children:l.profiles.map(r=>e.jsxs("div",{className:s.aiProfileInstanceCard,children:[e.jsxs("div",{className:s.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:s.aiProfileInstanceName,children:["@",r.username]}),e.jsx("div",{className:s.aiProfileIdChip,children:r.id})]}),l.profiles.length>1&&c?.keepId===r.id&&e.jsx("span",{className:s.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:s.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",u(r.createdAt)]}),e.jsxs("span",{children:["Updated ",u(r.updatedAt)]})]}),l.profiles.length>1&&c?.keepId!==r.id&&e.jsx("button",{className:s.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const a=l.profiles.find(t=>t.id!==r.id)?.id;a&&P({keepId:r.id,mergeId:a})},children:"Keep this"})]},r.id))}),c&&l.profiles.some(r=>r.id===c.keepId)&&e.jsxs("div",{className:s.aiProfileMergeActions,children:[e.jsx("button",{className:s.dangerBtn,onClick:()=>{b(c.keepId,c.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:s.secondaryHeaderBtn,onClick:()=>P(null),children:"Cancel"})]})]})]})})]}),p&&e.jsxs("div",{className:`${p.type==="success"?s.successMessage:s.errorMessage} ${s.marginTop12}`,children:[p.type==="success"?e.jsx(R,{size:14}):e.jsx(I,{size:14}),p.message]})]})})})})}export{F as AgentsModule};
|