@taskforcehq/taskforce 0.3.324 → 0.3.326

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 (64) hide show
  1. package/dist/Taskforce.module.css +39 -0
  2. package/dist/components/features/TaskSettings.js +26 -9
  3. package/dist/components/task/TaskKanban.d.ts +2 -2
  4. package/dist/components/views/StandaloneLayout.js +22 -22
  5. package/dist/components/views/panels/PlanningDrawer.d.ts +2 -2
  6. package/dist/components/views/panels/PlanningDrawer.js +26 -12
  7. package/dist/components/views/planningScope.d.ts +4 -0
  8. package/dist/components/views/planningScope.js +5 -0
  9. package/dist/core/AiProfileService.js +6 -1
  10. package/dist/hooks/useTaskforce.js +74 -17
  11. package/dist/hooks/workspace/workspaceLocalState.d.ts +25 -0
  12. package/dist/hooks/workspace/workspaceLocalState.js +38 -0
  13. package/dist/mcp/adminWorkspaceRegistrar.js +0 -10
  14. package/dist/mcp/clientIntegrations.d.ts +1 -1
  15. package/dist/mcp/clientIntegrations.js +50 -0
  16. package/dist/mcp/clientProfileDefaults.js +4 -0
  17. package/dist/mcp/collaborationRegistrar.js +3 -3
  18. package/dist/mcp/documentAssetRegistrar.js +24 -72
  19. package/dist/mcp/documentHelpers.d.ts +11 -4
  20. package/dist/mcp/documentHelpers.js +21 -17
  21. package/dist/mcp/runtime.d.ts +4 -0
  22. package/dist/mcp/runtime.js +466 -534
  23. package/dist/mcp/taskPlanningRegistrar.js +45 -81
  24. package/dist/mcp/toolProfiles.d.ts +7 -0
  25. package/dist/mcp/toolProfiles.js +36 -0
  26. package/dist/mcp/toolRegistry.d.ts +87 -609
  27. package/dist/mcp/toolRegistry.js +93 -88
  28. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +10 -10
  29. package/dist/resources/templates/workflows/collaborate.yaml +2 -3
  30. package/dist/resources/templates/workflows/do.yaml +2 -3
  31. package/dist/resources/templates/workflows/evaluate.yaml +2 -3
  32. package/dist/resources/templates/workflows/execute.yaml +12 -15
  33. package/dist/resources/templates/workflows/plan.yaml +3 -4
  34. package/dist/resources/templates/workflows/review.yaml +2 -3
  35. package/dist/shared/taxonomyLibrary.js +1 -1
  36. package/dist/sync/cloudSyncApi.js +22 -3
  37. package/dist/types.js +1 -1
  38. package/dist/ui/assets/{AgentsModule-DvkbUHi0.js → AgentsModule-C4LcqhcZ.js} +1 -1
  39. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jhOhxqHT.js → AnnotatedAttachmentWorkspace-B-Dq22d2.js} +1 -1
  40. package/dist/ui/assets/{ContextAttachmentManager-BAmEyLHL.js → ContextAttachmentManager-DsXPTvi-.js} +1 -1
  41. package/dist/ui/assets/{DocumentWorkspace-X-CpD5OO.js → DocumentWorkspace-CxA1wO3U.js} +1 -1
  42. package/dist/ui/assets/{EntityActivityTimeline-DD33onAC.js → EntityActivityTimeline-B8nSe8l3.js} +1 -1
  43. package/dist/ui/assets/{InitiativesModule-5nDXtIjf.js → InitiativesModule-VY95v38E.js} +1 -1
  44. package/dist/ui/assets/{PlansPage-B8nDR1hQ.js → PlansPage-CQbKDv1a.js} +1 -1
  45. package/dist/ui/assets/{TaskContextUpload-DrGrXJHm.js → TaskContextUpload-DvDlNpHC.js} +1 -1
  46. package/dist/ui/assets/TaskSettings-CUWbYYtP.js +12 -0
  47. package/dist/ui/assets/{WorkflowsModule-DWCGhEWT.js → WorkflowsModule-CgM3Jako.js} +1 -1
  48. package/dist/ui/assets/documentReferences-Do9YjrS3.js +1 -0
  49. package/dist/ui/assets/index--mh3pktj.js +5 -0
  50. package/dist/ui/assets/index-BawaaEXS.css +1 -0
  51. package/dist/ui/index.html +2 -2
  52. package/dist/utils/theme.js +1 -1
  53. package/package.json +1 -1
  54. package/src/resources/templates/workflow-sources/workflowDocs.mjs +10 -10
  55. package/src/resources/templates/workflows/collaborate.yaml +2 -3
  56. package/src/resources/templates/workflows/do.yaml +2 -3
  57. package/src/resources/templates/workflows/evaluate.yaml +2 -3
  58. package/src/resources/templates/workflows/execute.yaml +12 -15
  59. package/src/resources/templates/workflows/plan.yaml +3 -4
  60. package/src/resources/templates/workflows/review.yaml +2 -3
  61. package/dist/ui/assets/TaskSettings-BXVvZZwH.js +0 -9
  62. package/dist/ui/assets/documentReferences-Crup321p.js +0 -1
  63. package/dist/ui/assets/index-B9k24Aym.js +0 -5
  64. package/dist/ui/assets/index-DNeR5dYW.css +0 -1
@@ -62,7 +62,7 @@ import { useTaskFilters } from './tasks/useTaskFilters';
62
62
  import { useTaskFormActions } from './tasks/useTaskFormActions';
63
63
  import { useTaskRelationships } from './tasks/useTaskRelationships';
64
64
  import { useTaskActions } from './tasks/useTaskActions';
65
- import { clearPersistedWorkspaceId, logBootstrapDebug, persistRemoteUiState, readPersistedAppUiState, readPersistedWorkspaceId, writePersistedAppUiState, writePersistedWorkspaceId } from './workspace/workspaceLocalState';
65
+ import { areFilterTaxonomySignaturesEqual, buildFilterTaxonomySignature, clearPersistedWorkspaceId, logBootstrapDebug, persistRemoteUiState, readPersistedAppUiState, readPersistedWorkspaceId, writePersistedAppUiState, writePersistedWorkspaceId } from './workspace/workspaceLocalState';
66
66
  import { useTaskforceApiRouting } from './workspace/useTaskforceApiRouting';
67
67
  import { useTaskforceAuthBootstrap } from './auth/useTaskforceAuthBootstrap';
68
68
  import { deriveAuthGuardPolicy } from './auth/useAuthGuardPolicy';
@@ -442,6 +442,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
442
442
  const authSessionLastCheckedAtRef = useRef(0);
443
443
  const authSessionLastResultRef = useRef(hasOptimisticLocalAuth);
444
444
  const currentWorkspaceIdRef = useRef(currentWorkspaceId);
445
+ const pendingPersistedTaxonomyUiStateRef = useRef(null);
445
446
  const explicitWorkspaceSelectionRef = useRef(null);
446
447
  const workspaceTransitionEpochRef = useRef(0);
447
448
  const bootstrapTraceSeedRef = useRef((() => {
@@ -1238,9 +1239,26 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1238
1239
  authRequiredForApiRef.current = authRequiredForApi;
1239
1240
  isAuthenticatedRef.current = isAuthenticated;
1240
1241
  }, [authSessionResolved, authRequiredForApi, isAuthenticated]);
1242
+ const currentFilterTaxonomySignature = useMemo(() => (referenceDataLoaded
1243
+ ? buildFilterTaxonomySignature({
1244
+ categories: activeCategories,
1245
+ types: activeTypes,
1246
+ priorities,
1247
+ taxonomies
1248
+ })
1249
+ : null), [activeCategories, activeTypes, priorities, referenceDataLoaded, taxonomies]);
1241
1250
  const applyPersistedUiState = useCallback((state, options) => {
1242
- if (!state)
1251
+ if (!state) {
1252
+ pendingPersistedTaxonomyUiStateRef.current = null;
1243
1253
  return;
1254
+ }
1255
+ const hasPersistedTaxonomyFilterState = Array.isArray(state.filterCategories)
1256
+ || Array.isArray(state.filterPriorities)
1257
+ || Array.isArray(state.filterTypes)
1258
+ || (state.filterTaxonomies && typeof state.filterTaxonomies === 'object');
1259
+ if (!hasPersistedTaxonomyFilterState) {
1260
+ pendingPersistedTaxonomyUiStateRef.current = null;
1261
+ }
1244
1262
  const hasPersistedFilterState = Array.isArray(state.filterCategories)
1245
1263
  || Array.isArray(state.filterPriorities)
1246
1264
  || Array.isArray(state.filterTypes)
@@ -1248,6 +1266,20 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1248
1266
  || Array.isArray(state.filterAssignees)
1249
1267
  || typeof state.filterAssigneesAllSelected === 'boolean'
1250
1268
  || (state.filterTaxonomies && typeof state.filterTaxonomies === 'object');
1269
+ const canCompareTaxonomySignature = currentFilterTaxonomySignature !== null;
1270
+ if (hasPersistedTaxonomyFilterState && !canCompareTaxonomySignature) {
1271
+ pendingPersistedTaxonomyUiStateRef.current = {
1272
+ state,
1273
+ skipActiveWorkspaceModule: options?.skipActiveWorkspaceModule
1274
+ };
1275
+ }
1276
+ const canApplyTaxonomyFilters = hasPersistedTaxonomyFilterState
1277
+ && canCompareTaxonomySignature
1278
+ && areFilterTaxonomySignaturesEqual(state.filterTaxonomySignature, currentFilterTaxonomySignature);
1279
+ const shouldResetTaxonomyFilters = hasPersistedTaxonomyFilterState
1280
+ && canCompareTaxonomySignature
1281
+ && !canApplyTaxonomyFilters;
1282
+ const taxonomyFiltersAreReady = !hasPersistedTaxonomyFilterState || canApplyTaxonomyFilters || shouldResetTaxonomyFilters;
1251
1283
  if (!options?.skipActiveWorkspaceModule) {
1252
1284
  if (typeof state.activeWorkspaceModule === 'string') {
1253
1285
  setActiveWorkspaceModuleState(state.activeWorkspaceModule);
@@ -1264,28 +1296,39 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1264
1296
  setZenModeState(state.zenMode);
1265
1297
  if (typeof state.searchQuery === 'string')
1266
1298
  setSearchQuery(state.searchQuery);
1267
- if (Array.isArray(state.filterCategories))
1268
- setFilterCategories(state.filterCategories);
1269
- if (Array.isArray(state.filterPriorities))
1270
- setFilterPriorities(state.filterPriorities);
1271
- if (Array.isArray(state.filterTypes))
1272
- setFilterTypes(state.filterTypes);
1299
+ if (canApplyTaxonomyFilters) {
1300
+ if (Array.isArray(state.filterCategories))
1301
+ setFilterCategories(state.filterCategories);
1302
+ if (Array.isArray(state.filterPriorities))
1303
+ setFilterPriorities(state.filterPriorities);
1304
+ if (Array.isArray(state.filterTypes))
1305
+ setFilterTypes(state.filterTypes);
1306
+ if (state.filterTaxonomies && typeof state.filterTaxonomies === 'object')
1307
+ setFilterTaxonomies(state.filterTaxonomies);
1308
+ }
1309
+ else if (shouldResetTaxonomyFilters) {
1310
+ setFilterCategories(activeCategories.map((category) => category.value));
1311
+ setFilterPriorities(priorities.map((priority) => priority.value));
1312
+ setFilterTypes(activeTypes.map((type) => type.value));
1313
+ setFilterTaxonomies({});
1314
+ }
1273
1315
  if (Array.isArray(state.filterStatus))
1274
1316
  setFilterStatus(state.filterStatus);
1275
1317
  if (Array.isArray(state.filterAssignees))
1276
1318
  setFilterAssignees(state.filterAssignees);
1277
1319
  if (typeof state.filterAssigneesAllSelected === 'boolean')
1278
1320
  setFilterAssigneesAllSelected(state.filterAssigneesAllSelected);
1279
- if (state.filterTaxonomies && typeof state.filterTaxonomies === 'object')
1280
- setFilterTaxonomies(state.filterTaxonomies);
1281
1321
  if (state.sortBy === 'created' || state.sortBy === 'priority' || state.sortBy === 'updated' || state.sortBy === 'complexity')
1282
1322
  setSortBy(state.sortBy);
1283
1323
  if (state.sortOrder === 'asc' || state.sortOrder === 'desc')
1284
1324
  setSortOrder(state.sortOrder);
1285
- if (typeof state.hasInitedFilters === 'boolean') {
1325
+ if (shouldResetTaxonomyFilters) {
1326
+ setHasInitedFilters(true);
1327
+ }
1328
+ else if (typeof state.hasInitedFilters === 'boolean' && taxonomyFiltersAreReady) {
1286
1329
  setHasInitedFilters(state.hasInitedFilters);
1287
1330
  }
1288
- else if (hasPersistedFilterState) {
1331
+ else if (hasPersistedFilterState && taxonomyFiltersAreReady) {
1289
1332
  setHasInitedFilters(true);
1290
1333
  }
1291
1334
  if (typeof state.showChecklist === 'boolean')
@@ -1295,7 +1338,16 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1295
1338
  if (typeof state.exportEnvironment === 'string' && state.exportEnvironment.trim().length > 0) {
1296
1339
  setExportEnvironment(state.exportEnvironment.trim());
1297
1340
  }
1298
- }, []);
1341
+ }, [activeCategories, activeTypes, currentFilterTaxonomySignature, priorities]);
1342
+ useEffect(() => {
1343
+ if (!currentFilterTaxonomySignature || !pendingPersistedTaxonomyUiStateRef.current)
1344
+ return;
1345
+ const pending = pendingPersistedTaxonomyUiStateRef.current;
1346
+ pendingPersistedTaxonomyUiStateRef.current = null;
1347
+ applyPersistedUiState(pending.state, {
1348
+ skipActiveWorkspaceModule: pending.skipActiveWorkspaceModule
1349
+ });
1350
+ }, [applyPersistedUiState, currentFilterTaxonomySignature]);
1299
1351
  const loadPersistedUiState = useCallback(async (workspaceIdOverride) => {
1300
1352
  const targetWorkspaceId = String(workspaceIdOverride || currentWorkspaceIdRef.current || currentWorkspaceId || 'default').trim() || 'default';
1301
1353
  const uiStateLoadKey = `${runtimeMode}:${workspaceUiStateScope}:${targetWorkspaceId}`;
@@ -1369,6 +1421,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1369
1421
  filterAssignees,
1370
1422
  filterAssigneesAllSelected,
1371
1423
  filterTaxonomies,
1424
+ ...(currentFilterTaxonomySignature ? { filterTaxonomySignature: currentFilterTaxonomySignature } : {}),
1372
1425
  sortBy,
1373
1426
  sortOrder,
1374
1427
  hasInitedFilters,
@@ -1404,6 +1457,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1404
1457
  filterAssignees,
1405
1458
  filterAssigneesAllSelected,
1406
1459
  filterTaxonomies,
1460
+ currentFilterTaxonomySignature,
1407
1461
  sortBy,
1408
1462
  sortOrder,
1409
1463
  hasInitedFilters,
@@ -2557,6 +2611,9 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2557
2611
  setInitiatives((current) => upsertEntityById(current, data));
2558
2612
  return data;
2559
2613
  }, [fetchPlanningEntities]);
2614
+ const refreshPlanningEntitiesInBackground = useCallback(() => {
2615
+ void fetchPlanningEntities();
2616
+ }, [fetchPlanningEntities]);
2560
2617
  const archiveInitiative = useCallback(async (id) => {
2561
2618
  const res = await fetch(`/api/taskforce/initiative/${encodeURIComponent(id)}/archive`, {
2562
2619
  method: 'POST',
@@ -2565,10 +2622,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2565
2622
  if (!res.ok) {
2566
2623
  throw new Error(data?.error || `Failed to archive initiative (${res.status})`);
2567
2624
  }
2568
- await fetchPlanningEntities();
2569
2625
  setInitiatives((current) => upsertEntityById(current, data));
2626
+ refreshPlanningEntitiesInBackground();
2570
2627
  return data;
2571
- }, [fetchPlanningEntities]);
2628
+ }, [refreshPlanningEntitiesInBackground]);
2572
2629
  const unarchiveInitiative = useCallback(async (id) => {
2573
2630
  const res = await fetch(`/api/taskforce/initiative/${encodeURIComponent(id)}/unarchive`, {
2574
2631
  method: 'POST',
@@ -2577,10 +2634,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2577
2634
  if (!res.ok) {
2578
2635
  throw new Error(data?.error || `Failed to unarchive initiative (${res.status})`);
2579
2636
  }
2580
- await fetchPlanningEntities();
2581
2637
  setInitiatives((current) => upsertEntityById(current, data));
2638
+ refreshPlanningEntitiesInBackground();
2582
2639
  return data;
2583
- }, [fetchPlanningEntities]);
2640
+ }, [refreshPlanningEntitiesInBackground]);
2584
2641
  const createWorkstream = useCallback(async (payload) => {
2585
2642
  const res = await fetch('/api/taskforce/workstream', {
2586
2643
  method: 'POST',
@@ -1,5 +1,11 @@
1
1
  import type { TaskAssignee } from '../../types';
2
2
  import type { TaskSortKey } from '../../utils/taxonomySorting.js';
3
+ export interface FilterTaxonomySignature {
4
+ categories: string[];
5
+ types: string[];
6
+ priorities: string[];
7
+ taxonomies: Record<string, string[]>;
8
+ }
3
9
  export interface PersistedUiState {
4
10
  groupBy?: 'category' | 'type' | 'priority' | 'approach' | 'assignee' | 'status' | 'complexity' | 'schedule' | 'docs';
5
11
  activeWorkspaceModule?: 'tasks' | 'docs' | 'annotate' | 'workflows' | 'agents';
@@ -13,6 +19,7 @@ export interface PersistedUiState {
13
19
  filterAssignees?: TaskAssignee[];
14
20
  filterAssigneesAllSelected?: boolean;
15
21
  filterTaxonomies?: Record<string, (string | number)[]>;
22
+ filterTaxonomySignature?: FilterTaxonomySignature;
16
23
  sortBy?: TaskSortKey;
17
24
  sortOrder?: 'asc' | 'desc';
18
25
  hasInitedFilters?: boolean;
@@ -28,6 +35,24 @@ type RemoteUiStatePersistResult = {
28
35
  response?: Response;
29
36
  payload?: any;
30
37
  };
38
+ export declare function buildFilterTaxonomySignature(input: {
39
+ categories: Array<{
40
+ value?: unknown;
41
+ }>;
42
+ types: Array<{
43
+ value?: unknown;
44
+ }>;
45
+ priorities: Array<{
46
+ value?: unknown;
47
+ }>;
48
+ taxonomies?: Array<{
49
+ id?: unknown;
50
+ options?: Array<{
51
+ value?: unknown;
52
+ }>;
53
+ }>;
54
+ }): FilterTaxonomySignature | null;
55
+ export declare function areFilterTaxonomySignaturesEqual(left: FilterTaxonomySignature | null | undefined, right: FilterTaxonomySignature | null | undefined): boolean;
31
56
  export declare function generateClientUuid(): string;
32
57
  export declare function logBootstrapDebug(event: string, details?: Record<string, unknown>): void;
33
58
  export declare function resetRemoteUiStatePersistCache(): void;
@@ -4,6 +4,44 @@ const APP_UI_STATE_STORAGE_KEY_PREFIX = 'taskforce.uiState.app.v1';
4
4
  const BOOTSTRAP_DEBUG_LOG_KEY = 'taskforce.bootstrapDebug.v1';
5
5
  const REMOTE_UI_STATE_FAILURE_RETRY_MS = 5000;
6
6
  const remoteUiStatePersistCache = new Map();
7
+ function normalizeSignatureValues(values) {
8
+ return Array.from(new Set(values
9
+ .map((value) => String(value ?? '').trim())
10
+ .filter((value) => value.length > 0))).sort();
11
+ }
12
+ export function buildFilterTaxonomySignature(input) {
13
+ const categories = normalizeSignatureValues(input.categories.map((category) => category.value));
14
+ const types = normalizeSignatureValues(input.types.map((type) => type.value));
15
+ const priorities = normalizeSignatureValues(input.priorities.map((priority) => priority.value));
16
+ if (categories.length === 0 || types.length === 0 || priorities.length === 0)
17
+ return null;
18
+ const taxonomies = {};
19
+ (input.taxonomies || []).forEach((taxonomy) => {
20
+ const id = String(taxonomy.id ?? '').trim();
21
+ if (!id)
22
+ return;
23
+ taxonomies[id] = normalizeSignatureValues((taxonomy.options || []).map((option) => option.value));
24
+ });
25
+ return { categories, types, priorities, taxonomies };
26
+ }
27
+ function haveSameStringValues(left, right) {
28
+ return left.length === right.length && left.every((value, index) => value === right[index]);
29
+ }
30
+ export function areFilterTaxonomySignaturesEqual(left, right) {
31
+ if (!left || !right)
32
+ return false;
33
+ if (!haveSameStringValues(left.categories || [], right.categories || []))
34
+ return false;
35
+ if (!haveSameStringValues(left.types || [], right.types || []))
36
+ return false;
37
+ if (!haveSameStringValues(left.priorities || [], right.priorities || []))
38
+ return false;
39
+ const leftTaxonomyKeys = Object.keys(left.taxonomies || {}).sort();
40
+ const rightTaxonomyKeys = Object.keys(right.taxonomies || {}).sort();
41
+ if (!haveSameStringValues(leftTaxonomyKeys, rightTaxonomyKeys))
42
+ return false;
43
+ return leftTaxonomyKeys.every((key) => (haveSameStringValues(left.taxonomies[key] || [], right.taxonomies[key] || [])));
44
+ }
7
45
  export function generateClientUuid() {
8
46
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
9
47
  return crypto.randomUUID();
@@ -66,16 +66,6 @@ export function registerAdminWorkspaceTools(registerTool, executeTool) {
66
66
  required: ['types'],
67
67
  },
68
68
  }));
69
- register('bulk_update_type', (context) => ({
70
- description: context.describeTool('Bulk-update tasks from one task type to another. Use get_config first to confirm the current valid type values.'),
71
- inputSchema: {
72
- properties: {
73
- fromType: stringProperty('Current task type value to replace.', context.typeEnum),
74
- toType: stringProperty('Replacement task type value.', context.typeEnum),
75
- },
76
- required: ['fromType', 'toType'],
77
- },
78
- }));
79
69
  register('bulk_update_fields', (context) => ({
80
70
  description: context.describeTool('Apply field updates to multiple tasks in one request. Use get_config first to confirm valid taxonomy values before calling this tool.'),
81
71
  inputSchema: {
@@ -1,5 +1,5 @@
1
1
  import { type AiProfileBootstrapConfig } from './aiProfileBootstrap.js';
2
- export type McpClientId = 'general' | 'cursor' | 'cline' | 'vscode' | 'windsurf' | 'antigravity' | 'openclaw' | 'codex' | 'claude-code' | 'gemini-cli' | 'claude-desktop' | 'chatgpt-desktop';
2
+ export type McpClientId = 'general' | 'cursor' | 'cline' | 'vscode' | 'kiro' | 'windsurf' | 'antigravity' | 'openclaw' | 'codex' | 'grok' | 'claude-code' | 'gemini-cli' | 'claude-desktop' | 'chatgpt-desktop';
3
3
  export interface McpClientOption {
4
4
  id: McpClientId;
5
5
  label: string;
@@ -4,10 +4,12 @@ const LOCAL_AI_PROFILE_DEFAULTS = {
4
4
  cursor: { name: 'Cursor', icon: 'Bot', role: 'IDE coding assistant', provider: 'Cursor', model: 'Cursor', surfaceType: 'ide' },
5
5
  cline: { name: 'Cline', icon: 'Bot', role: 'IDE coding assistant', provider: 'Cline', model: 'Cline', surfaceType: 'ide' },
6
6
  vscode: { name: 'VS Code Copilot', icon: 'Bot', role: 'IDE coding assistant', provider: 'Microsoft', model: 'Copilot', surfaceType: 'ide' },
7
+ kiro: { name: 'Kiro', icon: 'Bot', role: 'IDE coding assistant', provider: 'Amazon', model: 'Kiro', surfaceType: 'ide' },
7
8
  windsurf: { name: 'Windsurf', icon: 'Bot', role: 'IDE coding assistant', provider: 'Windsurf', model: 'Windsurf', surfaceType: 'ide' },
8
9
  antigravity: { name: 'Antigravity', icon: 'Bot', role: 'Coding agent', provider: 'Google', model: 'Antigravity', surfaceType: 'coding_tool' },
9
10
  openclaw: { name: 'OpenClaw', icon: 'Bot', role: 'AI agent', provider: 'OpenClaw', model: 'OpenClaw', surfaceType: 'agent' },
10
11
  codex: { name: 'Codex', icon: 'Bot', role: 'Coding agent', provider: 'OpenAI', model: 'Codex', surfaceType: 'cli' },
12
+ grok: { name: 'Grok CLI', icon: 'Bot', role: 'Coding agent', provider: 'xAI', model: 'Grok CLI', surfaceType: 'cli' },
11
13
  'claude-code': { name: 'Claude Code', icon: 'Bot', role: 'Coding agent', provider: 'Anthropic', model: 'Claude Code', surfaceType: 'cli' },
12
14
  'gemini-cli': { name: 'Gemini CLI', icon: 'Bot', role: 'Coding agent', provider: 'Google', model: 'Gemini CLI', surfaceType: 'cli' },
13
15
  'claude-desktop': { name: 'Claude Desktop', icon: 'Bot', role: 'Desktop AI collaborator', provider: 'Anthropic', model: 'Claude Desktop', surfaceType: 'chat_app' },
@@ -32,6 +34,8 @@ export const MCP_CLIENT_OPTIONS = [
32
34
  { id: 'codex', label: 'Codex' },
33
35
  { id: 'cursor', label: 'Cursor' },
34
36
  { id: 'gemini-cli', label: 'Gemini CLI' },
37
+ { id: 'grok', label: 'Grok CLI' },
38
+ { id: 'kiro', label: 'Kiro' },
35
39
  { id: 'openclaw', label: 'OpenClaw' },
36
40
  { id: 'vscode', label: 'VS Code / Copilot' },
37
41
  { id: 'windsurf', label: 'Windsurf' },
@@ -149,6 +153,18 @@ export function buildCloudMcpClientIntegration(input) {
149
153
  instructionLabel: 'VS Code Configuration Instructions',
150
154
  instructionText: buildConfigurationInstruction('Open .vscode/mcp.json or MCP: Open User Configuration and add this server under the servers object.')
151
155
  };
156
+ case 'kiro':
157
+ return {
158
+ clientId,
159
+ kind: 'json',
160
+ rootKey: 'mcpServers',
161
+ serverConfig: {
162
+ url: endpoint,
163
+ headers
164
+ },
165
+ instructionLabel: 'Kiro Configuration Instructions',
166
+ instructionText: buildConfigurationInstruction('Open .kiro/settings/mcp.json in your workspace (or ~/.kiro/settings/mcp.json for user-level config) and add this server under mcpServers.')
167
+ };
152
168
  case 'windsurf':
153
169
  return {
154
170
  clientId,
@@ -199,6 +215,18 @@ export function buildCloudMcpClientIntegration(input) {
199
215
  instructionLabel: 'Codex Configuration Instructions',
200
216
  instructionText: buildConfigurationInstruction('Open ~/.codex/config.toml and add this Codex MCP entry.')
201
217
  };
218
+ case 'grok':
219
+ return {
220
+ clientId,
221
+ kind: 'text',
222
+ renderedText: [
223
+ `[mcp_servers.${serverId}]`,
224
+ `url = "${endpoint}"`,
225
+ `headers = { ${Object.entries(headers).map(([key, value]) => `${key} = "${value}"`).join(', ')} }`
226
+ ].join('\n'),
227
+ instructionLabel: 'Grok CLI Configuration Instructions',
228
+ instructionText: buildConfigurationInstruction('Open ~/.grok/config.toml (or .grok/config.toml in your project for project-scoped config) and add this Grok CLI MCP entry.')
229
+ };
202
230
  case 'claude-code':
203
231
  return {
204
232
  clientId,
@@ -288,6 +316,15 @@ export function buildLocalMcpClientIntegration(input) {
288
316
  instructionLabel: 'VS Code Configuration Instructions',
289
317
  instructionText: buildLocalConfigurationInstruction('Open .vscode/mcp.json or MCP: Open User Configuration and add this server under the servers object.')
290
318
  };
319
+ case 'kiro':
320
+ return {
321
+ clientId,
322
+ kind: 'json',
323
+ rootKey: 'mcpServers',
324
+ serverConfig: baseStdioConfig,
325
+ instructionLabel: 'Kiro Configuration Instructions',
326
+ instructionText: buildLocalConfigurationInstruction('Open .kiro/settings/mcp.json in your workspace (or ~/.kiro/settings/mcp.json for user-level config) and add this server under mcpServers.')
327
+ };
291
328
  case 'windsurf':
292
329
  return {
293
330
  clientId,
@@ -332,6 +369,19 @@ export function buildLocalMcpClientIntegration(input) {
332
369
  instructionLabel: 'Codex Configuration Instructions',
333
370
  instructionText: buildLocalConfigurationInstruction('Open ~/.codex/config.toml and add this local MCP entry.')
334
371
  };
372
+ case 'grok':
373
+ return {
374
+ clientId,
375
+ kind: 'text',
376
+ renderedText: [
377
+ `[mcp_servers.${serverId}]`,
378
+ `command = ${formatTomlString(serverConfig.command)}`,
379
+ `args = ${formatTomlArray(serverConfig.args)}`,
380
+ ...buildTomlEnvAssignment(serverConfig.env)
381
+ ].join('\n'),
382
+ instructionLabel: 'Grok CLI Configuration Instructions',
383
+ instructionText: buildLocalConfigurationInstruction('Open ~/.grok/config.toml (or .grok/config.toml in your project for project-scoped config) and add this local Grok CLI MCP entry.')
384
+ };
335
385
  case 'claude-code':
336
386
  return {
337
387
  clientId,
@@ -16,8 +16,12 @@ export function getDefaultAiProfileMetadataForClient(clientId) {
16
16
  return { icon: 'MessageSquareMore', color: '#d97706', description: 'Claude Desktop connected through the Taskforce MCP connector.', role: 'AI Assistant', provider: 'Anthropic', model: 'Claude', surfaceType: 'chat_app' };
17
17
  case 'gemini-cli':
18
18
  return { icon: 'Gem', color: '#2563eb', description: 'Gemini CLI coding agent connected through MCP.', role: 'CLI Coding Agent', provider: 'Google', model: 'Gemini', surfaceType: 'cli' };
19
+ case 'grok':
20
+ return { icon: 'Sparkles', color: '#111827', description: 'Grok CLI coding agent connected through MCP.', role: 'CLI Coding Agent', provider: 'xAI', model: 'Grok CLI', surfaceType: 'cli' };
19
21
  case 'vscode':
20
22
  return { icon: 'Code2', color: '#2563eb', description: 'VS Code MCP-connected coding agent.', role: 'Coding Agent', provider: null, model: null, surfaceType: 'ide' };
23
+ case 'kiro':
24
+ return { icon: 'Bot', color: '#7c3aed', description: 'Kiro IDE-connected AI assistant.', role: 'IDE coding assistant', provider: 'Amazon', model: null, surfaceType: 'ide' };
21
25
  case 'cline':
22
26
  case 'windsurf':
23
27
  case 'antigravity':
@@ -23,11 +23,11 @@ export function registerCollaborationTools(registerTool, executeTool) {
23
23
  });
24
24
  };
25
25
  register('list_assignees', (context) => ({
26
- description: context.describeTool('List eligible assignees for the active workspace. Returns human workspace members and AI profiles that can own tasks. Use this before create_task, update_task, assign_task, or bulk_assign_tasks when you need a valid assignee ID.'),
26
+ description: context.describeTool('List eligible assignees for the active workspace. Returns human workspace members and AI profiles that can own tasks. Use this before create_task, update_task, or bulk_update_fields when you need a valid assignee ID.'),
27
27
  inputSchema: { properties: {} },
28
28
  }));
29
29
  register('get_current_profile', (context) => ({
30
- description: context.describeTool('Inspect the effective AI profile for this session. Use this when an agent needs to know its current profile id before filtering for tasks assigned to itself. If no profile is currently bound, this tool explains how to bootstrap with resolve_profile. You may also pass your own profileToken to inspect a stateless AI profile without mutating session state. The response includes your profileToken only for private agent/client storage; do not write it to repos, task comments, task attachments, logs, generated docs, or shared output.'),
30
+ description: context.describeTool('Inspect current AI identity for this call (ambient session, connection bootstrap, or explicit profileToken). Returns identityBound (true if this invocation has a resolved identity), bindingSource, profile, taskFilter, suggestedArgs, and guidance. Stateless/scheduled agents: call with profileToken if no ambient bind; use the returned suggestedArgs (with assignee) for list_tasks and pass profileToken on writes. Call before mutating or to decide on resolve_profile.'),
31
31
  inputSchema: {
32
32
  properties: {
33
33
  profileToken: stringProperty('Optional AI profile token to inspect when no profile is currently bound in session state. Pass only a token previously issued to this AI identity.'),
@@ -35,7 +35,7 @@ export function registerCollaborationTools(registerTool, executeTool) {
35
35
  },
36
36
  }));
37
37
  register('resolve_profile', (context) => ({
38
- description: context.describeTool('Resolve or create the current AI collaborator profile for this workspace. On first bootstrap, omit profileToken and Taskforce will create a named AI profile, issue a stable profileToken, and return the profile id to use in assignee, createdBy, and comment author fields on later calls. Store the returned profileToken only in private agent/client state and include it on future mutating calls when your MCP client does not preserve session identity. Never write profileToken values to repos, task comments, task attachments, logs, generated docs, or shared output. When a valid profileToken is provided, Taskforce resolves the existing profile and updates any supplied presentation fields such as name, icon, color, description, role, provider, model, and surfaceType. Update semantics: omitted optional fields leave existing stored values unchanged. For description, role, provider, and model, empty strings are ignored rather than clearing. For surfaceType, null or an empty string clears the stored value; valid enum values replace it.'),
38
+ description: context.describeTool('Resolve or create this AI collaborator profile. For stateless agents (no ambient binding): call with name + surfaceType (and profileToken to re-use); the response includes created, profileToken, profile, *plus* identityBound, bindingSource, taskFilter, suggestedArgs, nextTool, guidance. Store profileToken privately only. Use suggestedArgs for list_tasks; pass profileToken on mutating calls. Never write profileToken to repos, tasks, logs, or docs.'),
39
39
  inputSchema: {
40
40
  properties: {
41
41
  name: stringProperty('Display name for the AI collaborator.'),
@@ -31,44 +31,27 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
31
31
  });
32
32
  };
33
33
  register('get_task_attachments', (context) => ({
34
- description: context.describeTool('List attachment descriptors for one task. This returns metadata only; use read_task_attachment for file contents. For canonical documents, prefer get_document and read_document_content for the document-focused workflow.'),
34
+ description: context.describeTool('List attachment descriptors for one task. Pass attachmentId when you need one attachment body or download reference. For canonical documents, prefer get_document with includeContent=true for the document-focused workflow.'),
35
35
  inputSchema: {
36
36
  properties: {
37
37
  taskId: stringProperty('Task ID or task reference such as T-123.'),
38
38
  id: stringProperty('Alias for taskId. Task ID or task reference such as T-123.'),
39
- },
40
- },
41
- }));
42
- register('read_task_attachment', (context) => ({
43
- description: context.describeTool('Read one task attachment by attachmentId. Prefer this tool over opening attachment path URLs directly. For a canonical document attachment, this response also points toward the document-specific workflow.'),
44
- inputSchema: {
45
- properties: {
46
- taskId: stringProperty('Task ID or task reference such as T-123.'),
47
- attachmentId: stringProperty('Attachment descriptor id from get_task_attachments.'),
39
+ attachmentId: stringProperty('Optional attachment descriptor id to include content for one attachment.'),
40
+ format: stringProperty('Optional read format when attachmentId is provided. Use metadata for descriptors or download for a download URL.', ['text', 'metadata', 'download']),
48
41
  maxChars: numberProperty('Optional max inline character count for readable text attachments.'),
49
42
  },
50
- required: ['taskId', 'attachmentId'],
51
43
  },
52
44
  }));
53
45
  register('get_workstream_attachments', (context) => ({
54
- description: context.describeTool('List attachment descriptors for one workstream. This returns metadata only; use read_workstream_attachment for file contents.'),
46
+ description: context.describeTool('List attachment descriptors for one workstream. Pass attachmentId when you need one attachment body or download reference.'),
55
47
  inputSchema: {
56
48
  properties: {
57
49
  id: stringProperty('Workstream ID or workstream reference such as WS-123.'),
58
- },
59
- required: ['id'],
60
- },
61
- }));
62
- register('read_workstream_attachment', (context) => ({
63
- description: context.describeTool('Read one workstream attachment by attachmentId. Prefer this tool over opening attachment path URLs directly.'),
64
- inputSchema: {
65
- properties: {
66
- workstreamId: stringProperty('Workstream ID or workstream reference such as WS-123.'),
67
- attachmentId: stringProperty('Attachment descriptor id from get_workstream_attachments.'),
68
- format: stringProperty('Read format. Use metadata for descriptors or download for a download URL.', ['text', 'metadata', 'download']),
50
+ attachmentId: stringProperty('Optional attachment descriptor id to include content for one attachment.'),
51
+ format: stringProperty('Optional read format when attachmentId is provided. Use metadata for descriptors or download for a download URL.', ['text', 'metadata', 'download']),
69
52
  maxChars: numberProperty('Optional max inline character count for readable text attachments.'),
70
53
  },
71
- required: ['workstreamId', 'attachmentId'],
54
+ required: ['id'],
72
55
  },
73
56
  }));
74
57
  register('save_workstream_attachment', (context) => ({
@@ -96,24 +79,15 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
96
79
  },
97
80
  }));
98
81
  register('get_initiative_attachments', (context) => ({
99
- description: context.describeTool('List attachment descriptors for one initiative. This returns metadata only; use read_initiative_attachment for file contents.'),
82
+ description: context.describeTool('List attachment descriptors for one initiative. Pass attachmentId when you need one attachment body or download reference.'),
100
83
  inputSchema: {
101
84
  properties: {
102
85
  id: stringProperty('Initiative ID or initiative reference such as IN-123.'),
103
- },
104
- required: ['id'],
105
- },
106
- }));
107
- register('read_initiative_attachment', (context) => ({
108
- description: context.describeTool('Read one initiative attachment by attachmentId. Prefer this tool over opening attachment path URLs directly.'),
109
- inputSchema: {
110
- properties: {
111
- initiativeId: stringProperty('Initiative ID or initiative reference such as IN-123.'),
112
- attachmentId: stringProperty('Attachment descriptor id from get_initiative_attachments.'),
113
- format: stringProperty('Read format. Use metadata for descriptors or download for a download URL.', ['text', 'metadata', 'download']),
86
+ attachmentId: stringProperty('Optional attachment descriptor id to include content for one attachment.'),
87
+ format: stringProperty('Optional read format when attachmentId is provided. Use metadata for descriptors or download for a download URL.', ['text', 'metadata', 'download']),
114
88
  maxChars: numberProperty('Optional max inline character count for readable text attachments.'),
115
89
  },
116
- required: ['initiativeId', 'attachmentId'],
90
+ required: ['id'],
117
91
  },
118
92
  }));
119
93
  register('save_initiative_attachment', (context) => ({
@@ -151,7 +125,7 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
151
125
  },
152
126
  }));
153
127
  register('get_image', (context) => ({
154
- description: context.describeTool('Get a single image by asset id or image reference such as I-123. Use this first when the user references an image, then use get_annotated_attachment_sessions and read_annotated_attachment_payload for annotation details. When the image is being used for UI, placement, or visual correction feedback, read the saved annotation payload before making changes.'),
128
+ description: context.describeTool('Get a single image by asset id or image reference such as I-123. Use this first when the user references an image, then use get_image_annotations for saved annotation sessions and payloads. When the image is being used for UI, placement, or visual correction feedback, read the saved annotation payload before making changes.'),
155
129
  inputSchema: {
156
130
  properties: {
157
131
  id: stringProperty('Image asset id or image reference such as I-123.'),
@@ -160,7 +134,7 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
160
134
  },
161
135
  }));
162
136
  register('search_documents', (context) => ({
163
- description: context.describeTool('Search document assets by keyword, title, filename, caption, or D-123 reference. Use this before get_document when you only know a rough identifier. Search results guide the next step into get_document and read_document_content.'),
137
+ description: context.describeTool('Search document assets by keyword, title, filename, caption, or D-123 reference. Use this before get_document when you only know a rough identifier. Search results guide the next step into get_document.'),
164
138
  inputSchema: {
165
139
  properties: {
166
140
  query: stringProperty('Search term for document lookup, including D-123 references.'),
@@ -170,40 +144,26 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
170
144
  },
171
145
  }));
172
146
  register('get_document', (context) => ({
173
- description: context.describeTool('Get a single canonical document by asset id or document reference such as D-123. Use this first when the user references a document, then call read_document_content to inspect what it is reviewing.'),
147
+ description: context.describeTool('Get a single canonical document by asset id or document reference such as D-123. Pass includeContent=true when you need the document body for review or analysis.'),
174
148
  inputSchema: {
175
149
  properties: {
176
150
  id: stringProperty('Document asset id or document reference such as D-123.'),
177
151
  documentId: stringProperty('Alias for id. Document asset id or document reference such as D-123.'),
178
- },
179
- },
180
- }));
181
- register('read_document_content', (context) => ({
182
- description: context.describeTool('Read the canonical document body for one document. Use this when an AI needs to inspect what it is reviewing before adding review comments.'),
183
- inputSchema: {
184
- properties: {
185
- documentId: stringProperty('Document asset id or document reference such as D-123.'),
152
+ includeContent: booleanProperty('When true, include bounded inline text content or a download reference for binary documents.'),
153
+ format: stringProperty('Optional read mode when including content.', ['metadata', 'text', 'content', 'full', 'download']),
186
154
  maxChars: numberProperty('Optional max inline character count for readable documents.'),
187
155
  },
188
- required: ['documentId'],
189
156
  },
190
157
  }));
191
- register('get_annotated_attachment_sessions', (context) => ({
192
- description: context.describeTool('List saved annotation sessions for one image. Use this after get_image when you need the saved annotation sessions before opening one payload. If the user is pointing at a visual target in I-123, do not stop at the screenshot alone; inspect the saved session payload next.'),
158
+ register('get_image_annotations', (context) => ({
159
+ description: context.describeTool('List saved annotation sessions for one image or task, and optionally include one session payload. Use this after get_image when an image reference such as I-123 is being used for UI, placement, or visual correction feedback. Pass sessionId when you need the authoritative payload for a specific saved session.'),
193
160
  inputSchema: {
194
161
  properties: {
195
162
  imageId: stringProperty('Image asset id or image reference such as I-123.'),
163
+ imageAssetId: stringProperty('Alias for imageId. Image asset id or image reference such as I-123.'),
164
+ taskId: stringProperty('Optional task ID or task reference such as T-123 to narrow sessions linked to one task.'),
165
+ sessionId: stringProperty('Optional annotated attachment session id to include the structured payload for one session.'),
196
166
  },
197
- required: ['imageId'],
198
- },
199
- }));
200
- register('read_annotated_attachment_payload', (context) => ({
201
- description: context.describeTool('Read the structured payload for one annotated attachment session, including markers, geometry, and derived crop hints. Use this as the authoritative instruction source when I-123 is being used for UI placement or visual change requests.'),
202
- inputSchema: {
203
- properties: {
204
- sessionId: stringProperty('Annotated attachment session id.'),
205
- },
206
- required: ['sessionId'],
207
167
  },
208
168
  }));
209
169
  register('create_annotated_attachment_session', (context) => ({
@@ -320,22 +280,14 @@ export function registerDocumentAssetTools(registerTool, executeTool) {
320
280
  required: ['sessionId', 'markerId'],
321
281
  },
322
282
  }));
323
- register('get_document_review_sessions', (context) => ({
324
- description: context.describeTool('List review sessions for one document such as D-123. Use read_document_content first when you need to inspect the document body before choosing a session.'),
283
+ register('get_document_reviews', (context) => ({
284
+ description: context.describeTool('List review sessions for one document such as D-123, or fetch one review session by sessionId. Use get_document with includeContent=true when you need the document body before choosing a session.'),
325
285
  inputSchema: {
326
286
  properties: {
327
287
  documentId: stringProperty('Document asset id or document reference such as D-123.'),
328
- },
329
- required: ['documentId'],
330
- },
331
- }));
332
- register('get_document_review_session', (context) => ({
333
- description: context.describeTool('Get one document review session with its comments and summary state.'),
334
- inputSchema: {
335
- properties: {
288
+ id: stringProperty('Alias for documentId. Document asset id or document reference such as D-123.'),
336
289
  sessionId: stringProperty('Document review session id.'),
337
290
  },
338
- required: ['sessionId'],
339
291
  },
340
292
  }));
341
293
  register('create_document_review_session', (context) => ({