@taskforcehq/taskforce 0.3.306 → 0.3.307

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.
Files changed (215) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.js +9 -1
  38. package/dist/components/views/PlanComparisonPage.test.js +19 -0
  39. package/dist/components/views/PlansPage.js +1 -1
  40. package/dist/components/views/StandaloneLayout.js +256 -69
  41. package/dist/components/views/WidgetView.js +10 -8
  42. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  43. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  44. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  45. package/dist/config/envSchema.js +1 -0
  46. package/dist/core/AiProfileService.d.ts +7 -0
  47. package/dist/core/AiProfileService.js +460 -35
  48. package/dist/core/AiProfiles.test.js +648 -2
  49. package/dist/core/AttachmentLinkService.d.ts +3 -0
  50. package/dist/core/AttachmentLinkService.js +58 -13
  51. package/dist/core/GlobalSettingsService.js +12 -0
  52. package/dist/core/McpTokenService.d.ts +12 -0
  53. package/dist/core/McpTokenService.js +44 -1
  54. package/dist/core/PlanningEntities.test.js +366 -4
  55. package/dist/core/TaskActivityService.d.ts +28 -1
  56. package/dist/core/TaskActivityService.js +227 -46
  57. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  58. package/dist/core/TaskActivitySync.test.js +90 -0
  59. package/dist/core/TaskAuditTimeline.test.js +96 -0
  60. package/dist/core/Taskforce.d.ts +190 -26
  61. package/dist/core/Taskforce.ids.test.js +58 -0
  62. package/dist/core/Taskforce.js +775 -71
  63. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  64. package/dist/core/types.d.ts +7 -0
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  66. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  67. package/dist/hooks/bootstrapTimeouts.js +8 -0
  68. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  69. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  70. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  71. package/dist/hooks/sync/orchestratorShared.js +61 -1
  72. package/dist/hooks/sync/recovery.js +8 -0
  73. package/dist/hooks/sync/recovery.test.js +5 -5
  74. package/dist/hooks/sync/transfers.d.ts +2 -1
  75. package/dist/hooks/sync/transfers.js +36 -9
  76. package/dist/hooks/sync/transfers.test.js +534 -2
  77. package/dist/hooks/useFilterSort.js +13 -13
  78. package/dist/hooks/useFilterSort.test.js +87 -1
  79. package/dist/hooks/useSyncOrchestrator.js +50 -5
  80. package/dist/hooks/useTaskforce.d.ts +85 -50
  81. package/dist/hooks/useTaskforce.js +44 -14
  82. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  83. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  84. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  85. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  86. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  87. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  88. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  89. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  90. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  91. package/dist/mcp/clientIntegrations.d.ts +1 -0
  92. package/dist/mcp/clientIntegrations.js +25 -0
  93. package/dist/mcp/clientIntegrations.test.js +14 -3
  94. package/dist/mcp/clientProfileDefaults.js +3 -3
  95. package/dist/mcp/collaborationRegistrar.js +7 -7
  96. package/dist/mcp/documentAssetRegistrar.js +113 -7
  97. package/dist/mcp/runtime.d.ts +1 -0
  98. package/dist/mcp/runtime.js +969 -122
  99. package/dist/mcp/runtime.test.js +1083 -11
  100. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  101. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  102. package/dist/mcp/toolRegistry.d.ts +56 -0
  103. package/dist/mcp/toolRegistry.js +8 -0
  104. package/dist/mcp/toolRegistry.test.js +4 -0
  105. package/dist/migrations/taskSchemaMigrations.js +21 -0
  106. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  107. package/dist/server/aiProfileAvatarAssets.js +319 -0
  108. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  109. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  110. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/index.test.js +6 -1
  113. package/dist/server/routes/admin.js +187 -9
  114. package/dist/server/routes/annotatedAttachments.js +51 -0
  115. package/dist/server/routes/auth.d.ts +11 -0
  116. package/dist/server/routes/auth.js +189 -27
  117. package/dist/server/routes/authSupport.d.ts +4 -0
  118. package/dist/server/routes/authSupport.js +12 -1
  119. package/dist/server/routes/billing.js +11 -0
  120. package/dist/server/routes/billing.test.js +3 -0
  121. package/dist/server/routes/documents.js +10 -0
  122. package/dist/server/routes/shared.js +0 -10
  123. package/dist/server/routes/sync.integration.test.js +221 -0
  124. package/dist/server/routes/sync.js +13 -3
  125. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  126. package/dist/server/routes/tasks.js +12 -9
  127. package/dist/server/routes.js +87 -8
  128. package/dist/server/routes.test.js +890 -22
  129. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  130. package/dist/shared/aiProfileSurfaceType.js +10 -8
  131. package/dist/storage/objectStorage.d.ts +1 -0
  132. package/dist/storage/objectStorage.js +2 -1
  133. package/dist/storage/objectStorageClient.d.ts +1 -0
  134. package/dist/storage/objectStorageClient.js +41 -8
  135. package/dist/storage/objectStorageClient.test.js +26 -1
  136. package/dist/storage/objectStorageConfig.js +3 -1
  137. package/dist/storage/objectStorageConfig.test.js +13 -1
  138. package/dist/sync/collaborationSyncPayload.js +2 -0
  139. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  140. package/dist/sync/collaborationSyncState.d.ts +3 -3
  141. package/dist/sync/collaborationSyncState.js +19 -12
  142. package/dist/sync/contentSyncPayload.d.ts +5 -1
  143. package/dist/sync/contentSyncPayload.js +9 -0
  144. package/dist/sync/contentSyncPayload.test.js +39 -0
  145. package/dist/sync/contentSyncState.d.ts +5 -1
  146. package/dist/sync/contentSyncState.js +231 -37
  147. package/dist/sync/planningSyncPayload.js +5 -0
  148. package/dist/sync/planningSyncPayload.test.js +24 -0
  149. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  150. package/dist/sync/syncApplyHandlers.js +89 -31
  151. package/dist/sync/syncApplyHandlers.test.js +184 -4
  152. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  153. package/dist/sync/syncResourceAdapters.js +2 -0
  154. package/dist/sync/syncService.d.ts +1 -0
  155. package/dist/sync/syncService.js +13 -1
  156. package/dist/sync/syncService.test.js +39 -0
  157. package/dist/sync/workspacePullFeed.d.ts +4 -0
  158. package/dist/sync/workspacePullFeed.js +129 -70
  159. package/dist/sync/workspacePullFeed.test.js +49 -0
  160. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  161. package/dist/sync/workspaceSyncModel.js +159 -45
  162. package/dist/sync/workspaceSyncModel.test.js +269 -3
  163. package/dist/types.d.ts +33 -2
  164. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  165. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  166. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  167. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  168. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  169. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  170. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  171. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  172. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
  173. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  174. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  175. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  176. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  177. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  178. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  179. package/dist/ui/assets/index-607WPo_X.js +4 -0
  180. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  181. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  182. package/dist/ui/index.html +3 -3
  183. package/dist/ui/product/agent manager.png +0 -0
  184. package/dist/ui/product/agent_manager_02.png +0 -0
  185. package/dist/utils/aiProfileEvents.d.ts +7 -0
  186. package/dist/utils/aiProfileEvents.js +6 -0
  187. package/dist/utils/assignees.d.ts +6 -0
  188. package/dist/utils/assignees.js +8 -1
  189. package/dist/utils/assignees.test.d.ts +1 -0
  190. package/dist/utils/assignees.test.js +25 -0
  191. package/dist/utils/avatarDisplay.d.ts +1 -0
  192. package/dist/utils/avatarDisplay.js +15 -0
  193. package/dist/utils/contextFiles.d.ts +3 -1
  194. package/dist/utils/contextFiles.js +2 -1
  195. package/dist/utils/taskActivity.d.ts +7 -1
  196. package/dist/utils/taskActivity.js +63 -1
  197. package/dist/utils/taskActivity.test.js +23 -1
  198. package/dist/utils/taskSearch.d.ts +22 -0
  199. package/dist/utils/taskSearch.js +87 -0
  200. package/dist/utils/taskSearch.test.d.ts +1 -0
  201. package/dist/utils/taskSearch.test.js +69 -0
  202. package/dist/utils/taskUtils.js +10 -3
  203. package/dist/utils/taskUtils.test.d.ts +1 -0
  204. package/dist/utils/taskUtils.test.js +19 -0
  205. package/package.json +1 -1
  206. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  207. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  208. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  209. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  210. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  211. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  212. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  213. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  214. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  215. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -8,13 +8,13 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
9
9
  <link rel="apple-touch-icon" href="/taskforce/favicon/apple-touch-icon.png" />
10
10
  <title>Taskforce</title>
11
- <script type="module" crossorigin src="/taskforce/assets/index-C6k75jr8.js"></script>
11
+ <script type="module" crossorigin src="/taskforce/assets/index-607WPo_X.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
13
- <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-DuEd_65c.js">
13
+ <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-B32hGuF2.js">
14
14
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
15
15
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-dnd-DRzYolkg.js">
16
16
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-router-BbWMxlnO.js">
17
- <link rel="stylesheet" crossorigin href="/taskforce/assets/index-9eT8e0Lu.css">
17
+ <link rel="stylesheet" crossorigin href="/taskforce/assets/index-CYyodXsg.css">
18
18
  </head>
19
19
  <body>
20
20
  <div id="root"></div>
@@ -0,0 +1,7 @@
1
+ export declare const TASKFORCE_AI_PROFILES_MUTATED_EVENT = "taskforce:ai-profiles-mutated";
2
+ export interface TaskforceAiProfilesMutatedDetail {
3
+ workspaceId?: string | null;
4
+ profileId?: string | null;
5
+ reason?: 'avatar' | 'merge' | 'archive' | 'update';
6
+ }
7
+ export declare function dispatchTaskforceAiProfilesMutated(detail: TaskforceAiProfilesMutatedDetail): void;
@@ -0,0 +1,6 @@
1
+ export const TASKFORCE_AI_PROFILES_MUTATED_EVENT = 'taskforce:ai-profiles-mutated';
2
+ export function dispatchTaskforceAiProfilesMutated(detail) {
3
+ if (typeof window === 'undefined' || typeof window.dispatchEvent !== 'function')
4
+ return;
5
+ window.dispatchEvent(new CustomEvent(TASKFORCE_AI_PROFILES_MUTATED_EVENT, { detail }));
6
+ }
@@ -15,6 +15,12 @@ export interface TaskAssigneeOption {
15
15
  color: string;
16
16
  kind: AssigneeKind;
17
17
  avatarUrl?: string | null;
18
+ avatarRevision?: number | null;
19
+ avatarUpdatedAt?: string | null;
20
+ username?: string | null;
21
+ archivedAt?: string | null;
22
+ createdAt?: string | null;
23
+ updatedAt?: string | null;
18
24
  }
19
25
  export declare function isAgentAssignee(value: unknown): boolean;
20
26
  export declare function isUnassignedAssignee(value: unknown): boolean;
@@ -61,7 +61,8 @@ export function mergeAssigneeOptions(options) {
61
61
  }
62
62
  for (const option of Array.isArray(options) ? options : []) {
63
63
  const value = String(option?.value || '').trim();
64
- if (!value || value === TASK_ASSIGNEE_UNASSIGNED || value === TASK_ASSIGNEE_AGENT)
64
+ const archivedAt = typeof option?.archivedAt === 'string' ? option.archivedAt.trim() : '';
65
+ if (!value || archivedAt || value === TASK_ASSIGNEE_UNASSIGNED || value === TASK_ASSIGNEE_AGENT)
65
66
  continue;
66
67
  deduped.set(value, {
67
68
  value,
@@ -70,6 +71,12 @@ export function mergeAssigneeOptions(options) {
70
71
  color: String(option.color || (option.kind === 'agent' ? '#8b5cf6' : '#22c55e')),
71
72
  kind: option.kind === 'agent' ? 'agent' : 'member',
72
73
  avatarUrl: typeof option.avatarUrl === 'string' && option.avatarUrl.trim().length > 0 ? option.avatarUrl.trim() : null,
74
+ avatarRevision: Number.isFinite(Number(option.avatarRevision)) ? Math.max(0, Math.floor(Number(option.avatarRevision))) : null,
75
+ avatarUpdatedAt: typeof option.avatarUpdatedAt === 'string' && option.avatarUpdatedAt.trim().length > 0 ? option.avatarUpdatedAt.trim() : null,
76
+ username: typeof option.username === 'string' && option.username.trim().length > 0 ? option.username.trim() : null,
77
+ archivedAt: null,
78
+ createdAt: typeof option.createdAt === 'string' && option.createdAt.trim().length > 0 ? option.createdAt.trim() : null,
79
+ updatedAt: typeof option.updatedAt === 'string' && option.updatedAt.trim().length > 0 ? option.updatedAt.trim() : null,
73
80
  });
74
81
  }
75
82
  const defaults = [TASK_ASSIGNEE_UNASSIGNED];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mergeAssigneeOptions } from './assignees';
3
+ describe('mergeAssigneeOptions', () => {
4
+ it('keeps one unassigned option and filters archived AI profile options', () => {
5
+ expect(mergeAssigneeOptions([
6
+ { value: 'unassigned', label: 'Unassigned', icon: 'HelpCircle', color: '#999', kind: 'unassigned' },
7
+ { value: 'ai-profile-retired', label: 'Retired Agent', icon: 'Bot', color: '#8b5cf6', kind: 'agent', archivedAt: '2026-04-24T15:28:13.577Z' },
8
+ { value: 'ai-profile-codex', label: 'Codex', icon: 'Bot', color: '#8b5cf6', kind: 'agent' },
9
+ ])).toEqual([
10
+ { value: 'unassigned', label: 'Unassigned', icon: 'HelpCircle', color: 'var(--text-secondary)', kind: 'unassigned' },
11
+ {
12
+ value: 'ai-profile-codex',
13
+ label: 'Codex',
14
+ icon: 'Bot',
15
+ color: '#8b5cf6',
16
+ kind: 'agent',
17
+ avatarUrl: null,
18
+ username: null,
19
+ archivedAt: null,
20
+ createdAt: null,
21
+ updatedAt: null,
22
+ },
23
+ ]);
24
+ });
25
+ });
@@ -0,0 +1 @@
1
+ export declare function buildVersionedAvatarUrl(url: unknown, revision?: unknown, updatedAt?: unknown): string;
@@ -0,0 +1,15 @@
1
+ export function buildVersionedAvatarUrl(url, revision, updatedAt) {
2
+ const normalizedUrl = typeof url === 'string' ? url.trim() : '';
3
+ if (!normalizedUrl || normalizedUrl.startsWith('data:') || normalizedUrl.startsWith('blob:')) {
4
+ return normalizedUrl;
5
+ }
6
+ const normalizedRevision = Number(revision);
7
+ const version = Number.isFinite(normalizedRevision) && normalizedRevision > 0
8
+ ? String(Math.floor(normalizedRevision))
9
+ : (typeof updatedAt === 'string' && updatedAt.trim().length > 0 ? updatedAt.trim() : '');
10
+ if (!version)
11
+ return normalizedUrl;
12
+ const [base, hash = ''] = normalizedUrl.split('#');
13
+ const separator = base.includes('?') ? '&' : '?';
14
+ return `${base}${separator}v=${encodeURIComponent(version)}${hash ? `#${hash}` : ''}`;
15
+ }
@@ -2,11 +2,13 @@ export interface OpenMarkdownEditorDetail {
2
2
  path: string;
3
3
  fsPath: string;
4
4
  assetId?: string;
5
+ taskId?: string | null;
5
6
  }
6
7
  type MarkdownOpenTarget = string | {
7
8
  path: string;
8
9
  fsPath?: string;
9
- assetId?: string;
10
+ assetId?: string | null;
11
+ taskId?: string | null;
10
12
  caption?: string;
11
13
  displayName?: string;
12
14
  originalFilename?: string;
@@ -77,7 +77,8 @@ export function dispatchOpenMarkdownDocument(target, fsPath) {
77
77
  detail: {
78
78
  path: sourcePath,
79
79
  fsPath: resolvedFsPath,
80
- ...(assetId ? { assetId } : {})
80
+ ...(assetId ? { assetId } : {}),
81
+ ...((typeof target !== 'string' && target.taskId) ? { taskId: target.taskId } : {})
81
82
  },
82
83
  cancelable: true
83
84
  });
@@ -1,11 +1,17 @@
1
- import type { TaskActivityItem, TaskEvent, TaskItem } from '../types';
1
+ import type { EntityActivityItem, EntityEvent, TaskActivityItem, TaskEvent, TaskItem } from '../types';
2
2
  export type TaskEventValueFormatter = (field: string, value: unknown) => string | undefined;
3
3
  export declare function summarizeTaskChange(field: string, change: {
4
4
  from?: unknown;
5
5
  to?: unknown;
6
6
  } | undefined, formatter?: TaskEventValueFormatter): string;
7
7
  export declare function summarizeTaskEvent(event: TaskEvent, formatter?: TaskEventValueFormatter): string;
8
+ export declare function summarizeEntityEvent(event: EntityEvent, formatter?: TaskEventValueFormatter): string;
8
9
  export declare function getTaskEventPrimaryChangeField(event: TaskEvent): string | null;
10
+ export declare function getEntityEventPrimaryChangeField(event: EntityEvent): string | null;
11
+ export declare function getEntityActivityItems(task: {
12
+ activity?: EntityActivityItem[] | TaskActivityItem[];
13
+ comments?: TaskItem['comments'];
14
+ }): EntityActivityItem[];
9
15
  export declare function getTaskActivityItems(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem[];
10
16
  export declare function getLatestTaskActivity(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem | null;
11
17
  export declare function summarizeTaskActivity(item: TaskActivityItem | null, formatter?: TaskEventValueFormatter): string;
@@ -1,10 +1,14 @@
1
1
  function getActivityFieldLabel(field) {
2
+ if (field === 'taskId')
3
+ return 'Task';
2
4
  if (field === 'title')
3
5
  return 'Title';
4
6
  if (field === 'description')
5
7
  return 'Description';
6
8
  if (field === 'assignee')
7
9
  return 'Assigned to';
10
+ if (field === 'ownerId')
11
+ return 'Owner';
8
12
  if (field === 'status')
9
13
  return 'Status';
10
14
  if (field === 'scheduledDate')
@@ -277,6 +281,47 @@ export function summarizeTaskEvent(event, formatter) {
277
281
  return `${fields.length} fields updated`;
278
282
  return 'Task updated';
279
283
  }
284
+ export function summarizeEntityEvent(event, formatter) {
285
+ if (event.taskId || event.entityType === 'task') {
286
+ return summarizeTaskEvent(event, formatter);
287
+ }
288
+ const changes = event.details?.changes || {};
289
+ const fields = Object.keys(changes);
290
+ const entityLabel = event.entityType === 'initiative' ? 'Initiative' : 'Workstream';
291
+ const taskTitle = typeof event.details?.taskTitle === 'string' && event.details.taskTitle.trim().length > 0
292
+ ? event.details.taskTitle.trim()
293
+ : null;
294
+ if (event.action === 'initiative-created')
295
+ return 'Initiative created';
296
+ if (event.action === 'initiative-archived')
297
+ return 'Initiative archived';
298
+ if (event.action === 'initiative-unarchived')
299
+ return 'Initiative restored';
300
+ if (event.action === 'workstream-created')
301
+ return 'Workstream created';
302
+ if (event.action === 'workstream-archived')
303
+ return 'Workstream archived';
304
+ if (event.action === 'workstream-unarchived')
305
+ return 'Workstream restored';
306
+ if (event.action === 'workstream-task-added')
307
+ return taskTitle ? `Task added: ${taskTitle}` : 'Task added';
308
+ if (event.action === 'workstream-task-removed')
309
+ return taskTitle ? `Task removed: ${taskTitle}` : 'Task removed';
310
+ if (event.action === 'workstream-relationship-changed' && changes.initiativeId) {
311
+ return `Initiative changed: ${formatValue(changes.initiativeId.from, 'initiativeId', formatter)} -> ${formatValue(changes.initiativeId.to, 'initiativeId', formatter)}`;
312
+ }
313
+ if ((event.action === 'initiative-attachments-changed' || event.action === 'workstream-attachments-changed') && changes.attachments) {
314
+ return summarizeTaskChange('attachments', changes.attachments, formatter);
315
+ }
316
+ if (fields.length === 1) {
317
+ return summarizeTaskChange(fields[0], changes[fields[0]], formatter);
318
+ }
319
+ if (fields.length > 1)
320
+ return `${fields.length} fields updated`;
321
+ if (event.action === 'initiative-updated' || event.action === 'workstream-updated')
322
+ return `${entityLabel} updated`;
323
+ return `${entityLabel} activity updated`;
324
+ }
280
325
  function activityTimestamp(item) {
281
326
  const parsed = Date.parse(String(item.timestamp || ''));
282
327
  return Number.isFinite(parsed) ? parsed : null;
@@ -302,7 +347,21 @@ export function getTaskEventPrimaryChangeField(event) {
302
347
  return 'attachments';
303
348
  return fields.length === 1 ? fields[0] : null;
304
349
  }
305
- export function getTaskActivityItems(task) {
350
+ export function getEntityEventPrimaryChangeField(event) {
351
+ if (event.taskId || event.entityType === 'task') {
352
+ return getTaskEventPrimaryChangeField(event);
353
+ }
354
+ const changes = event.details?.changes || {};
355
+ const fields = Object.keys(changes);
356
+ if ((event.action === 'workstream-task-added' || event.action === 'workstream-task-removed') && event.details?.taskId)
357
+ return 'taskId';
358
+ if (event.action === 'workstream-relationship-changed' && changes.initiativeId)
359
+ return 'initiativeId';
360
+ if ((event.action === 'initiative-attachments-changed' || event.action === 'workstream-attachments-changed') && changes.attachments)
361
+ return 'attachments';
362
+ return fields.length === 1 ? fields[0] : null;
363
+ }
364
+ export function getEntityActivityItems(task) {
306
365
  const deduped = new Map();
307
366
  const commentIds = new Set();
308
367
  const addItem = (item) => {
@@ -344,6 +403,9 @@ export function getTaskActivityItems(task) {
344
403
  return String(left.id || '').localeCompare(String(right.id || ''));
345
404
  });
346
405
  }
406
+ export function getTaskActivityItems(task) {
407
+ return getEntityActivityItems(task);
408
+ }
347
409
  export function getLatestTaskActivity(task) {
348
410
  const items = getTaskActivityItems(task);
349
411
  if (items.length === 0)
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { getTaskActivityItems, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
2
+ import { getTaskActivityItems, summarizeEntityEvent, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
3
3
  describe('summarizeTaskChange', () => {
4
4
  it('summarizes title edits without dumping before and after text', () => {
5
5
  expect(summarizeTaskChange('title', {
@@ -149,6 +149,28 @@ describe('summarizeTaskEvent', () => {
149
149
  },
150
150
  })).toBe('Image replaced: Old proof -> New proof');
151
151
  });
152
+ it('uses task-focused summaries for workstream task relationship events', () => {
153
+ expect(summarizeEntityEvent({
154
+ id: 'event-3',
155
+ workspaceId: 'default',
156
+ entityType: 'workstream',
157
+ entityId: 'workstream-1',
158
+ action: 'workstream-task-added',
159
+ actor: 'user',
160
+ actorType: 'human',
161
+ createdAt: '2026-04-12T21:42:00.000Z',
162
+ details: {
163
+ taskId: 'task-1',
164
+ taskTitle: 'Ship launch checklist',
165
+ changes: {
166
+ workstreamId: {
167
+ from: null,
168
+ to: 'workstream-1',
169
+ },
170
+ },
171
+ },
172
+ })).toBe('Task added: Ship launch checklist');
173
+ });
152
174
  });
153
175
  describe('getTaskActivityItems', () => {
154
176
  it('combines comments and audit events while hiding duplicate comment-added events', () => {
@@ -0,0 +1,22 @@
1
+ import type { CategoryConfig, PriorityDefinition, TaskItem, TaxonomyDefinition } from '../types';
2
+ import type { TaskAssigneeOption } from './assignees';
3
+ export interface NormalizedTaskSearchQuery {
4
+ raw: string;
5
+ tokens: string[];
6
+ referenceToken: string | null;
7
+ }
8
+ export interface TaskSearchContext {
9
+ categories?: CategoryConfig[];
10
+ types?: Array<{
11
+ value: string | number;
12
+ label: string;
13
+ }>;
14
+ priorities?: PriorityDefinition[];
15
+ assigneeOptions?: TaskAssigneeOption[];
16
+ taxonomies?: TaxonomyDefinition[];
17
+ }
18
+ export declare function normalizeSearchText(value: unknown): string;
19
+ export declare function normalizeTaskReferenceToken(value: unknown): string | null;
20
+ export declare function normalizeTaskSearchQuery(query: string): NormalizedTaskSearchQuery;
21
+ export declare function getTaskSearchFields(task: TaskItem, context?: TaskSearchContext): string[];
22
+ export declare function taskMatchesSearch(task: TaskItem, query: string | NormalizedTaskSearchQuery, context?: TaskSearchContext): boolean;
@@ -0,0 +1,87 @@
1
+ import { STATUS_OPTIONS } from './constants';
2
+ import { getTaskReferenceLabel } from './taskReferences';
3
+ const DASH_LIKE_PATTERN = /[\u2010-\u2015\u2212]/g;
4
+ export function normalizeSearchText(value) {
5
+ return String(value ?? '')
6
+ .trim()
7
+ .replace(DASH_LIKE_PATTERN, '-')
8
+ .replace(/\s+/g, ' ')
9
+ .toLowerCase();
10
+ }
11
+ export function normalizeTaskReferenceToken(value) {
12
+ const normalized = normalizeSearchText(value);
13
+ if (!normalized)
14
+ return null;
15
+ const match = normalized.match(/^(l\s*t|t)\s*(?:-\s*)?(\d+)$/i);
16
+ if (!match)
17
+ return null;
18
+ const prefix = match[1].replace(/\s+/g, '').toLowerCase();
19
+ return `${prefix}-${match[2]}`;
20
+ }
21
+ export function normalizeTaskSearchQuery(query) {
22
+ const raw = normalizeSearchText(query);
23
+ return {
24
+ raw,
25
+ tokens: raw ? raw.split(' ').filter(Boolean) : [],
26
+ referenceToken: normalizeTaskReferenceToken(query)
27
+ };
28
+ }
29
+ function addValue(values, value) {
30
+ const normalized = String(value ?? '').trim();
31
+ if (normalized)
32
+ values.push(normalized);
33
+ }
34
+ function addMappedLabel(values, options, targetValue) {
35
+ const target = String(targetValue ?? '').trim();
36
+ if (!target)
37
+ return;
38
+ const option = options?.find((entry) => String(entry.value) === target);
39
+ addValue(values, option?.label);
40
+ }
41
+ export function getTaskSearchFields(task, context = {}) {
42
+ const values = [];
43
+ addValue(values, task.title);
44
+ addValue(values, task.description);
45
+ addValue(values, task.id);
46
+ addValue(values, getTaskReferenceLabel(task));
47
+ addValue(values, task.category);
48
+ addValue(values, task.type);
49
+ addValue(values, task.priority);
50
+ addValue(values, task.status);
51
+ addValue(values, task.assignee || 'unassigned');
52
+ addValue(values, task.canceledReason);
53
+ addMappedLabel(values, context.categories, task.category);
54
+ addMappedLabel(values, context.types, task.type);
55
+ addMappedLabel(values, context.priorities, task.priority);
56
+ addMappedLabel(values, STATUS_OPTIONS, task.status);
57
+ addMappedLabel(values, context.assigneeOptions, task.assignee || 'unassigned');
58
+ Object.entries(task.taxonomies || {}).forEach(([taxonomyId, taskValue]) => {
59
+ const taxonomy = context.taxonomies?.find((entry) => entry.id === taxonomyId);
60
+ addValue(values, taxonomy?.label);
61
+ const taskValues = Array.isArray(taskValue) ? taskValue : [taskValue];
62
+ taskValues.forEach((value) => {
63
+ addValue(values, value);
64
+ addMappedLabel(values, taxonomy?.options, value);
65
+ });
66
+ });
67
+ return values;
68
+ }
69
+ export function taskMatchesSearch(task, query, context = {}) {
70
+ const normalizedQuery = typeof query === 'string' ? normalizeTaskSearchQuery(query) : query;
71
+ if (!normalizedQuery.raw)
72
+ return true;
73
+ const fields = getTaskSearchFields(task, context);
74
+ const normalizedFields = fields.map(normalizeSearchText).filter(Boolean);
75
+ const joinedFields = normalizedFields.join(' ');
76
+ if (joinedFields.includes(normalizedQuery.raw))
77
+ return true;
78
+ if (normalizedQuery.referenceToken) {
79
+ const referenceMatches = fields
80
+ .map(normalizeTaskReferenceToken)
81
+ .some((referenceToken) => referenceToken?.includes(normalizedQuery.referenceToken));
82
+ if (referenceMatches)
83
+ return true;
84
+ }
85
+ return normalizedQuery.tokens.length > 0
86
+ && normalizedQuery.tokens.every((token) => joinedFields.includes(token));
87
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeTaskReferenceToken, normalizeTaskSearchQuery, taskMatchesSearch, } from './taskSearch';
3
+ function makeTask(overrides = {}) {
4
+ return {
5
+ id: overrides.id || 'task-1',
6
+ title: overrides.title || 'Task',
7
+ description: overrides.description ?? null,
8
+ status: overrides.status || 'task',
9
+ priority: overrides.priority ?? 2,
10
+ complexity: overrides.complexity ?? 3,
11
+ type: overrides.type || 'feature',
12
+ category: overrides.category || 'default',
13
+ createdAt: overrides.createdAt || '2026-03-23T00:00:00.000Z',
14
+ updatedAt: overrides.updatedAt || null,
15
+ completedAt: overrides.completedAt || null,
16
+ assignee: overrides.assignee || 'unassigned',
17
+ comments: [],
18
+ attachments: [],
19
+ activity: [],
20
+ taskEvents: [],
21
+ referenceLabel: overrides.referenceLabel,
22
+ taxonomies: overrides.taxonomies || {}
23
+ };
24
+ }
25
+ describe('taskSearch', () => {
26
+ it('normalizes common pasted task reference formats', () => {
27
+ expect(normalizeTaskReferenceToken(' T-123 ')).toBe('t-123');
28
+ expect(normalizeTaskReferenceToken('T - 123')).toBe('t-123');
29
+ expect(normalizeTaskReferenceToken('T 123')).toBe('t-123');
30
+ expect(normalizeTaskReferenceToken('t–123')).toBe('t-123');
31
+ expect(normalizeTaskReferenceToken('LT - 45')).toBe('lt-45');
32
+ });
33
+ it('matches canonicalized task references', () => {
34
+ const task = makeTask({ referenceLabel: 'T-123' });
35
+ expect(taskMatchesSearch(task, normalizeTaskSearchQuery('T - 123'))).toBe(true);
36
+ expect(taskMatchesSearch(task, normalizeTaskSearchQuery('t–123'))).toBe(true);
37
+ });
38
+ it('matches all query tokens across task text', () => {
39
+ const task = makeTask({ title: 'Billing cleanup', description: 'Audit the API handlers' });
40
+ expect(taskMatchesSearch(task, 'api billing')).toBe(true);
41
+ expect(taskMatchesSearch(task, 'api invoices')).toBe(false);
42
+ });
43
+ it('matches task metadata labels', () => {
44
+ const task = makeTask({
45
+ category: 'ops',
46
+ type: 'bug',
47
+ priority: 3,
48
+ assignee: 'member-1',
49
+ taxonomies: { track: 'platform' }
50
+ });
51
+ const context = {
52
+ categories: [{ value: 'ops', label: 'Operations', disabled: false }],
53
+ types: [{ value: 'bug', label: 'Bug' }],
54
+ priorities: [{ value: 3, label: 'High' }],
55
+ assigneeOptions: [{ value: 'member-1', label: 'Member One', icon: 'User', color: '#22c55e', kind: 'member' }],
56
+ taxonomies: [{
57
+ id: 'track',
58
+ label: 'Track',
59
+ options: [{ value: 'platform', label: 'Platform' }],
60
+ widgetType: 'select',
61
+ isSystem: false,
62
+ multiSelect: false
63
+ }]
64
+ };
65
+ expect(taskMatchesSearch(task, 'operations', context)).toBe(true);
66
+ expect(taskMatchesSearch(task, 'member one', context)).toBe(true);
67
+ expect(taskMatchesSearch(task, 'track platform', context)).toBe(true);
68
+ });
69
+ });
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as Icons from 'lucide-react';
3
3
  import { TypeMeta } from './constants';
4
4
  import styles from '../Taskforce.module.css';
5
+ import { normalizeTaskReferenceToken } from './taskSearch';
5
6
  export const getIconForType = (type, size = 10) => {
6
7
  const meta = TypeMeta[type?.toLowerCase()] || { icon: 'FileCode', color: 'indigo-500' };
7
8
  const IconComp = Icons[meta.icon] || Icons.FileCode;
@@ -10,11 +11,17 @@ export const getIconForType = (type, size = 10) => {
10
11
  export const renderHighlighted = (text, query) => {
11
12
  if (!text)
12
13
  return null;
13
- if (!query.trim())
14
+ const normalizedQuery = query.trim();
15
+ if (!normalizedQuery)
14
16
  return text;
15
- const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ const referenceToken = normalizeTaskReferenceToken(normalizedQuery);
18
+ const referenceParts = referenceToken?.match(/^(lt|t)-(\d+)$/);
19
+ const escapedQuery = referenceParts
20
+ ? `${referenceParts[1] === 'lt' ? 'l\\s*t' : 't'}\\s*(?:-|\\s)?\\s*${referenceParts[2]}`
21
+ : normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ const highlightPattern = new RegExp(`^${escapedQuery}$`, 'i');
16
23
  const parts = text.split(new RegExp(`(${escapedQuery})`, 'gi'));
17
- return (_jsx(_Fragment, { children: parts.map((part, i) => part.toLowerCase() === query.toLowerCase()
24
+ return (_jsx(_Fragment, { children: parts.map((part, i) => highlightPattern.test(part)
18
25
  ? _jsx("mark", { className: styles.highlight, children: part }, i)
19
26
  : part) }));
20
27
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from '@testing-library/react';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { renderHighlighted } from './taskUtils';
5
+ describe('renderHighlighted', () => {
6
+ it('highlights matches when the query has surrounding spaces', () => {
7
+ const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' T-123 ') }));
8
+ expect(container.querySelector('mark')?.textContent).toBe('T-123');
9
+ });
10
+ it('highlights task references when the query has pasted formatting', () => {
11
+ const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' T - 123 ') }));
12
+ expect(container.querySelector('mark')?.textContent).toBe('T-123');
13
+ });
14
+ it('treats whitespace-only queries as empty', () => {
15
+ const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' ') }));
16
+ expect(container.querySelector('mark')).toBeNull();
17
+ expect(container.textContent).toBe('T-123');
18
+ });
19
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskforcehq/taskforce",
3
- "version": "0.3.306",
3
+ "version": "0.3.307",
4
4
  "description": "Mission Command center for AI-powered task management (beta - under active development)",
5
5
  "author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- import{r as c,j as e}from"./vendor-react-CKJs5o3c.js";import{u as se,p as oe,v as ne,w as ce,x as _,y as de,z as pe,t as r,E as fe,B as me,C as K}from"./index-C6k75jr8.js";import{h as ue,B as V,k as U,b as he,$ as ve,a3 as ge}from"./vendor-icons-DuEd_65c.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function J(d){return!d||typeof d!="object"?null:{used:Math.max(0,Number(d.used||0)),limit:d.limit===null||d.limit===void 0?null:Math.max(0,Number(d.limit||0)),remaining:d.remaining===null||d.remaining===void 0?null:Math.max(0,Number(d.remaining||0))}}function W(d,P){const x=P?.variant==="inline"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconInline}`:P?.variant==="detail"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconDetail}`:r.aiProfileSeatScopeIcon;return d==="cloud_metered"?e.jsx("span",{className:`${x} ${r.aiProfileSeatScopeIconCloud}`,title:"Cloud MCP","aria-label":"Cloud MCP",children:e.jsx(ve,{size:20})}):d==="local_unmetered"?e.jsx("span",{className:`${x} ${r.aiProfileSeatScopeIconLocal}`,title:"Local MCP","aria-label":"Local MCP",children:e.jsx(ge,{size:20})}):null}function Ie({workspaceId:d,cloudAuthConfigured:P=!1,authSessionResolved:x=!1,isAuthenticated:Y=!1,cloudAiProfileSeatUsage:q=null}){const[S,L]=c.useState([]),[Q,X]=c.useState(null),[M,$]=c.useState(!1),[f,j]=c.useState(null),[I,v]=c.useState(null),[N,A]=c.useState(null),[g,b]=c.useState(null),[E,T]=c.useState(null),[B,D]=c.useState(!1),[Z,m]=c.useState(null),w=c.useCallback(async()=>{if(d){$(!0);try{const a=`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(d)}`,i=await fetch(a,{credentials:"include"}),t=i.ok?await i.json().catch(()=>({})):{},s=J(t?.aiProfileSeatUsage),n=Array.isArray(t?.assignees)?t.assignees.filter(l=>l.kind==="agent").map(l=>({id:String(l.value||""),name:String(l.label||l.value||"Unknown Agent"),username:String(l.username||l.value||""),icon:String(l.icon||"Bot"),color:String(l.color||"#6B7280"),avatarUrl:typeof l.avatarUrl=="string"?l.avatarUrl:null,avatarSourceUrl:typeof l.avatarSourceUrl=="string"?l.avatarSourceUrl:null,kind:String(l.kind||"agent"),description:typeof l.description=="string"?l.description:null,role:typeof l.role=="string"?l.role:null,provider:typeof l.provider=="string"?l.provider:null,model:typeof l.model=="string"?l.model:null,surfaceType:oe(l.surfaceType),seatScope:se(l.seatScope),archivedAt:typeof l.archivedAt=="string"?l.archivedAt:null,createdAt:String(l.createdAt||""),updatedAt:String(l.updatedAt||l.createdAt||""),lastActiveAt:typeof l.lastActiveAt=="string"?l.lastActiveAt:null})):[];L(n),X(s),A(l=>l&&!n.some(h=>h.id===l.profileId)?null:l)}finally{$(!1)}}},[d]);c.useEffect(()=>{w()},[w]);const k=c.useMemo(()=>{const a=new Map;for(const i of S){const t=ne(i.surfaceType);a.has(t)||a.set(t,new Map);const s=a.get(t),n=i.name.toLowerCase();s.has(n)||s.set(n,[]),s.get(n).push(i)}return ce.map(i=>({section:i,label:_(i),groups:Array.from(a.get(i)?.entries()||[]).map(([t,s])=>({groupId:`${i}:${t}`,section:i,sectionLabel:_(i),profiles:s,primaryProfile:s[0]}))})).filter(i=>i.groups.length>0)},[S]),u=c.useMemo(()=>k.flatMap(a=>a.groups),[k]),o=c.useMemo(()=>u.find(a=>a.groupId===g)||u[0]||null,[u,g]),p=c.useMemo(()=>S.find(a=>a.id===E)||null,[S,E]);c.useEffect(()=>{if(!u.length){g!==null&&b(null);return}(!g||!u.some(a=>a.groupId===g))&&b(u[0].groupId)},[u,g]);const ee=async(a,i)=>{v(null),A(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:a,mergeId:i})}),s=await t.json().catch(()=>({}));if(!t.ok){v({type:"error",message:String(s?.error||"Failed to merge AI profiles.")});return}j(null),v({type:"success",message:"AI profiles merged."}),await w()}catch{v({type:"error",message:"Failed to merge AI profiles."})}},G=async a=>{if(window.confirm(`Remove ${a.name} from the active roster? This frees an AI profile seat and preserves task and comment history.`)){A(null),v(null);try{const t=await fetch("/api/taskforce/workspace/ai-profiles/archive",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a.id,reason:"manual_archive"})}),s=await t.json().catch(()=>({}));if(!t.ok){A({profileId:a.id,message:String(s?.error||"Failed to remove AI profile from roster.")});return}(f?.keepId===a.id||f?.mergeId===a.id)&&j(null),v({type:"success",message:"AI profile removed from active roster."}),await w()}catch{A({profileId:a.id,message:"Failed to remove AI profile from roster."})}}},ae=a=>new Promise((i,t)=>{const s=new FileReader;s.onload=()=>i(String(s.result||"")),s.onerror=()=>t(new Error("Failed to read image file.")),s.readAsDataURL(a)}),re=a=>{const i=String(a?.id||"").trim();i&&L(t=>t.map(s=>s.id===i?{...s,avatarUrl:typeof a.avatarUrl=="string"?a.avatarUrl:null,avatarSourceUrl:typeof a.avatarSourceUrl=="string"?a.avatarSourceUrl:null,updatedAt:typeof a.updatedAt=="string"?a.updatedAt:s.updatedAt}:s))},F=async(a,i,t)=>{const s=await fetch("/api/taskforce/workspace/ai-profiles/avatar",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a,avatarUrl:i,...t!==void 0?{avatarSourceUrl:t}:{}})}),n=await s.json().catch(()=>({}));if(!s.ok||!n?.profile)throw new Error(String(n?.error||"Failed to update AI profile avatar."));re(n.profile)},z=async(a,i,t)=>{const s=await ae(a),n=await fetch("/api/taskforce/context-upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({workspaceId:d,file:s,originalName:`${t}-${a.name}`,folderPath:`ai-profiles/${i.id}/avatar`})}),l=await n.json().catch(()=>({})),h=typeof l?.path=="string"?l.path:"";if(!n.ok||!h)throw new Error(String(l?.error||"Failed to upload AI profile photo."));return h},ie=async(a,i)=>{if(!p)return!1;if(!a.type.startsWith("image/"))return m("AI profile photo must be an image file."),!1;D(!0),m(null);try{const t=await K(a,{maxBytes:5242880});if(t.exceededLimit)throw new Error(a.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile photo must be 5 MB or smaller.");const s=t.file,n=await z(s,p,"cropped");let l;if(i){const h=await K(i,{maxBytes:5242880});if(h.exceededLimit)throw new Error(i.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile source photo must be 5 MB or smaller.");l=i.type==="image/gif"?n:await z(h.file,p,"source")}return await F(p.id,n,l),!0}catch(t){return m(String(t?.message||"Failed to update AI profile photo.")),!1}finally{D(!1)}},te=async()=>{if(p){D(!0),m(null);try{await F(p.id,null,null)}catch(a){m(String(a?.message||"Failed to remove AI profile photo."))}finally{D(!1)}}},y=a=>{const i=String(a||"").trim();if(!i)return"Unknown";const t=Date.parse(i);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):i},H=c.useMemo(()=>{if(!o)return{attributes:[],dates:[]};const a=o.primaryProfile,i=t=>t||"Not set";return{attributes:[{label:"Category",value:a.surfaceType?de(a.surfaceType):""},{label:"Connection",value:a.seatScope?pe(a.seatScope):""},{label:"Provider",value:a.provider||""},{label:"Model",value:a.model||""}].map(t=>({...t,value:i(t.value)})),dates:[{label:"Recruited",value:y(a.createdAt)},{label:"Last updated",value:y(a.updatedAt)},{label:"Last active",value:y(a.lastActiveAt)},{label:"Status",value:a.archivedAt?"Retired":"Active"}].map(t=>({...t,value:i(t.value)}))}},[o]),le=!!o&&o.profiles.length>1,O=P&&x&&!Y,C=P?J(q):Q,R=O?"Log into account for Cloud agents":C?`${C.used}/${C.limit===null?"Unlimited":C.limit}`:null;return e.jsxs("section",{className:r.agentsModuleRoot,children:[e.jsx("div",{className:r.agentsModuleContent,children:e.jsx("div",{className:`${r.settingGroup} ${r.agentsModuleGroup}`,children:e.jsxs("div",{children:[e.jsx("h4",{className:r.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${r.settingsHint} ${r.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history."}),R&&e.jsx("div",{className:r.aiProfileSeatSummary,children:O?R:`Registered Cloud Agents: ${R}`}),M&&e.jsxs("div",{className:r.settingsHint,children:[e.jsx(ue,{size:13,className:r.spinner})," Loading profiles…"]}),!M&&S.length===0&&e.jsx("div",{className:r.settingsHint,children:"No AI profiles registered yet."}),!M&&k.length>0&&e.jsxs("div",{className:r.aiProfilesExplorer,children:[e.jsx("div",{className:`${r.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.aiProfilesList,children:k.map(a=>e.jsxs("div",{className:r.aiProfilesSection,children:[e.jsx("div",{className:r.aiProfilesSectionHeader,children:a.label}),a.groups.map(i=>{const t=o?.groupId===i.groupId,s=i.primaryProfile;return e.jsxs("div",{role:"button",tabIndex:0,className:`${r.aiProfileGroup} ${i.profiles.length>1?r.aiProfileGroupDuplicate:""} ${t?r.aiProfileGroupSelected:""}`,onClick:()=>b(i.groupId),onKeyDown:n=>{n.key!=="Enter"&&n.key!==" "||(n.preventDefault(),b(i.groupId))},"aria-pressed":t,children:[W(s.seatScope),e.jsxs("div",{className:r.aiProfileGroupHeader,children:[e.jsx("span",{className:r.aiProfileGroupAvatar,style:{color:s.color},children:s.avatarUrl?e.jsx("img",{src:s.avatarUrl,alt:""}):e.jsx(V,{size:22})}),e.jsxs("span",{className:r.aiProfileGroupIdentity,children:[e.jsx("span",{className:r.aiProfileName,children:s.name}),e.jsxs("span",{className:r.aiProfileHandle,children:["@",s.username]}),e.jsx("span",{className:r.aiProfileRole,children:s.role||"Role not set"}),i.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDuplicateBadge,children:[e.jsx(U,{size:11})," ",i.profiles.length," duplicates"]})]})]})]},i.groupId)})]},a.section))})}),o&&e.jsx("div",{className:r.aiProfileDetailPane,children:e.jsxs("div",{className:r.aiProfileDetailCard,children:[W(o.primaryProfile.seatScope,{variant:"detail"}),e.jsxs("div",{className:r.aiProfileDetailHero,children:[e.jsx(fe,{label:"Edit AI profile photo",imageUrl:o.primaryProfile.avatarUrl,fallback:e.jsx(V,{size:38}),accentColor:o.primaryProfile.color,size:176,width:153,height:207,radius:6,editBadgeSize:28,editIconSize:14,className:r.aiProfileDetailAvatar,onClick:()=>{m(null),T(o.primaryProfile.id)}}),e.jsxs("div",{className:r.aiProfileDetailHeading,children:[e.jsx("div",{className:r.aiProfileDetailTitleRow,children:e.jsx("h5",{className:r.aiProfileDetailTitle,children:o.primaryProfile.name})}),e.jsxs("div",{className:r.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:r.aiProfileDetailHandle,children:["@",o.primaryProfile.username]}),o.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDetailLinkedCount,children:[o.profiles.length," linked"]})]}),e.jsxs("div",{className:r.aiProfileDetailRole,children:["Role: ",o.primaryProfile.role||"Not set"]}),o.primaryProfile.description&&e.jsx("div",{className:r.aiProfileDetailDescription,children:o.primaryProfile.description})]})]}),e.jsxs("div",{className:r.aiProfileDetailDataList,children:[e.jsx("div",{className:r.aiProfileDetailDataGroup,children:H.attributes.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))}),e.jsx("div",{className:`${r.aiProfileDetailDataGroup} ${r.aiProfileDetailDateGroup}`,children:H.dates.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Status"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent roster status",value:o.primaryProfile.archivedAt?"retired":"active",onChange:i=>{i.target.value==="retire"&&G(o.primaryProfile)},children:[e.jsx("option",{value:"active",children:"Active"}),o.primaryProfile.archivedAt?e.jsx("option",{value:"retired",children:"Retired"}):e.jsx("option",{value:"retire",children:"Retire from roster"})]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))})]}),le?e.jsxs("div",{className:r.aiProfileInstanceSection,children:[e.jsxs("div",{className:r.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:r.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:r.aiProfileInstanceList,children:o.profiles.map(a=>e.jsxs("div",{className:r.aiProfileInstanceCard,children:[e.jsxs("div",{className:r.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:r.aiProfileInstanceName,children:["@",a.username]}),e.jsx("div",{className:r.aiProfileIdChip,children:a.id})]}),f?.keepId===a.id&&e.jsx("span",{className:r.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:r.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",y(a.createdAt)]}),e.jsxs("span",{children:["Updated ",y(a.updatedAt)]})]}),f?.keepId!==a.id&&e.jsx("button",{className:r.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const i=o.profiles.find(t=>t.id!==a.id)?.id;i&&j({keepId:a.id,mergeId:i})},children:"Keep this"}),N?.profileId===a.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(U,{size:12}),e.jsx("span",{children:N.message})]}),e.jsx("button",{className:r.aiProfileDangerTextButton,title:"Remove this profile from the active roster",onClick:()=>{G(a)},children:"Remove from roster"})]},a.id))}),f&&o.profiles.some(a=>a.id===f.keepId)&&e.jsxs("div",{className:r.aiProfileMergeActions,children:[e.jsx("button",{className:r.dangerBtn,onClick:()=>{ee(f.keepId,f.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:r.secondaryHeaderBtn,onClick:()=>j(null),children:"Cancel"})]})]}):e.jsxs("div",{className:r.aiProfileIdFooter,children:[e.jsx("div",{className:r.aiProfileIdFooterValue,children:o.primaryProfile.id}),N?.profileId===o.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(U,{size:12}),e.jsx("span",{children:N.message})]})]})]})})]}),I&&e.jsxs("div",{className:`${I.type==="success"?r.successMessage:r.errorMessage} ${r.marginTop12}`,children:[I.type==="success"?e.jsx(he,{size:14}):e.jsx(U,{size:14}),I.message]})]})})}),e.jsx(me,{isOpen:!!p,theme:"dark",title:"Edit AI Profile Photo",currentImageUrl:p?.avatarUrl||"",editorImageUrl:p?.avatarSourceUrl||"",fallbackInitial:(p?.name||"AI").charAt(0).toUpperCase(),accept:"image/png,image/jpeg,image/webp,image/gif",busy:B,hasPendingImage:!1,canRemove:!!p?.avatarUrl,error:Z,notice:null,onClose:()=>{B||(T(null),m(null))},onApplyImage:ie,onRemoveImage:te})]})}export{Ie as AgentsModule};
@@ -1 +0,0 @@
1
- ._shell_sf7jc_1{align-self:stretch;width:100%;box-sizing:border-box;display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr) minmax(280px,340px);gap:16px;height:100%;min-height:0;padding:16px;background:radial-gradient(circle at top left,color-mix(in srgb,var(--brand-primary) 8%,transparent),transparent 26%),linear-gradient(180deg,color-mix(in srgb,var(--surface-page) 84%,var(--surface-panel)) 0%,var(--surface-page) 100%)}._panel_sf7jc_16{min-height:0;border-radius:18px;box-shadow:0 18px 38px #0f172a14}._sessionPanel_sf7jc_22,._detailPanel_sf7jc_23{display:flex;flex-direction:column;overflow:hidden}._sessionContextBar_sf7jc_29{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:58px;padding:12px 16px;border-bottom:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 88%,transparent)}._sessionContextLeft_sf7jc_40,._sessionContextRight_sf7jc_41{display:flex;align-items:center;min-width:0}._sessionContextLeft_sf7jc_40{flex:1}._sessionContextRight_sf7jc_41{justify-content:flex-end;min-width:118px}._sessionContextLabel_sf7jc_56{font-size:.84rem;font-weight:600;color:var(--text-secondary)}._sessionContextSpacer_sf7jc_62{width:118px;height:32px}._canvasPanel_sf7jc_67{display:flex;flex-direction:column;min-height:0;overflow:hidden}._canvasWorkspace_sf7jc_74{display:grid;grid-template-columns:auto minmax(0,1fr);min-height:0;flex:1}._canvasMain_sf7jc_81{display:flex;flex-direction:column;min-width:0;min-height:0}._panelHeader_sf7jc_88{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border-default)}._panelHeaderText_sf7jc_97{min-width:0}._panelTitle_sf7jc_101{font-size:1rem}._canvasHeading_sf7jc_105{min-width:0}._sessionActions_sf7jc_109{display:inline-flex;align-items:center;gap:8px;margin-left:auto;flex-shrink:0}._sessionList_sf7jc_117,._annotationList_sf7jc_118{display:flex;flex:1 1 auto;flex-direction:column;gap:10px;min-height:0;padding:14px;overflow:auto;scrollbar-gutter:stable}._annotationList_sf7jc_118{gap:12px}._markerHelpModalBody_sf7jc_133{display:flex;flex-direction:column;gap:14px}._openImageModalBody_sf7jc_139{display:flex;flex-direction:column;gap:12px}._openImageField_sf7jc_145{width:100%}._openImageActions_sf7jc_149{display:flex;justify-content:flex-end;gap:8px}._markerHelpItem_sf7jc_155{display:flex;flex-direction:column;gap:6px;padding:12px 14px;border-radius:14px}._markerHelpHeader_sf7jc_163{display:flex;align-items:baseline;justify-content:space-between;gap:12px}._markerHelpItem_sf7jc_155 p{margin:0;line-height:1.45}._markerHelpExample_sf7jc_175{font-weight:600}._sessionCard_sf7jc_179,._annotationCard_sf7jc_180{display:flex;flex-direction:column;gap:8px;padding:12px;border-radius:14px;text-align:left;transition:border-color var(--transition-base),box-shadow var(--transition-base),transform var(--transition-base)}._sessionCard_sf7jc_179{gap:10px}._sessionEmptyState_sf7jc_197{display:flex;flex-direction:column;gap:8px;padding:8px 2px 0}._sessionCardButton_sf7jc_204,._annotationCardButton_sf7jc_214{display:flex;flex-direction:column;gap:8px;padding:0;border:0;background:transparent;text-align:left}._sessionCardBody_sf7jc_224{display:flex;flex-direction:column;gap:8px}._sessionCardActive_sf7jc_230,._annotationCardActive_sf7jc_231{border-color:var(--button-tile-active-border);box-shadow:0 0 0 1px transparent,0 12px 28px var(--brand-primary-glow-soft)}._sessionCard_sf7jc_179:hover,._annotationCard_sf7jc_180:hover{transform:translateY(-1px)}._annotationMeta_sf7jc_241{display:flex;align-items:center;justify-content:space-between;gap:8px}._annotationInstructionPreview_sf7jc_248{display:-webkit-box;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.45}._annotationPreviewFooter_sf7jc_256{display:flex;align-items:center;justify-content:flex-start;min-height:16px}._annotationInstructionEditor_sf7jc_263,._annotationTypeField_sf7jc_269{display:flex;flex-direction:column;gap:8px}._annotationInstructionButton_sf7jc_275{display:block;width:100%;padding:0;border:0;background:transparent;text-align:left}._annotationInstructionField_sf7jc_284{min-height:92px}._sessionMeta_sf7jc_288{display:flex;flex-direction:column;align-items:flex-start;gap:4px}._sessionTitle_sf7jc_295,._annotationTitle_sf7jc_296{font-size:.98rem;font-weight:700;color:var(--text-primary)}._sessionTimestamp_sf7jc_302,._annotationKind_sf7jc_303{text-transform:capitalize}._annotationInstructionTypeIcon_sf7jc_307{display:inline-flex;align-items:center;justify-content:center}._sessionInstructionPreview_sf7jc_313{line-height:1.45;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}._sessionInstructionEditor_sf7jc_321{display:flex;flex-direction:column;gap:8px}._sessionCardFooter_sf7jc_327{display:flex;align-items:center;justify-content:flex-end;gap:8px}._toolRail_sf7jc_334{display:flex;flex-wrap:wrap;gap:8px;padding:0;border-bottom:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 92%,transparent)}._canvasToolRail_sf7jc_343{display:flex;flex-direction:column;align-items:center;gap:8px;padding:0;border-right:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 94%,transparent)}._toolGroup_sf7jc_353{display:inline-flex;flex-wrap:wrap;gap:8px;align-items:center;padding:4px;border:1px solid var(--border-default);border-radius:16px;background:var(--surface-panel)}._toolbarCluster_sf7jc_364{display:inline-flex;flex-wrap:wrap;gap:8px;align-items:center}._toolbarViewportCluster_sf7jc_371{margin-left:44px}._toolbarSeparator_sf7jc_375{width:1px;align-self:stretch;background:var(--border-default)}._toolBtn_sf7jc_381{white-space:nowrap}._toolRailButton_sf7jc_385{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border-radius:14px}._toolbarButton_sf7jc_395{min-height:44px;border-radius:14px}._toolBtnActive_sf7jc_400{background:var(--button-tile-active-bg);border-color:var(--button-tile-active-border);color:var(--button-tile-active-text);box-shadow:0 0 0 1px transparent,0 8px 24px var(--brand-primary-glow-soft)}._toolbarActions_sf7jc_407{display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px;flex:1 1 auto;min-width:0}._toolbarSelectionActions_sf7jc_416{display:inline-flex;align-items:center;gap:8px;min-width:44px;flex-wrap:wrap}._toolbarColorPicker_sf7jc_424{position:relative}._colorPickerButton_sf7jc_428{min-width:44px;padding:0}._colorPickerSwatch_sf7jc_433{width:24px;height:24px;border-radius:var(--radius-sm);border:2px solid color-mix(in srgb,var(--surface-panel) 88%,transparent);box-shadow:inset 0 0 0 1px #0f172a1f}._colorPickerPopover_sf7jc_441{position:absolute;top:calc(100% + 8px);left:0;z-index:3;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:10px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-panel);box-shadow:0 18px 32px #0f172a24}._colorOption_sf7jc_456{width:24px;height:24px;padding:0;border:2px solid transparent;border-radius:var(--radius-sm);cursor:pointer;box-shadow:inset 0 0 0 1px #0f172a24}._colorOptionActive_sf7jc_466{border-color:color-mix(in srgb,var(--surface-panel) 78%,var(--brand-primary));box-shadow:inset 0 0 0 1px #0f172a24,0 0 0 2px color-mix(in srgb,var(--brand-primary) 24%,transparent)}._toolbarUtilities_sf7jc_473{margin-left:auto;display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px}._toolbarGeometryFields_sf7jc_481{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}._toolbarGeometryField_sf7jc_481{display:inline-flex;align-items:center;gap:6px}._toolbarGeometryLabel_sf7jc_494{margin:0;text-transform:uppercase;white-space:nowrap}._toolbarGeometryInput_sf7jc_500{width:68px;text-align:right}._iconButton_sf7jc_505{flex-shrink:0}._primaryBtn_sf7jc_509,._ghostBtn_sf7jc_510,._payloadBtn_sf7jc_511,._backToTaskBtn_sf7jc_512{white-space:nowrap}._backToTaskBtn_sf7jc_512{padding:7px 10px;color:var(--text-secondary);font-size:.88rem;font-weight:600}._backToTaskBtn_sf7jc_512:hover,._backToTaskBtn_sf7jc_512:focus-visible{color:var(--text-primary)}._payloadBtn_sf7jc_511{font-weight:700}._canvasScroller_sf7jc_532{position:relative;flex:1;min-height:0;overflow:auto;display:block;scrollbar-gutter:stable;background:linear-gradient(45deg,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 25%,transparent 25%),linear-gradient(-45deg,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 75%),linear-gradient(-45deg,transparent 75%,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 75%);background-size:22px 22px;background-position:0 0,0 11px,11px -11px,-11px 0}._canvasFrame_sf7jc_548{position:relative;display:block;width:max-content;min-width:100%}._canvasMedia_sf7jc_555{position:relative;display:block;flex-shrink:0;margin:0 auto;overflow:hidden}._canvasStatusOverlay_sf7jc_563{position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;padding:24px;background:color-mix(in srgb,var(--surface-scrim) 68%,transparent);pointer-events:none}._canvasStatusCard_sf7jc_575{display:inline-flex;align-items:center;gap:10px;padding:12px 16px;border:1px solid color-mix(in srgb,var(--border-strong) 44%,white);border-radius:14px;background:#0f172ac7;color:#fff;box-shadow:0 18px 30px #0f172a38;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}._canvasImage_sf7jc_588{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}._overlay_sf7jc_595{position:absolute;inset:0;cursor:crosshair}._overlaySelect_sf7jc_601{cursor:default}._overlayPan_sf7jc_605{cursor:grab}._overlaySvg_sf7jc_609{position:absolute;inset:0;display:block;width:100%;height:100%;pointer-events:none}._overlayHitLayer_sf7jc_618{position:absolute;inset:0;display:block;width:100%;height:100%;pointer-events:auto}._arrowHitArea_sf7jc_627{stroke:transparent;stroke-width:24;pointer-events:stroke;cursor:move}._canvasHandleHit_sf7jc_634{fill:transparent;pointer-events:all;cursor:grab;outline:none}._canvasResizeHandleHit_sf7jc_641{cursor:nwse-resize}._canvasHandleVisible_sf7jc_645{--canvas-handle-fill: var(--brand-primary);--canvas-handle-outline: var(--surface-elevated);--canvas-handle-shadow: 0 6px 12px rgba(15, 23, 42, .18);fill:var(--canvas-handle-fill);stroke:var(--canvas-handle-outline);stroke-width:3;vector-effect:non-scaling-stroke;filter:drop-shadow(var(--canvas-handle-shadow));pointer-events:none}._canvasHandleHit_sf7jc_634:focus-visible+._canvasHandleVisible_sf7jc_645{stroke:color-mix(in srgb,var(--brand-primary) 72%,var(--surface-elevated));stroke-width:4}._pin_sf7jc_662,._note_sf7jc_663{position:absolute;transform:translate(-50%,-50%);display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;border:2px solid var(--surface-elevated);border-radius:999px;box-shadow:0 8px 18px #0f172a38;color:#fff;font-size:.74rem;font-weight:800;pointer-events:auto}._annotationNumberBadge_sf7jc_680{position:absolute;display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;padding:0 8px;border:2px solid var(--surface-elevated);border-radius:999px;box-shadow:0 8px 18px #0f172a38;color:#fff;font-size:.74rem;font-weight:800;line-height:1;pointer-events:none;z-index:2}._note_sf7jc_663{min-width:38px;padding:0 10px;border-radius:999px}._box_sf7jc_705{position:absolute;border:2px solid;border-radius:var(--radius-sm);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--surface-elevated) 34%,transparent);background:color-mix(in srgb,var(--surface-elevated) 10%,transparent);pointer-events:auto}._boxNumberBadge_sf7jc_714{top:0;left:0;transform:translate(-28%,-36%)}._arrowNumberBadge_sf7jc_720{transform:translate(-50%,-50%)}._boxSurface_sf7jc_724{position:absolute;inset:0;border:0;background:transparent;cursor:move}._boxSurface_sf7jc_724:focus-visible{outline:2px solid color-mix(in srgb,var(--brand-primary) 34%,var(--surface-elevated));outline-offset:2px}._selected_sf7jc_737{outline:3px solid color-mix(in srgb,var(--surface-elevated) 92%,transparent);outline-offset:1px}._fieldGroup_sf7jc_742{display:flex;flex-direction:column;gap:8px;padding:14px 16px}._detailPanel_sf7jc_23 ._fieldGroup_sf7jc_742{padding-top:12px;padding-bottom:12px}._textInput_sf7jc_754,._textArea_sf7jc_755,._select_sf7jc_737{width:100%;box-sizing:border-box;font:inherit}._sessionTitleInput_sf7jc_762{font-size:1rem;font-weight:600}._sessionInstructionField_sf7jc_767{min-height:112px}._textArea_sf7jc_755{min-height:110px;resize:vertical}._detailEmpty_sf7jc_776,._emptyState_sf7jc_777,._errorState_sf7jc_778{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;min-height:240px;padding:24px;text-align:center}._payloadModalBody_sf7jc_789{display:flex;flex-direction:column;gap:12px;min-height:0}._payloadModalToolbar_sf7jc_796{display:flex;align-items:center;justify-content:space-between;gap:12px}._payloadViewToggle_sf7jc_803,._payloadModalActions_sf7jc_804{display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px}._payloadModalPreview_sf7jc_811{margin:0;min-height:360px;max-height:min(70vh,720px);padding:16px;overflow:auto;scrollbar-gutter:stable;white-space:pre-wrap;word-break:break-word;font-size:.78rem;line-height:1.55;border-radius:14px;color:var(--text-body)}._statusBar_sf7jc_826{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-top:1px solid var(--border-default);color:var(--text-secondary);font-size:.82rem;background:color-mix(in srgb,var(--surface-panel) 88%,var(--surface-inset))}._annotationSummary_sf7jc_838{font-size:.82rem;line-height:1.5}@media(max-width:1180px){._shell_sf7jc_1{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(320px,1fr) auto;gap:12px;padding:12px}._sessionContextBar_sf7jc_29,._panelHeader_sf7jc_88,._toolRail_sf7jc_334,._statusBar_sf7jc_826,._payloadModalToolbar_sf7jc_796,._markerHelpHeader_sf7jc_163{flex-wrap:wrap}._sessionContextRight_sf7jc_41,._sessionContextLeft_sf7jc_40,._canvasHeading_sf7jc_105{min-width:0}._sessionContextRight_sf7jc_41{width:100%;justify-content:flex-start}._toolbarActions_sf7jc_407{width:100%;margin-left:0}._toolbarUtilities_sf7jc_473{margin-left:0}._toolGroup_sf7jc_353{max-width:100%}._canvasWorkspace_sf7jc_74{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}._toolbarViewportCluster_sf7jc_371{margin-left:0}._canvasToolRail_sf7jc_343{flex-direction:row;justify-content:flex-start;overflow-x:auto;padding:0;border-right:0;border-bottom:1px solid var(--border-default)}._statusBar_sf7jc_826{align-items:flex-start}._detailEmpty_sf7jc_776,._emptyState_sf7jc_777,._errorState_sf7jc_778{min-height:180px}}