@taskforcehq/taskforce 0.3.323 → 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 (70) hide show
  1. package/dist/Taskforce.module.css +56 -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/AppShellHeader.js +47 -2
  5. package/dist/components/views/StandaloneLayout.js +22 -22
  6. package/dist/components/views/panels/PlanningDrawer.d.ts +2 -2
  7. package/dist/components/views/panels/PlanningDrawer.js +26 -12
  8. package/dist/components/views/planningScope.d.ts +4 -0
  9. package/dist/components/views/planningScope.js +5 -0
  10. package/dist/core/AiProfileService.js +6 -1
  11. package/dist/core/Taskforce.d.ts +3 -0
  12. package/dist/core/Taskforce.js +21 -0
  13. package/dist/hooks/useTaskforce.js +74 -17
  14. package/dist/hooks/workspace/workspaceLocalState.d.ts +25 -0
  15. package/dist/hooks/workspace/workspaceLocalState.js +38 -0
  16. package/dist/mcp/adminWorkspaceRegistrar.js +0 -10
  17. package/dist/mcp/clientIntegrations.d.ts +1 -1
  18. package/dist/mcp/clientIntegrations.js +50 -0
  19. package/dist/mcp/clientProfileDefaults.js +4 -0
  20. package/dist/mcp/collaborationRegistrar.js +3 -3
  21. package/dist/mcp/documentAssetRegistrar.js +24 -72
  22. package/dist/mcp/documentHelpers.d.ts +11 -4
  23. package/dist/mcp/documentHelpers.js +21 -17
  24. package/dist/mcp/runtime.d.ts +4 -0
  25. package/dist/mcp/runtime.js +466 -534
  26. package/dist/mcp/taskPlanningRegistrar.js +45 -81
  27. package/dist/mcp/toolProfiles.d.ts +7 -0
  28. package/dist/mcp/toolProfiles.js +36 -0
  29. package/dist/mcp/toolRegistry.d.ts +87 -609
  30. package/dist/mcp/toolRegistry.js +93 -88
  31. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +10 -10
  32. package/dist/resources/templates/workflows/collaborate.yaml +2 -3
  33. package/dist/resources/templates/workflows/do.yaml +2 -3
  34. package/dist/resources/templates/workflows/evaluate.yaml +2 -3
  35. package/dist/resources/templates/workflows/execute.yaml +12 -15
  36. package/dist/resources/templates/workflows/plan.yaml +3 -4
  37. package/dist/resources/templates/workflows/review.yaml +2 -3
  38. package/dist/server/routes/admin.d.ts +1 -0
  39. package/dist/server/routes/admin.js +29 -1
  40. package/dist/shared/taxonomyLibrary.js +1 -1
  41. package/dist/sync/cloudSyncApi.js +22 -3
  42. package/dist/types.js +1 -1
  43. package/dist/ui/.well-known/openai-apps-challenge +1 -1
  44. package/dist/ui/assets/{AgentsModule-7jpDeuVR.js → AgentsModule-C4LcqhcZ.js} +1 -1
  45. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BJLe8DFj.js → AnnotatedAttachmentWorkspace-B-Dq22d2.js} +1 -1
  46. package/dist/ui/assets/{ContextAttachmentManager-AmEny3eI.js → ContextAttachmentManager-DsXPTvi-.js} +1 -1
  47. package/dist/ui/assets/{DocumentWorkspace-Cqdq31Ha.js → DocumentWorkspace-CxA1wO3U.js} +1 -1
  48. package/dist/ui/assets/{EntityActivityTimeline-BlS7DhA4.js → EntityActivityTimeline-B8nSe8l3.js} +1 -1
  49. package/dist/ui/assets/{InitiativesModule-C1qfO1uZ.js → InitiativesModule-VY95v38E.js} +1 -1
  50. package/dist/ui/assets/{PlansPage-DFt5w-0Y.js → PlansPage-CQbKDv1a.js} +1 -1
  51. package/dist/ui/assets/{TaskContextUpload-zuro5gDT.js → TaskContextUpload-DvDlNpHC.js} +1 -1
  52. package/dist/ui/assets/TaskSettings-CUWbYYtP.js +12 -0
  53. package/dist/ui/assets/{WorkflowsModule-CEEGuxsf.js → WorkflowsModule-CgM3Jako.js} +1 -1
  54. package/dist/ui/assets/documentReferences-Do9YjrS3.js +1 -0
  55. package/dist/ui/assets/index--mh3pktj.js +5 -0
  56. package/dist/ui/assets/index-BawaaEXS.css +1 -0
  57. package/dist/ui/index.html +2 -2
  58. package/dist/utils/theme.js +1 -1
  59. package/package.json +1 -1
  60. package/src/resources/templates/workflow-sources/workflowDocs.mjs +10 -10
  61. package/src/resources/templates/workflows/collaborate.yaml +2 -3
  62. package/src/resources/templates/workflows/do.yaml +2 -3
  63. package/src/resources/templates/workflows/evaluate.yaml +2 -3
  64. package/src/resources/templates/workflows/execute.yaml +12 -15
  65. package/src/resources/templates/workflows/plan.yaml +3 -4
  66. package/src/resources/templates/workflows/review.yaml +2 -3
  67. package/dist/ui/assets/TaskSettings-CJPC2Bft.js +0 -9
  68. package/dist/ui/assets/documentReferences-Ca2P--i5.js +0 -1
  69. package/dist/ui/assets/index-CGDQY0nN.js +0 -5
  70. package/dist/ui/assets/index-DcK_8tuE.css +0 -1
@@ -5422,6 +5422,27 @@ export class TaskforceCore {
5422
5422
  `).run(enabled ? 1 : 0, new Date().toISOString(), this.tenantId, normalizedUserId);
5423
5423
  return Number(result?.changes || 0) > 0;
5424
5424
  }
5425
+ markUserEmailVerified(userId) {
5426
+ const normalizedUserId = String(userId || '').trim();
5427
+ if (!normalizedUserId)
5428
+ return null;
5429
+ const now = new Date().toISOString();
5430
+ const row = this.db.prepare(`
5431
+ SELECT id, email_verified_at
5432
+ FROM users
5433
+ WHERE tenant_id = ? AND id = ?
5434
+ LIMIT 1
5435
+ `).get(this.tenantId, normalizedUserId);
5436
+ if (!row?.id)
5437
+ return null;
5438
+ const emailVerifiedAt = row.email_verified_at || now;
5439
+ this.db.prepare(`
5440
+ UPDATE users
5441
+ SET email_verified_at = COALESCE(email_verified_at, ?), updated_at = ?
5442
+ WHERE tenant_id = ? AND id = ?
5443
+ `).run(now, now, this.tenantId, normalizedUserId);
5444
+ return { emailVerifiedAt };
5445
+ }
5425
5446
  purgeSystemUser(input) {
5426
5447
  const targetUserId = String(input.targetUserId || '').trim();
5427
5448
  const actorUserId = String(input.actorUserId || '').trim();
@@ -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.'),