@task-boards/mcp-server 0.15.0 → 0.17.0

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 (3) hide show
  1. package/build/cli.js +1826 -1092
  2. package/build/cli.js.map +4 -4
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -25,6 +25,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  mod
26
26
  ));
27
27
 
28
+ // ../api/build/agent-runs/agent-runs.api.js
29
+ var require_agent_runs_api = __commonJS({
30
+ "../api/build/agent-runs/agent-runs.api.js"(exports) {
31
+ "use strict";
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.AGENT_RUNS_ROUTES = exports.AGENT_RUNS_PREFIX = void 0;
34
+ exports.AGENT_RUNS_PREFIX = "/agent-runs";
35
+ exports.AGENT_RUNS_ROUTES = {
36
+ listAgentRuns: () => exports.AGENT_RUNS_PREFIX,
37
+ claimAgentRuns: () => `${exports.AGENT_RUNS_PREFIX}/claim`,
38
+ listAgentRunsByWorkItem: (workItemId) => `/work-items/${workItemId}/agent-runs`,
39
+ retryAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/retry`,
40
+ acknowledgeAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/acknowledge`,
41
+ setAgentRunAttention: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/attention`,
42
+ completeAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/complete`
43
+ };
44
+ }
45
+ });
46
+
28
47
  // ../api/build/api-prefix.js
29
48
  var require_api_prefix = __commonJS({
30
49
  "../api/build/api-prefix.js"(exports) {
@@ -102,6 +121,7 @@ var require_agent_run_outcome_enum = __commonJS({
102
121
  exports.AGENT_RUN_OUTCOME = {
103
122
  DEFAULT: "DEFAULT",
104
123
  SKIP_DESIGN: "SKIP_DESIGN",
124
+ SKIP_DEV: "SKIP_DEV",
105
125
  HAS_BUGS: "HAS_BUGS",
106
126
  NEEDS_CLARIFICATION: "NEEDS_CLARIFICATION",
107
127
  FAILED: "FAILED"
@@ -794,7 +814,8 @@ var require_feedback_api = __commonJS({
794
814
  listFeedback: () => exports.FEEDBACK_ADMIN_PREFIX,
795
815
  getUnreadSummary: () => `${exports.FEEDBACK_ADMIN_PREFIX}/unread-summary`,
796
816
  markFeedbackRead: () => `${exports.FEEDBACK_ADMIN_PREFIX}/mark-read`,
797
- getFeedback: (id) => `${exports.FEEDBACK_ADMIN_PREFIX}/${id}`
817
+ getFeedback: (id) => `${exports.FEEDBACK_ADMIN_PREFIX}/${id}`,
818
+ deleteFeedback: (id) => `${exports.FEEDBACK_ADMIN_PREFIX}/${id}`
798
819
  };
799
820
  }
800
821
  });
@@ -860,7 +881,8 @@ var require_notifications_api = __commonJS({
860
881
  listNotifications: () => exports.NOTIFICATIONS_PREFIX,
861
882
  getUnreadCount: () => `${exports.NOTIFICATIONS_PREFIX}/unread-count`,
862
883
  markNotificationRead: (id) => `${exports.NOTIFICATIONS_PREFIX}/${id}/mark-read`,
863
- markAllNotificationsRead: () => `${exports.NOTIFICATIONS_PREFIX}/mark-all-read`
884
+ markAllNotificationsRead: () => `${exports.NOTIFICATIONS_PREFIX}/mark-all-read`,
885
+ clearAllNotifications: () => `${exports.NOTIFICATIONS_PREFIX}/clear-all`
864
886
  };
865
887
  }
866
888
  });
@@ -915,25 +937,6 @@ var require_notifications = __commonJS({
915
937
  }
916
938
  });
917
939
 
918
- // ../api/build/agent-runs/agent-runs.api.js
919
- var require_agent_runs_api = __commonJS({
920
- "../api/build/agent-runs/agent-runs.api.js"(exports) {
921
- "use strict";
922
- Object.defineProperty(exports, "__esModule", { value: true });
923
- exports.AGENT_RUNS_ROUTES = exports.AGENT_RUNS_PREFIX = void 0;
924
- exports.AGENT_RUNS_PREFIX = "/agent-runs";
925
- exports.AGENT_RUNS_ROUTES = {
926
- listAgentRuns: () => exports.AGENT_RUNS_PREFIX,
927
- claimAgentRuns: () => `${exports.AGENT_RUNS_PREFIX}/claim`,
928
- listAgentRunsByWorkItem: (workItemId) => `/work-items/${workItemId}/agent-runs`,
929
- retryAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/retry`,
930
- acknowledgeAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/acknowledge`,
931
- setAgentRunAttention: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/attention`,
932
- completeAgentRun: (id) => `${exports.AGENT_RUNS_PREFIX}/${id}/complete`
933
- };
934
- }
935
- });
936
-
937
940
  // ../api/build/agent-runs/agent-runs.types.js
938
941
  var require_agent_runs_types = __commonJS({
939
942
  "../api/build/agent-runs/agent-runs.types.js"(exports) {
@@ -1661,10 +1664,16 @@ var require_exception_codes = __commonJS({
1661
1664
  LABEL_NAME_CONFLICT: "LABEL_NAME_CONFLICT",
1662
1665
  LABEL_INVALID_IDS: "LABEL_INVALID_IDS",
1663
1666
  FEEDBACK_NOT_FOUND: "FEEDBACK_NOT_FOUND",
1667
+ FEEDBACK_NOT_READ: "FEEDBACK_NOT_READ",
1664
1668
  STAFF_ROLE_ALREADY_ASSIGNED: "STAFF_ROLE_ALREADY_ASSIGNED",
1665
1669
  STAFF_ROLE_NOT_ASSIGNED: "STAFF_ROLE_NOT_ASSIGNED",
1666
1670
  STAFF_ROLE_INCOMPATIBLE_WITH_ADMIN: "STAFF_ROLE_INCOMPATIBLE_WITH_ADMIN",
1667
1671
  CANNOT_ASSIGN_STAFF_ROLE_TO_SERVICE_USER: "CANNOT_ASSIGN_STAFF_ROLE_TO_SERVICE_USER",
1672
+ USER_BLOCKED: "USER_BLOCKED",
1673
+ CANNOT_BLOCK_SELF: "CANNOT_BLOCK_SELF",
1674
+ CANNOT_BLOCK_ADMIN: "CANNOT_BLOCK_ADMIN",
1675
+ USER_ALREADY_BLOCKED: "USER_ALREADY_BLOCKED",
1676
+ USER_NOT_BLOCKED: "USER_NOT_BLOCKED",
1668
1677
  SUBSCRIPTION_PROJECT_LIMIT_REACHED: "SUBSCRIPTION_PROJECT_LIMIT_REACHED",
1669
1678
  SUBSCRIPTION_MEMBER_LIMIT_REACHED: "SUBSCRIPTION_MEMBER_LIMIT_REACHED",
1670
1679
  SUBSCRIPTION_PROJECT_READ_ONLY: "SUBSCRIPTION_PROJECT_READ_ONLY",
@@ -1682,7 +1691,11 @@ var require_exception_codes = __commonJS({
1682
1691
  ADMIN_NOTIFICATION_SETTINGS_NOT_FOUND: "ADMIN_NOTIFICATION_SETTINGS_NOT_FOUND",
1683
1692
  ADMIN_NOTIFICATION_CHANNEL_INVALID: "ADMIN_NOTIFICATION_CHANNEL_INVALID",
1684
1693
  ADMIN_NOTIFICATION_EMAIL_RECIPIENT_REQUIRED: "ADMIN_NOTIFICATION_EMAIL_RECIPIENT_REQUIRED",
1685
- ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE: "ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE"
1694
+ ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE: "ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE",
1695
+ PAYMENTS_DISABLED: "PAYMENTS_DISABLED",
1696
+ PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED",
1697
+ PAYMENTS_PROBE_FAILED: "PAYMENTS_PROBE_FAILED",
1698
+ SYSTEM_READ_ONLY: "SYSTEM_READ_ONLY"
1686
1699
  };
1687
1700
  }
1688
1701
  });
@@ -1695,7 +1708,8 @@ var require_health_api = __commonJS({
1695
1708
  exports.HEALTH_ROUTES = exports.HEALTH_PREFIX = void 0;
1696
1709
  exports.HEALTH_PREFIX = "/health";
1697
1710
  exports.HEALTH_ROUTES = {
1698
- getHealth: () => exports.HEALTH_PREFIX
1711
+ getHealth: () => exports.HEALTH_PREFIX,
1712
+ getReadiness: () => `${exports.HEALTH_PREFIX}/ready`
1699
1713
  };
1700
1714
  }
1701
1715
  });
@@ -1705,10 +1719,18 @@ var require_health_types = __commonJS({
1705
1719
  "../shared/build/internal/health/health.types.js"(exports) {
1706
1720
  "use strict";
1707
1721
  Object.defineProperty(exports, "__esModule", { value: true });
1708
- exports.HEALTH_STATUS = void 0;
1722
+ exports.READINESS_CHECK_STATUS = exports.READINESS_STATUS = exports.HEALTH_STATUS = void 0;
1709
1723
  exports.HEALTH_STATUS = {
1710
1724
  OK: "ok"
1711
1725
  };
1726
+ exports.READINESS_STATUS = {
1727
+ OK: "ok",
1728
+ ERROR: "error"
1729
+ };
1730
+ exports.READINESS_CHECK_STATUS = {
1731
+ UP: "up",
1732
+ DOWN: "down"
1733
+ };
1712
1734
  }
1713
1735
  });
1714
1736
 
@@ -1745,6 +1767,19 @@ var require_health = __commonJS({
1745
1767
  }
1746
1768
  });
1747
1769
 
1770
+ // ../shared/build/internal/platform/platform-status.api.js
1771
+ var require_platform_status_api = __commonJS({
1772
+ "../shared/build/internal/platform/platform-status.api.js"(exports) {
1773
+ "use strict";
1774
+ Object.defineProperty(exports, "__esModule", { value: true });
1775
+ exports.PLATFORM_STATUS_ROUTES = exports.PLATFORM_STATUS_PREFIX = void 0;
1776
+ exports.PLATFORM_STATUS_PREFIX = "/platform/status";
1777
+ exports.PLATFORM_STATUS_ROUTES = {
1778
+ getPlatformStatus: () => exports.PLATFORM_STATUS_PREFIX
1779
+ };
1780
+ }
1781
+ });
1782
+
1748
1783
  // ../shared/build/internal/admin/admin.api.js
1749
1784
  var require_admin_api = __commonJS({
1750
1785
  "../shared/build/internal/admin/admin.api.js"(exports) {
@@ -1758,6 +1793,44 @@ var require_admin_api = __commonJS({
1758
1793
  }
1759
1794
  });
1760
1795
 
1796
+ // ../shared/build/internal/platform/platform-read-only-admin.api.js
1797
+ var require_platform_read_only_admin_api = __commonJS({
1798
+ "../shared/build/internal/platform/platform-read-only-admin.api.js"(exports) {
1799
+ "use strict";
1800
+ Object.defineProperty(exports, "__esModule", { value: true });
1801
+ exports.PLATFORM_READ_ONLY_ADMIN_ROUTES = exports.PLATFORM_READ_ONLY_ADMIN_PREFIX = void 0;
1802
+ var admin_api_1 = require_admin_api();
1803
+ exports.PLATFORM_READ_ONLY_ADMIN_PREFIX = `${admin_api_1.ADMIN_PREFIX}/platform-read-only`;
1804
+ exports.PLATFORM_READ_ONLY_ADMIN_ROUTES = {
1805
+ getPlatformReadOnly: () => exports.PLATFORM_READ_ONLY_ADMIN_PREFIX,
1806
+ updatePlatformReadOnly: () => exports.PLATFORM_READ_ONLY_ADMIN_PREFIX
1807
+ };
1808
+ }
1809
+ });
1810
+
1811
+ // ../shared/build/internal/platform/index.js
1812
+ var require_platform = __commonJS({
1813
+ "../shared/build/internal/platform/index.js"(exports) {
1814
+ "use strict";
1815
+ Object.defineProperty(exports, "__esModule", { value: true });
1816
+ exports.PLATFORM_READ_ONLY_ADMIN_ROUTES = exports.PLATFORM_READ_ONLY_ADMIN_PREFIX = exports.PLATFORM_STATUS_ROUTES = exports.PLATFORM_STATUS_PREFIX = void 0;
1817
+ var platform_status_api_1 = require_platform_status_api();
1818
+ Object.defineProperty(exports, "PLATFORM_STATUS_PREFIX", { enumerable: true, get: function() {
1819
+ return platform_status_api_1.PLATFORM_STATUS_PREFIX;
1820
+ } });
1821
+ Object.defineProperty(exports, "PLATFORM_STATUS_ROUTES", { enumerable: true, get: function() {
1822
+ return platform_status_api_1.PLATFORM_STATUS_ROUTES;
1823
+ } });
1824
+ var platform_read_only_admin_api_1 = require_platform_read_only_admin_api();
1825
+ Object.defineProperty(exports, "PLATFORM_READ_ONLY_ADMIN_PREFIX", { enumerable: true, get: function() {
1826
+ return platform_read_only_admin_api_1.PLATFORM_READ_ONLY_ADMIN_PREFIX;
1827
+ } });
1828
+ Object.defineProperty(exports, "PLATFORM_READ_ONLY_ADMIN_ROUTES", { enumerable: true, get: function() {
1829
+ return platform_read_only_admin_api_1.PLATFORM_READ_ONLY_ADMIN_ROUTES;
1830
+ } });
1831
+ }
1832
+ });
1833
+
1761
1834
  // ../shared/build/internal/admin/admin-notification-channel.enum.js
1762
1835
  var require_admin_notification_channel_enum = __commonJS({
1763
1836
  "../shared/build/internal/admin/admin-notification-channel.enum.js"(exports) {
@@ -1798,11 +1871,27 @@ var require_admin_users_api = __commonJS({
1798
1871
  exports.ADMIN_USERS_ROUTES = {
1799
1872
  listUsers: () => `${admin_api_1.ADMIN_PREFIX}/users`,
1800
1873
  assignStaffRole: (userId) => `${admin_api_1.ADMIN_PREFIX}/users/${userId}/staff-roles`,
1801
- revokeStaffRole: (userId, role) => `${admin_api_1.ADMIN_PREFIX}/users/${userId}/staff-roles/${role}`
1874
+ revokeStaffRole: (userId, role) => `${admin_api_1.ADMIN_PREFIX}/users/${userId}/staff-roles/${role}`,
1875
+ blockUser: (userId) => `${admin_api_1.ADMIN_PREFIX}/users/${userId}/block`,
1876
+ unblockUser: (userId) => `${admin_api_1.ADMIN_PREFIX}/users/${userId}/unblock`
1802
1877
  };
1803
1878
  }
1804
1879
  });
1805
1880
 
1881
+ // ../shared/build/internal/admin/user-block-status.enum.js
1882
+ var require_user_block_status_enum = __commonJS({
1883
+ "../shared/build/internal/admin/user-block-status.enum.js"(exports) {
1884
+ "use strict";
1885
+ Object.defineProperty(exports, "__esModule", { value: true });
1886
+ exports.UserBlockStatus = void 0;
1887
+ var UserBlockStatus;
1888
+ (function(UserBlockStatus2) {
1889
+ UserBlockStatus2["ACTIVE"] = "ACTIVE";
1890
+ UserBlockStatus2["BLOCKED"] = "BLOCKED";
1891
+ })(UserBlockStatus || (exports.UserBlockStatus = UserBlockStatus = {}));
1892
+ }
1893
+ });
1894
+
1806
1895
  // ../shared/build/internal/admin/admin-users.consts.js
1807
1896
  var require_admin_users_consts = __commonJS({
1808
1897
  "../shared/build/internal/admin/admin-users.consts.js"(exports) {
@@ -1842,7 +1931,7 @@ var require_admin = __commonJS({
1842
1931
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
1843
1932
  };
1844
1933
  Object.defineProperty(exports, "__esModule", { value: true });
1845
- exports.REGISTERED_WITHIN_DAYS_OPTIONS = exports.ADMIN_USERS_ROUTES = exports.ADMIN_NOTIFICATION_SETTINGS_ROUTES = exports.ADMIN_NOTIFICATION_SETTINGS_PREFIX = exports.ADMIN_NOTIFICATION_CHANNEL = exports.ADMIN_ROUTES = exports.ADMIN_PREFIX = void 0;
1934
+ exports.REGISTERED_WITHIN_DAYS_OPTIONS = exports.UserBlockStatus = exports.ADMIN_USERS_ROUTES = exports.ADMIN_NOTIFICATION_SETTINGS_ROUTES = exports.ADMIN_NOTIFICATION_SETTINGS_PREFIX = exports.ADMIN_NOTIFICATION_CHANNEL = exports.ADMIN_ROUTES = exports.ADMIN_PREFIX = void 0;
1846
1935
  var admin_api_1 = require_admin_api();
1847
1936
  Object.defineProperty(exports, "ADMIN_PREFIX", { enumerable: true, get: function() {
1848
1937
  return admin_api_1.ADMIN_PREFIX;
@@ -1865,6 +1954,10 @@ var require_admin = __commonJS({
1865
1954
  Object.defineProperty(exports, "ADMIN_USERS_ROUTES", { enumerable: true, get: function() {
1866
1955
  return admin_users_api_1.ADMIN_USERS_ROUTES;
1867
1956
  } });
1957
+ var user_block_status_enum_1 = require_user_block_status_enum();
1958
+ Object.defineProperty(exports, "UserBlockStatus", { enumerable: true, get: function() {
1959
+ return user_block_status_enum_1.UserBlockStatus;
1960
+ } });
1868
1961
  var admin_users_consts_1 = require_admin_users_consts();
1869
1962
  Object.defineProperty(exports, "REGISTERED_WITHIN_DAYS_OPTIONS", { enumerable: true, get: function() {
1870
1963
  return admin_users_consts_1.REGISTERED_WITHIN_DAYS_OPTIONS;
@@ -1873,6 +1966,36 @@ var require_admin = __commonJS({
1873
1966
  }
1874
1967
  });
1875
1968
 
1969
+ // ../shared/build/internal/payments/admin-payments.api.js
1970
+ var require_admin_payments_api = __commonJS({
1971
+ "../shared/build/internal/payments/admin-payments.api.js"(exports) {
1972
+ "use strict";
1973
+ Object.defineProperty(exports, "__esModule", { value: true });
1974
+ exports.ADMIN_PAYMENTS_ROUTES = exports.ADMIN_PAYMENTS_PREFIX = void 0;
1975
+ var admin_api_1 = require_admin_api();
1976
+ exports.ADMIN_PAYMENTS_PREFIX = `${admin_api_1.ADMIN_PREFIX}/payments`;
1977
+ exports.ADMIN_PAYMENTS_ROUTES = {
1978
+ probePaymentsConnectivity: () => `${exports.ADMIN_PAYMENTS_PREFIX}/probe`
1979
+ };
1980
+ }
1981
+ });
1982
+
1983
+ // ../shared/build/internal/payments/index.js
1984
+ var require_payments = __commonJS({
1985
+ "../shared/build/internal/payments/index.js"(exports) {
1986
+ "use strict";
1987
+ Object.defineProperty(exports, "__esModule", { value: true });
1988
+ exports.ADMIN_PAYMENTS_ROUTES = exports.ADMIN_PAYMENTS_PREFIX = void 0;
1989
+ var admin_payments_api_1 = require_admin_payments_api();
1990
+ Object.defineProperty(exports, "ADMIN_PAYMENTS_PREFIX", { enumerable: true, get: function() {
1991
+ return admin_payments_api_1.ADMIN_PAYMENTS_PREFIX;
1992
+ } });
1993
+ Object.defineProperty(exports, "ADMIN_PAYMENTS_ROUTES", { enumerable: true, get: function() {
1994
+ return admin_payments_api_1.ADMIN_PAYMENTS_ROUTES;
1995
+ } });
1996
+ }
1997
+ });
1998
+
1876
1999
  // ../shared/build/internal/subscriptions/subscription-source.enum.js
1877
2000
  var require_subscription_source_enum = __commonJS({
1878
2001
  "../shared/build/internal/subscriptions/subscription-source.enum.js"(exports) {
@@ -2089,7 +2212,9 @@ var require_internal = __commonJS({
2089
2212
  __exportStar(require_imports(), exports);
2090
2213
  __exportStar(require_exception_codes(), exports);
2091
2214
  __exportStar(require_health(), exports);
2215
+ __exportStar(require_platform(), exports);
2092
2216
  __exportStar(require_admin(), exports);
2217
+ __exportStar(require_payments(), exports);
2093
2218
  __exportStar(require_subscriptions2(), exports);
2094
2219
  }
2095
2220
  });
@@ -2428,7 +2553,8 @@ var require_agentic_sdlc_workflow = __commonJS({
2428
2553
  "in-analysis": {
2429
2554
  defaultNextSlug: "in-development",
2430
2555
  outcomes: {
2431
- [agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN]: "in-development"
2556
+ [agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN]: "in-development",
2557
+ [agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DEV]: "released"
2432
2558
  }
2433
2559
  },
2434
2560
  "in-development": {
@@ -2470,7 +2596,7 @@ var require_agentic_sdlc_workflow = __commonJS({
2470
2596
  nextAgentRole
2471
2597
  };
2472
2598
  }
2473
- function resolveInAnalysisTransition(outcome, workflowPhase) {
2599
+ function resolveInAnalysisTransition(outcome, workflowPhase, context) {
2474
2600
  if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.NEEDS_CLARIFICATION) {
2475
2601
  return moveTransition(exports.IN_AWAITING_COLUMN_SLUG, workflowPhase, null);
2476
2602
  }
@@ -2491,6 +2617,12 @@ var require_agentic_sdlc_workflow = __commonJS({
2491
2617
  if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN) {
2492
2618
  return moveTransition("in-development", workflow_phase_enum_1.WORKFLOW_PHASE.DEVELOPMENT, null);
2493
2619
  }
2620
+ if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DEV) {
2621
+ if (context.codeChangesRequired !== false) {
2622
+ return null;
2623
+ }
2624
+ return moveTransition("released", null, null);
2625
+ }
2494
2626
  return null;
2495
2627
  }
2496
2628
  if (workflowPhase === workflow_phase_enum_1.WORKFLOW_PHASE.ARCHITECT) {
@@ -2566,7 +2698,7 @@ var require_agentic_sdlc_workflow = __commonJS({
2566
2698
  }
2567
2699
  if (currentColumnSlug === "in-analysis") {
2568
2700
  const phase = context.workflowPhase ?? (0, workflow_phase_subagent_role_map_1.subagentRoleToWorkflowPhase)(context.agentRole);
2569
- return resolveInAnalysisTransition(outcome, phase);
2701
+ return resolveInAnalysisTransition(outcome, phase, context);
2570
2702
  }
2571
2703
  if (currentColumnSlug === "in-development") {
2572
2704
  return resolveInDevelopmentTransition(outcome, context);
@@ -3326,7 +3458,8 @@ var require_mcp_sensitive_data_util = __commonJS({
3326
3458
  exports.MCP_REDACTED_HOST = "[REDACTED_HOST]";
3327
3459
  exports.MCP_REDACTED_PORT = "[REDACTED_PORT]";
3328
3460
  exports.MCP_WORKSPACE_PLACEHOLDER = "[workspace]";
3329
- var SENSITIVE_OBJECT_KEY_PATTERN = /^(password|passwd|secret|clientsecret|client_secret|apikey|api_key|token|authorization|smtp_password|tokenpepper|token_pepper|privatekey|private_key)$/i;
3461
+ var SENSITIVE_OBJECT_KEY_PATTERN = /^(password|passwd|secret|clientsecret|client_secret|apikey|api_key|apisecret|api_secret|token|authorization|smtp_password|tokenpepper|token_pepper|privatekey|private_key|pan|cardnumber|card_number)$/i;
3462
+ var PAN_PATTERN = /\b(?:\d[ -]*?){13,19}\b/g;
3330
3463
  var BLOCKED_ATTACHMENT_FILE_NAME_PATTERN = /^(?:\.env(?:\..*)?|secrets\.ya?ml|config\.ya?ml|.*\.pem|.*\.p12|.*\.key|id_rsa(?:\..*)?|credentials\.json|\.npmrc|docker-compose\.ya?ml|compose\.ya?ml)$/i;
3331
3464
  var JWT_PATTERN = /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
3332
3465
  var BEARER_PATTERN = /Bearer\s+[A-Za-z0-9._~+/=-]+/gi;
@@ -3373,6 +3506,7 @@ var require_mcp_sensitive_data_util = __commonJS({
3373
3506
  result = result.replace(HTTPS_REMOTE_URL_PATTERN, `${exports.MCP_REDACTED_HOST}/...`);
3374
3507
  result = result.replace(ENV_ASSIGNMENT_PATTERN, `${exports.MCP_REDACTED}_ENV`);
3375
3508
  result = result.replace(NPM_TOKEN_PATTERN, `NpmToken.${exports.MCP_REDACTED}`);
3509
+ result = result.replace(PAN_PATTERN, exports.MCP_REDACTED);
3376
3510
  return result;
3377
3511
  }
3378
3512
  function redactSensitiveValueDeep(value, workspaceRoot) {
@@ -3551,43 +3685,44 @@ var require_build2 = __commonJS({
3551
3685
  }
3552
3686
  });
3553
3687
 
3554
- // src/index.ts
3555
- import { createRequire } from "node:module";
3556
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3557
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3688
+ // src/orchestrator/orchestrator-transport.ts
3689
+ var import_agent_runs2 = __toESM(require_agent_runs_api(), 1);
3690
+ var import_shared5 = __toESM(require_build2(), 1);
3558
3691
 
3559
- // src/config.ts
3560
- var import_api = __toESM(require_build(), 1);
3561
- function resolveSanitizeResponses() {
3562
- const raw = process.env.TASK_BOARDS_MCP_SANITIZE;
3563
- if (raw === void 0 || raw.trim() === "") {
3564
- return true;
3565
- }
3566
- const normalized = raw.trim().toLowerCase();
3567
- return normalized !== "false" && normalized !== "0" && normalized !== "no";
3692
+ // src/tools/attachments.ts
3693
+ var import_attachments = __toESM(require_attachments_api(), 1);
3694
+ var import_shared3 = __toESM(require_build2(), 1);
3695
+ import { existsSync as existsSync3 } from "node:fs";
3696
+ import { z } from "zod";
3697
+
3698
+ // src/attachments/detect-mime-type.ts
3699
+ import { basename } from "node:path";
3700
+ import { lookup as lookupMimeType } from "mime-types";
3701
+ function detectMimeType(filePath) {
3702
+ const mimeType = lookupMimeType(basename(filePath));
3703
+ return typeof mimeType === "string" ? mimeType : "application/octet-stream";
3568
3704
  }
3569
- function resolveBlockSensitiveAttachments() {
3570
- const raw = process.env.TASK_BOARDS_MCP_BLOCK_SENSITIVE_ATTACHMENTS;
3571
- if (raw === void 0 || raw.trim() === "") {
3572
- return true;
3573
- }
3574
- const normalized = raw.trim().toLowerCase();
3575
- return normalized !== "false" && normalized !== "0" && normalized !== "no";
3705
+
3706
+ // src/attachments/staging.util.ts
3707
+ import { mkdir, writeFile } from "node:fs/promises";
3708
+ import { dirname, resolve } from "node:path";
3709
+ var MAX_FILE_NAME_LENGTH = 200;
3710
+ function sanitizeFileName(name) {
3711
+ const baseName = name.split(/[/\\]/).pop() ?? name;
3712
+ const sanitized = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
3713
+ return sanitized.length > MAX_FILE_NAME_LENGTH ? sanitized.slice(0, MAX_FILE_NAME_LENGTH) : sanitized;
3576
3714
  }
3577
- function loadConfig() {
3578
- const apiUrl = process.env.TASK_BOARDS_API_URL ?? "http://localhost:3000";
3579
- const apiToken = process.env.TASK_BOARDS_API_TOKEN;
3580
- const workspaceRoot = process.env.WORKSPACE_ROOT;
3581
- if (process.env.NODE_ENV === "production" && (apiToken === void 0 || apiToken.trim() === "")) {
3582
- console.error("WARNING: TASK_BOARDS_API_TOKEN is not set. MCP server cannot authenticate to the Task Boards API.");
3583
- }
3584
- return {
3585
- apiUrl: `${apiUrl.replace(/\/$/, "")}${import_api.API_V1_PREFIX}`,
3586
- apiToken,
3587
- workspaceRoot: workspaceRoot !== void 0 && workspaceRoot.trim() !== "" ? workspaceRoot : void 0,
3588
- sanitizeResponses: resolveSanitizeResponses(),
3589
- blockSensitiveAttachments: resolveBlockSensitiveAttachments()
3590
- };
3715
+ function buildStagingPath(workspaceRoot, workItemId, attachmentId, fileName) {
3716
+ const sanitizedFileName = sanitizeFileName(fileName);
3717
+ return resolve(workspaceRoot, ".task-boards", "attachments", workItemId, `${attachmentId}_${sanitizedFileName}`);
3718
+ }
3719
+ function buildStagingDir(workspaceRoot, workItemId) {
3720
+ return resolve(workspaceRoot, ".task-boards", "attachments", workItemId);
3721
+ }
3722
+ async function writeStagingFile(path, data) {
3723
+ const absolutePath = resolve(path);
3724
+ await mkdir(dirname(absolutePath), { recursive: true });
3725
+ await writeFile(absolutePath, data);
3591
3726
  }
3592
3727
 
3593
3728
  // src/rest-client.ts
@@ -3694,6 +3829,121 @@ var RestClient = class {
3694
3829
  }
3695
3830
  };
3696
3831
 
3832
+ // src/workspace/confine-file-path.ts
3833
+ import { existsSync, readFileSync, statSync } from "node:fs";
3834
+ import { basename as basename2, isAbsolute, relative, resolve as resolve2 } from "node:path";
3835
+
3836
+ // src/workspace/workspace-error.ts
3837
+ var WorkspaceError = class extends Error {
3838
+ constructor(code, message) {
3839
+ super(message);
3840
+ this.code = code;
3841
+ this.name = "WorkspaceError";
3842
+ }
3843
+ };
3844
+
3845
+ // src/workspace/confine-file-path.ts
3846
+ function confineFilePath(workspaceRoot, filePath) {
3847
+ const resolvedRoot = resolve2(workspaceRoot);
3848
+ const resolvedPath = isAbsolute(filePath) ? resolve2(filePath) : resolve2(resolvedRoot, filePath);
3849
+ const relativePath = relative(resolvedRoot, resolvedPath);
3850
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
3851
+ throw new WorkspaceError(
3852
+ "FILE_OUT_OF_BOUNDS",
3853
+ `filePath is outside workspaceRoot: ${basename2(filePath) || filePath}`
3854
+ );
3855
+ }
3856
+ if (!existsSync(resolvedPath)) {
3857
+ throw new WorkspaceError("FILE_NOT_FOUND", `filePath does not exist: ${basename2(filePath) || filePath}`);
3858
+ }
3859
+ const stats = statSync(resolvedPath);
3860
+ if (!stats.isFile()) {
3861
+ throw new WorkspaceError("FILE_NOT_FOUND", `filePath is not a file: ${basename2(filePath) || filePath}`);
3862
+ }
3863
+ return resolvedPath;
3864
+ }
3865
+ function readConfinedWorkspaceFile(workspaceRoot, filePath) {
3866
+ const absolutePath = confineFilePath(workspaceRoot, filePath);
3867
+ const stats = statSync(absolutePath);
3868
+ const buffer = readFileSync(absolutePath);
3869
+ return {
3870
+ absolutePath,
3871
+ fileName: basename2(absolutePath),
3872
+ sizeBytes: stats.size,
3873
+ buffer
3874
+ };
3875
+ }
3876
+
3877
+ // src/workspace/resolve-workspace-root.ts
3878
+ import { existsSync as existsSync2, statSync as statSync2 } from "node:fs";
3879
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve3 } from "node:path";
3880
+ var TASK_BOARDS_FILE = ".task-boards.yaml";
3881
+ var MAX_UPWARD_LEVELS = 20;
3882
+ function assertDirectory(path, source) {
3883
+ const absolutePath = resolve3(path);
3884
+ if (!existsSync2(absolutePath)) {
3885
+ throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} does not exist: ${absolutePath}`);
3886
+ }
3887
+ const stats = statSync2(absolutePath);
3888
+ if (!stats.isDirectory()) {
3889
+ throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} is not a directory: ${absolutePath}`);
3890
+ }
3891
+ return absolutePath;
3892
+ }
3893
+ function assertWithinAllowedRoot(target, allowedRoot) {
3894
+ const relativePath = relative2(allowedRoot, target);
3895
+ const isNested = relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute2(relativePath);
3896
+ if (target !== allowedRoot && !isNested) {
3897
+ throw new WorkspaceError(
3898
+ "WORKSPACE_OUT_OF_BOUNDS",
3899
+ `workspaceRoot is outside the allowed WORKSPACE_ROOT: ${target}`
3900
+ );
3901
+ }
3902
+ }
3903
+ function assertGitRepository(absolutePath) {
3904
+ if (!existsSync2(join(absolutePath, ".git"))) {
3905
+ throw new WorkspaceError("WORKSPACE_NOT_GIT_REPO", `workspaceRoot is not a git repository: ${absolutePath}`);
3906
+ }
3907
+ }
3908
+ function findWorkspaceRootUpward(startDir) {
3909
+ let current = resolve3(startDir);
3910
+ for (let level = 0; level <= MAX_UPWARD_LEVELS; level += 1) {
3911
+ const markerPath = join(current, TASK_BOARDS_FILE);
3912
+ if (existsSync2(markerPath)) {
3913
+ return current;
3914
+ }
3915
+ const parent = dirname2(current);
3916
+ if (parent === current) {
3917
+ break;
3918
+ }
3919
+ current = parent;
3920
+ }
3921
+ return null;
3922
+ }
3923
+ function resolveWorkspaceRoot(explicitOverride, envWorkspaceRoot) {
3924
+ const allowedRoot = envWorkspaceRoot !== void 0 && envWorkspaceRoot.trim() !== "" ? assertDirectory(envWorkspaceRoot, "WORKSPACE_ROOT") : void 0;
3925
+ if (explicitOverride !== void 0 && explicitOverride.trim() !== "") {
3926
+ const resolved = assertDirectory(explicitOverride, "workspaceRoot");
3927
+ if (allowedRoot !== void 0) {
3928
+ assertWithinAllowedRoot(resolved, allowedRoot);
3929
+ } else {
3930
+ assertGitRepository(resolved);
3931
+ }
3932
+ return resolved;
3933
+ }
3934
+ if (allowedRoot !== void 0) {
3935
+ return allowedRoot;
3936
+ }
3937
+ const fromMarker = findWorkspaceRootUpward(process.cwd());
3938
+ if (fromMarker !== null) {
3939
+ return fromMarker;
3940
+ }
3941
+ return resolve3(process.cwd());
3942
+ }
3943
+
3944
+ // src/tools/tool-utils.ts
3945
+ var import_shared2 = __toESM(require_build2(), 1);
3946
+
3697
3947
  // src/tool-runtime.ts
3698
3948
  var serverConfig = null;
3699
3949
  function initToolRuntime(config) {
@@ -3706,26 +3956,579 @@ function shouldSanitizeResponses() {
3706
3956
  return serverConfig.sanitizeResponses !== false;
3707
3957
  }
3708
3958
 
3709
- // src/tools/agent-runs.ts
3710
- var import_agent_runs2 = __toESM(require_agent_runs_api(), 1);
3711
- var import_agent_run_outcome = __toESM(require_agent_run_outcome_enum(), 1);
3712
- var import_agent_run_status4 = __toESM(require_agent_run_status_enum(), 1);
3713
- import { z } from "zod";
3714
-
3715
- // src/tools/orchestrator-hints.ts
3716
- var import_agent_run_status2 = __toESM(require_agent_run_status_enum(), 1);
3717
- var import_subagent_role = __toESM(require_subagent_role_enum(), 1);
3718
-
3719
- // src/tools/orchestrator-attention.util.ts
3720
- var import_agent_run_status = __toESM(require_agent_run_status_enum(), 1);
3721
- var ATTENTION_MESSAGE_CATEGORY = {
3722
- SHELL: "shell",
3723
- GIT: "git",
3724
- NETWORK: "network",
3725
- SMART_MODE: "smart-mode",
3726
- MCP: "mcp",
3727
- FILE: "file",
3728
- OTHER: "other"
3959
+ // src/tools/tool-utils.ts
3960
+ function jsonResult(data) {
3961
+ return {
3962
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
3963
+ };
3964
+ }
3965
+ function toolError(code, message) {
3966
+ const sanitizedMessage = shouldSanitizeResponses() ? (0, import_shared2.sanitizeMcpErrorMessage)(message) : message;
3967
+ return {
3968
+ isError: true,
3969
+ content: [{ type: "text", text: JSON.stringify({ code, message: sanitizedMessage }) }]
3970
+ };
3971
+ }
3972
+ function toToolError(error) {
3973
+ if (error instanceof RestClientError || error instanceof WorkspaceError) {
3974
+ return toolError(error.code, error.message);
3975
+ }
3976
+ const message = error instanceof Error ? error.message : "Unknown error";
3977
+ return toolError("INTERNAL_ERROR", message);
3978
+ }
3979
+ async function runTool(handler, options) {
3980
+ try {
3981
+ let data = await handler();
3982
+ if (shouldSanitizeResponses()) {
3983
+ data = (0, import_shared2.sanitizeMcpToolResult)(data, { workspaceRoot: options?.workspaceRoot });
3984
+ }
3985
+ return jsonResult(data);
3986
+ } catch (error) {
3987
+ return toToolError(error);
3988
+ }
3989
+ }
3990
+
3991
+ // src/tools/attachments.ts
3992
+ var MAX_ATTACHMENT_COUNT = 20;
3993
+ var MAX_TOTAL_BYTES = 200 * 1024 * 1024;
3994
+ async function fetchAttachments(client, workItemId) {
3995
+ return client.get(import_attachments.ATTACHMENTS_ROUTES.listAttachments(workItemId));
3996
+ }
3997
+ function filterAttachments(items, attachmentIds) {
3998
+ if (attachmentIds === void 0 || attachmentIds.length === 0) {
3999
+ return items;
4000
+ }
4001
+ const idSet = new Set(attachmentIds);
4002
+ return items.filter((item) => idSet.has(item.id));
4003
+ }
4004
+ function assertDownloadLimits(attachments) {
4005
+ if (attachments.length > MAX_ATTACHMENT_COUNT) {
4006
+ throw new RestClientError(
4007
+ "ATTACHMENT_LIMIT_EXCEEDED",
4008
+ `Cannot download more than ${MAX_ATTACHMENT_COUNT} attachments at once (requested ${attachments.length})`
4009
+ );
4010
+ }
4011
+ const totalBytes = attachments.reduce((sum, attachment) => sum + attachment.sizeBytes, 0);
4012
+ if (totalBytes > MAX_TOTAL_BYTES) {
4013
+ throw new RestClientError(
4014
+ "ATTACHMENT_SIZE_LIMIT_EXCEEDED",
4015
+ `Total attachment size ${totalBytes} bytes exceeds limit of ${MAX_TOTAL_BYTES} bytes`
4016
+ );
4017
+ }
4018
+ }
4019
+ async function downloadWorkItemAttachments(client, params) {
4020
+ const { workItemId, workspaceRoot, attachmentIds, overwrite = false, blockSensitiveAttachments = true } = params;
4021
+ const listResponse = await fetchAttachments(client, workItemId);
4022
+ const selected = filterAttachments(listResponse.items, attachmentIds);
4023
+ assertDownloadLimits(selected);
4024
+ const stagingDir = buildStagingDir(workspaceRoot, workItemId);
4025
+ const downloaded = [];
4026
+ const skipped = [];
4027
+ if (attachmentIds !== void 0 && attachmentIds.length > 0) {
4028
+ const selectedIds = new Set(selected.map((item) => item.id));
4029
+ for (const attachmentId of attachmentIds) {
4030
+ if (!selectedIds.has(attachmentId)) {
4031
+ skipped.push({ attachmentId, reason: "not found on work item" });
4032
+ }
4033
+ }
4034
+ }
4035
+ for (const attachment of selected) {
4036
+ if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(attachment.fileName)) {
4037
+ skipped.push({ attachmentId: attachment.id, reason: "blocked_sensitive_filename" });
4038
+ continue;
4039
+ }
4040
+ const stagingPath = buildStagingPath(workspaceRoot, workItemId, attachment.id, attachment.fileName);
4041
+ if (!overwrite && existsSync3(stagingPath)) {
4042
+ skipped.push({ attachmentId: attachment.id, reason: "file already exists (set overwrite=true to replace)" });
4043
+ continue;
4044
+ }
4045
+ const { data } = await client.downloadBinary(import_attachments.ATTACHMENTS_ROUTES.downloadAttachment(workItemId, attachment.id));
4046
+ await writeStagingFile(stagingPath, data);
4047
+ downloaded.push({
4048
+ attachmentId: attachment.id,
4049
+ fileName: attachment.fileName,
4050
+ path: stagingPath,
4051
+ sizeBytes: data.length
4052
+ });
4053
+ }
4054
+ return {
4055
+ workItemId,
4056
+ downloaded,
4057
+ skipped,
4058
+ stagingDir
4059
+ };
4060
+ }
4061
+ async function uploadWorkItemAttachment(client, params) {
4062
+ const { workItemId, workspaceRoot, filePath, blockSensitiveAttachments = true } = params;
4063
+ const confinedFile = readConfinedWorkspaceFile(workspaceRoot, filePath);
4064
+ if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(confinedFile.fileName)) {
4065
+ throw new RestClientError(
4066
+ "BLOCKED_SENSITIVE_FILENAME",
4067
+ `Attachment filename is blocked for MCP upload: ${confinedFile.fileName}`
4068
+ );
4069
+ }
4070
+ const mimeType = detectMimeType(confinedFile.absolutePath);
4071
+ const attachment = await client.uploadMultipart(
4072
+ import_attachments.ATTACHMENTS_ROUTES.uploadAttachment(workItemId),
4073
+ "file",
4074
+ {
4075
+ buffer: confinedFile.buffer,
4076
+ fileName: confinedFile.fileName,
4077
+ mimeType
4078
+ }
4079
+ );
4080
+ return {
4081
+ workItemId,
4082
+ attachment,
4083
+ sourcePath: confinedFile.absolutePath
4084
+ };
4085
+ }
4086
+ function registerAttachmentTools(server, client, config) {
4087
+ server.registerTool(
4088
+ "list_work_item_attachments",
4089
+ {
4090
+ description: "List file attachments for a work item.",
4091
+ inputSchema: {
4092
+ workItemId: z.string().uuid().describe("Work item UUID")
4093
+ }
4094
+ },
4095
+ async ({ workItemId }) => runTool(async () => fetchAttachments(client, workItemId))
4096
+ );
4097
+ server.registerTool(
4098
+ "download_work_item_attachments",
4099
+ {
4100
+ description: "Download work item attachments into the local workspace staging directory (.task-boards/attachments).",
4101
+ inputSchema: {
4102
+ workItemId: z.string().uuid().describe("Work item UUID"),
4103
+ attachmentIds: z.array(z.string().uuid()).optional().describe("Optional subset of attachment UUIDs; downloads all when omitted"),
4104
+ overwrite: z.boolean().optional().default(false).describe("Replace existing staged files when true"),
4105
+ workspaceRoot: z.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
4106
+ }
4107
+ },
4108
+ async ({ workItemId, attachmentIds, overwrite, workspaceRoot }) => {
4109
+ const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
4110
+ return runTool(
4111
+ async () => downloadWorkItemAttachments(client, {
4112
+ workItemId,
4113
+ workspaceRoot: resolvedWorkspaceRoot,
4114
+ attachmentIds,
4115
+ overwrite,
4116
+ blockSensitiveAttachments: config.blockSensitiveAttachments
4117
+ }),
4118
+ { workspaceRoot: resolvedWorkspaceRoot }
4119
+ );
4120
+ }
4121
+ );
4122
+ server.registerTool(
4123
+ "upload_work_item_attachment",
4124
+ {
4125
+ description: "Upload a local workspace file as a work item attachment (multipart field file).",
4126
+ inputSchema: {
4127
+ workItemId: z.string().uuid().describe("Work item UUID"),
4128
+ filePath: z.string().describe("Absolute or workspace-relative path to the file; must stay within workspaceRoot"),
4129
+ workspaceRoot: z.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
4130
+ }
4131
+ },
4132
+ async ({ workItemId, filePath, workspaceRoot }) => {
4133
+ const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
4134
+ return runTool(
4135
+ async () => uploadWorkItemAttachment(client, {
4136
+ workItemId,
4137
+ workspaceRoot: resolvedWorkspaceRoot,
4138
+ filePath,
4139
+ blockSensitiveAttachments: config.blockSensitiveAttachments
4140
+ }),
4141
+ { workspaceRoot: resolvedWorkspaceRoot }
4142
+ );
4143
+ }
4144
+ );
4145
+ }
4146
+
4147
+ // src/tools/comments.ts
4148
+ var import_comments = __toESM(require_comments_api(), 1);
4149
+ import { z as z2 } from "zod";
4150
+ async function fetchComments(client, workItemId) {
4151
+ return client.get(import_comments.COMMENTS_ROUTES.listComments(workItemId));
4152
+ }
4153
+ async function postComment(client, workItemId, body) {
4154
+ const request = { body };
4155
+ return client.post(import_comments.COMMENTS_ROUTES.createComment(workItemId), request);
4156
+ }
4157
+ function registerCommentTools(server, client) {
4158
+ server.registerTool(
4159
+ "list_comments",
4160
+ {
4161
+ description: "List comments for a work item (newest last).",
4162
+ inputSchema: {
4163
+ workItemId: z2.string().uuid().describe("Work item UUID")
4164
+ }
4165
+ },
4166
+ async ({ workItemId }) => runTool(async () => fetchComments(client, workItemId))
4167
+ );
4168
+ server.registerTool(
4169
+ "create_comment",
4170
+ {
4171
+ description: "Create a comment on a work item. Use for orchestrator NEEDS_CLARIFICATION questions before complete_agent_run.",
4172
+ inputSchema: {
4173
+ workItemId: z2.string().uuid().describe("Work item UUID"),
4174
+ body: z2.string().min(1).describe("Comment body (markdown supported)")
4175
+ }
4176
+ },
4177
+ async ({ workItemId, body }) => runTool(async () => postComment(client, workItemId, body))
4178
+ );
4179
+ }
4180
+
4181
+ // src/subagents/sync-project-subagents.ts
4182
+ var import_shared4 = __toESM(require_build2(), 1);
4183
+ import { mkdirSync, writeFileSync } from "node:fs";
4184
+ import { join as join2 } from "node:path";
4185
+ var SAFE_SUBAGENT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4186
+ var MAX_SUBAGENT_SLUG_LENGTH = 64;
4187
+ function isSyncableSlug(slug) {
4188
+ const trimmed = slug.trim();
4189
+ return trimmed.length > 0 && trimmed.length <= MAX_SUBAGENT_SLUG_LENGTH && SAFE_SUBAGENT_SLUG_PATTERN.test(trimmed);
4190
+ }
4191
+ function buildFileContent(subagent) {
4192
+ return (0, import_shared4.buildSubagentFileContentFromBody)({
4193
+ slug: subagent.slug,
4194
+ description: subagent.description,
4195
+ bodyMarkdown: subagent.bodyMarkdown
4196
+ });
4197
+ }
4198
+ async function syncProjectSubagents(client, params) {
4199
+ const { projectId, workspaceRoot } = params;
4200
+ const listResponse = await client.get(import_shared4.PROJECT_SUBAGENTS_ROUTES.listSubagents(projectId));
4201
+ const directory = join2(workspaceRoot, ".cursor", "agents");
4202
+ mkdirSync(directory, { recursive: true });
4203
+ const synced = [];
4204
+ const skipped = [];
4205
+ for (const subagent of listResponse.items) {
4206
+ if (!isSyncableSlug(subagent.slug)) {
4207
+ skipped.push(subagent.slug);
4208
+ continue;
4209
+ }
4210
+ const filePath = join2(directory, `${subagent.slug}.md`);
4211
+ writeFileSync(filePath, buildFileContent(subagent), "utf8");
4212
+ synced.push(subagent.slug);
4213
+ }
4214
+ return { synced, skipped, directory };
4215
+ }
4216
+
4217
+ // src/tools/wait-for-agent-runs.ts
4218
+ var import_agent_runs = __toESM(require_agent_runs_api(), 1);
4219
+ var import_agent_run_status = __toESM(require_agent_run_status_enum(), 1);
4220
+ var DEFAULT_RETRY_BASE_MS = 500;
4221
+ var DEFAULT_RETRY_MAX_MS = 3e4;
4222
+ var DEFAULT_INFLIGHT_LIMIT = 10;
4223
+ function sleep(ms) {
4224
+ return new Promise((resolve4) => {
4225
+ setTimeout(resolve4, ms);
4226
+ });
4227
+ }
4228
+ function isTransientClaimError(error) {
4229
+ if (!(error instanceof RestClientError)) {
4230
+ return false;
4231
+ }
4232
+ if (error.status === void 0) {
4233
+ return error.code === "HTTP_ERROR";
4234
+ }
4235
+ return error.status >= 500;
4236
+ }
4237
+ function computeBackoffMs(consecutiveFailures, baseMs, maxMs) {
4238
+ const exponential = baseMs * 2 ** (consecutiveFailures - 1);
4239
+ return Math.min(exponential, maxMs);
4240
+ }
4241
+ function compareInflightRuns(a, b) {
4242
+ if (a.requiresAttention !== b.requiresAttention) {
4243
+ return a.requiresAttention ? -1 : 1;
4244
+ }
4245
+ const aDispatched = a.dispatchedAt ?? "";
4246
+ const bDispatched = b.dispatchedAt ?? "";
4247
+ return aDispatched.localeCompare(bDispatched);
4248
+ }
4249
+ async function fetchInflightAgentRuns(client, options) {
4250
+ const limit = options.limit ?? DEFAULT_INFLIGHT_LIMIT;
4251
+ const page = await client.get(import_agent_runs.AGENT_RUNS_ROUTES.listAgentRuns(), {
4252
+ projectId: options.projectId,
4253
+ activeOnly: "true",
4254
+ limit: String(limit)
4255
+ });
4256
+ const filtered = page.items.filter(
4257
+ (run) => run.status === import_agent_run_status.AGENT_RUN_STATUS.DISPATCHED || run.status === import_agent_run_status.AGENT_RUN_STATUS.ACKNOWLEDGED
4258
+ );
4259
+ filtered.sort(compareInflightRuns);
4260
+ return filtered.slice(0, limit);
4261
+ }
4262
+ async function pollAgentRuns(client, options, deps) {
4263
+ const { projectId, timeoutMs, pollIntervalMs, limit } = options;
4264
+ if (pollIntervalMs > timeoutMs) {
4265
+ throw new WorkspaceError("VALIDATION_ERROR", "pollInterval must not exceed timeout");
4266
+ }
4267
+ const sleepFn = deps?.sleep ?? sleep;
4268
+ const deadline = Date.now() + timeoutMs;
4269
+ const claimLimit = limit ?? 1;
4270
+ const retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
4271
+ const retryMaxMs = options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS;
4272
+ let consecutiveFailures = 0;
4273
+ while (true) {
4274
+ let response;
4275
+ try {
4276
+ response = await client.post(import_agent_runs.AGENT_RUNS_ROUTES.claimAgentRuns(), {
4277
+ projectId,
4278
+ limit: claimLimit
4279
+ });
4280
+ consecutiveFailures = 0;
4281
+ } catch (error) {
4282
+ if (!isTransientClaimError(error)) {
4283
+ throw error;
4284
+ }
4285
+ if (Date.now() >= deadline) {
4286
+ return { items: [], inflightRuns: [], timedOut: true };
4287
+ }
4288
+ consecutiveFailures += 1;
4289
+ const backoffMs = computeBackoffMs(consecutiveFailures, retryBaseMs, retryMaxMs);
4290
+ const remainingMs2 = deadline - Date.now();
4291
+ await sleepFn(Math.min(backoffMs, remainingMs2));
4292
+ continue;
4293
+ }
4294
+ if (response.items.length > 0) {
4295
+ return { items: response.items, inflightRuns: [], timedOut: false };
4296
+ }
4297
+ const inflightRuns = await fetchInflightAgentRuns(client, { projectId, limit: claimLimit });
4298
+ if (inflightRuns.length > 0) {
4299
+ return { items: [], inflightRuns, timedOut: false };
4300
+ }
4301
+ if (Date.now() >= deadline) {
4302
+ return { items: [], inflightRuns: [], timedOut: true };
4303
+ }
4304
+ const remainingMs = deadline - Date.now();
4305
+ const waitMs = Math.min(pollIntervalMs, remainingMs);
4306
+ await sleepFn(waitMs);
4307
+ }
4308
+ }
4309
+
4310
+ // src/orchestrator/orchestrator-transport.ts
4311
+ var OrchestratorTransport = class {
4312
+ constructor(client, options = {}) {
4313
+ this.client = client;
4314
+ this.options = options;
4315
+ }
4316
+ async claimAndPoll(options, deps) {
4317
+ return pollAgentRuns(this.client, options, deps);
4318
+ }
4319
+ async fetchInflightRuns(projectId, limit) {
4320
+ return fetchInflightAgentRuns(this.client, { projectId, limit });
4321
+ }
4322
+ async ackRun(agentRunId, note) {
4323
+ const body = note !== void 0 ? { note } : void 0;
4324
+ return this.client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.acknowledgeAgentRun(agentRunId), body);
4325
+ }
4326
+ async setAttention(agentRunId, requiresAttention, message) {
4327
+ const body = requiresAttention ? { requiresAttention: true, attentionMessage: message ?? "" } : { requiresAttention: false };
4328
+ return this.client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
4329
+ }
4330
+ async completeRun(agentRunId, body) {
4331
+ return this.client.post(import_agent_runs2.AGENT_RUNS_ROUTES.completeAgentRun(agentRunId), body);
4332
+ }
4333
+ async listAttachments(workItemId) {
4334
+ return fetchAttachments(this.client, workItemId);
4335
+ }
4336
+ async downloadAttachments(workItemId, workspaceRoot, attachmentIds) {
4337
+ return downloadWorkItemAttachments(this.client, {
4338
+ workItemId,
4339
+ workspaceRoot,
4340
+ attachmentIds,
4341
+ blockSensitiveAttachments: this.options.blockSensitiveAttachments ?? true
4342
+ });
4343
+ }
4344
+ async syncSubagents(projectId, workspaceRoot) {
4345
+ return syncProjectSubagents(this.client, { projectId, workspaceRoot });
4346
+ }
4347
+ async createComment(workItemId, body) {
4348
+ return postComment(this.client, workItemId, body);
4349
+ }
4350
+ };
4351
+ function createOrchestratorTransport(client, config) {
4352
+ return new OrchestratorTransport(client, {
4353
+ workspaceRoot: config?.workspaceRoot,
4354
+ blockSensitiveAttachments: config?.blockSensitiveAttachments
4355
+ });
4356
+ }
4357
+
4358
+ // src/orchestrator/active-run-state.ts
4359
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
4360
+ import { join as join3 } from "node:path";
4361
+ var ACTIVE_RUN_STATE_RELATIVE_PATH = ".cursor/orchestrator-active-run.json";
4362
+ var ATTENTION_PENDING_RELATIVE_PATH = ".cursor/orchestrator-attention-pending.json";
4363
+ var ACTIVE_RUN_LEASE_MINUTES = 30;
4364
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4365
+ function isRecord(value) {
4366
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4367
+ }
4368
+ function isUuid(value) {
4369
+ return UUID_PATTERN.test(value);
4370
+ }
4371
+ function isIsoDateTimeString(value) {
4372
+ return !Number.isNaN(Date.parse(value));
4373
+ }
4374
+ function getActiveRunStatePath(workspaceRoot) {
4375
+ return join3(workspaceRoot, ACTIVE_RUN_STATE_RELATIVE_PATH);
4376
+ }
4377
+ function getAttentionPendingPath(workspaceRoot) {
4378
+ return join3(workspaceRoot, ATTENTION_PENDING_RELATIVE_PATH);
4379
+ }
4380
+ function computeExpiresAt(acknowledgedAt, leaseMinutes = ACTIVE_RUN_LEASE_MINUTES) {
4381
+ const expiresMs = Date.parse(acknowledgedAt) + leaseMinutes * 60 * 1e3;
4382
+ return new Date(expiresMs).toISOString();
4383
+ }
4384
+ function ensureCursorDir(workspaceRoot) {
4385
+ const cursorDir = join3(workspaceRoot, ".cursor");
4386
+ if (!existsSync4(cursorDir)) {
4387
+ mkdirSync2(cursorDir, { recursive: true });
4388
+ }
4389
+ }
4390
+ function parseActiveRunState(raw) {
4391
+ if (!isRecord(raw)) {
4392
+ return null;
4393
+ }
4394
+ const version = raw.version;
4395
+ const agentRunId = raw.agentRunId;
4396
+ const workItemId = raw.workItemId;
4397
+ const projectId = raw.projectId;
4398
+ const acknowledgedAt = raw.acknowledgedAt;
4399
+ const expiresAt = raw.expiresAt;
4400
+ if (version !== 1 || typeof agentRunId !== "string" || typeof workItemId !== "string" || typeof projectId !== "string" || typeof acknowledgedAt !== "string" || typeof expiresAt !== "string" || !isUuid(agentRunId) || !isUuid(workItemId) || !isUuid(projectId) || !isIsoDateTimeString(acknowledgedAt) || !isIsoDateTimeString(expiresAt)) {
4401
+ return null;
4402
+ }
4403
+ return {
4404
+ version: 1,
4405
+ agentRunId,
4406
+ workItemId,
4407
+ projectId,
4408
+ acknowledgedAt,
4409
+ expiresAt
4410
+ };
4411
+ }
4412
+ function parseAttentionPendingState(raw) {
4413
+ if (!isRecord(raw)) {
4414
+ return null;
4415
+ }
4416
+ const agentRunId = raw.agentRunId;
4417
+ const reportedAt = raw.reportedAt;
4418
+ if (typeof agentRunId !== "string" || typeof reportedAt !== "string" || !isUuid(agentRunId) || !isIsoDateTimeString(reportedAt)) {
4419
+ return null;
4420
+ }
4421
+ return { agentRunId, reportedAt };
4422
+ }
4423
+ function isActiveRunStateUsable(state, nowMs = Date.now()) {
4424
+ if (state === null) {
4425
+ return false;
4426
+ }
4427
+ const expiresMs = Date.parse(state.expiresAt);
4428
+ if (Number.isNaN(expiresMs)) {
4429
+ return false;
4430
+ }
4431
+ return nowMs < expiresMs;
4432
+ }
4433
+ function readActiveRunState(workspaceRoot) {
4434
+ const filePath = getActiveRunStatePath(workspaceRoot);
4435
+ if (!existsSync4(filePath)) {
4436
+ return null;
4437
+ }
4438
+ try {
4439
+ const content = readFileSync2(filePath, "utf8");
4440
+ const parsed = JSON.parse(content);
4441
+ return parseActiveRunState(parsed);
4442
+ } catch {
4443
+ return null;
4444
+ }
4445
+ }
4446
+ function writeActiveRunState(workspaceRoot, input) {
4447
+ ensureCursorDir(workspaceRoot);
4448
+ const state = {
4449
+ version: 1,
4450
+ agentRunId: input.agentRunId,
4451
+ workItemId: input.workItemId,
4452
+ projectId: input.projectId,
4453
+ acknowledgedAt: input.acknowledgedAt,
4454
+ expiresAt: computeExpiresAt(input.acknowledgedAt)
4455
+ };
4456
+ writeFileSync2(getActiveRunStatePath(workspaceRoot), `${JSON.stringify(state, null, 2)}
4457
+ `, "utf8");
4458
+ return state;
4459
+ }
4460
+ function clearActiveRunState(workspaceRoot) {
4461
+ const filePath = getActiveRunStatePath(workspaceRoot);
4462
+ if (existsSync4(filePath)) {
4463
+ rmSync(filePath, { force: true });
4464
+ }
4465
+ }
4466
+ function readAttentionPendingState(workspaceRoot) {
4467
+ const filePath = getAttentionPendingPath(workspaceRoot);
4468
+ if (!existsSync4(filePath)) {
4469
+ return null;
4470
+ }
4471
+ try {
4472
+ const content = readFileSync2(filePath, "utf8");
4473
+ const parsed = JSON.parse(content);
4474
+ return parseAttentionPendingState(parsed);
4475
+ } catch {
4476
+ return null;
4477
+ }
4478
+ }
4479
+ function writeAttentionPendingState(workspaceRoot, agentRunId) {
4480
+ ensureCursorDir(workspaceRoot);
4481
+ const pending = {
4482
+ agentRunId,
4483
+ reportedAt: (/* @__PURE__ */ new Date()).toISOString()
4484
+ };
4485
+ writeFileSync2(getAttentionPendingPath(workspaceRoot), `${JSON.stringify(pending, null, 2)}
4486
+ `, "utf8");
4487
+ return pending;
4488
+ }
4489
+ function clearAttentionPendingState(workspaceRoot) {
4490
+ const filePath = getAttentionPendingPath(workspaceRoot);
4491
+ if (existsSync4(filePath)) {
4492
+ rmSync(filePath, { force: true });
4493
+ }
4494
+ }
4495
+ function clearOrchestratorBridgeState(workspaceRoot) {
4496
+ clearActiveRunState(workspaceRoot);
4497
+ clearAttentionPendingState(workspaceRoot);
4498
+ }
4499
+
4500
+ // src/workspace/parse-task-boards-yaml.ts
4501
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
4502
+ import { join as join4 } from "node:path";
4503
+ var TASK_BOARDS_FILE2 = ".task-boards.yaml";
4504
+ var VERSION_PATTERN = /^\s*version\s*:\s*1\s*$/m;
4505
+ var PROJECT_ID_PATTERN = /^\s*projectId\s*:\s*["']?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})["']?\s*$/im;
4506
+ function parseTaskBoardsYaml(workspaceRoot) {
4507
+ const filePath = join4(workspaceRoot, TASK_BOARDS_FILE2);
4508
+ if (!existsSync5(filePath)) {
4509
+ return null;
4510
+ }
4511
+ const content = readFileSync3(filePath, "utf8");
4512
+ if (!VERSION_PATTERN.test(content)) {
4513
+ return null;
4514
+ }
4515
+ const match = PROJECT_ID_PATTERN.exec(content);
4516
+ if (match === null) {
4517
+ return null;
4518
+ }
4519
+ return match[1] ?? null;
4520
+ }
4521
+
4522
+ // src/tools/orchestrator-attention.util.ts
4523
+ var import_agent_run_status2 = __toESM(require_agent_run_status_enum(), 1);
4524
+ var ATTENTION_MESSAGE_CATEGORY = {
4525
+ SHELL: "shell",
4526
+ GIT: "git",
4527
+ NETWORK: "network",
4528
+ SMART_MODE: "smart-mode",
4529
+ MCP: "mcp",
4530
+ FILE: "file",
4531
+ OTHER: "other"
3729
4532
  };
3730
4533
  var ATTENTION_MESSAGE_PREFIX_PATTERN = /^\[([^\]]+)\]\s*/;
3731
4534
  var ATTENTION_MESSAGE_TEMPLATES = {
@@ -3780,10 +4583,10 @@ function deriveOrchestratorRunPhase(run) {
3780
4583
  if (run.requiresAttention) {
3781
4584
  return "awaiting_ide_approval";
3782
4585
  }
3783
- if (run.status === import_agent_run_status.AGENT_RUN_STATUS.DISPATCHED) {
4586
+ if (run.status === import_agent_run_status2.AGENT_RUN_STATUS.DISPATCHED) {
3784
4587
  return "dispatched";
3785
4588
  }
3786
- if (run.status === import_agent_run_status.AGENT_RUN_STATUS.ACKNOWLEDGED) {
4589
+ if (run.status === import_agent_run_status2.AGENT_RUN_STATUS.ACKNOWLEDGED) {
3787
4590
  return "acknowledged";
3788
4591
  }
3789
4592
  if (run.acknowledgedAt !== null) {
@@ -3802,1114 +4605,1067 @@ function parseAttentionMessageCategory(message) {
3802
4605
  }
3803
4606
  return ATTENTION_MESSAGE_CATEGORY.OTHER;
3804
4607
  }
4608
+ function formatAttentionMessage(category, detail) {
4609
+ return `[${category}] ${detail.trim()}`;
4610
+ }
3805
4611
  function buildAttentionGuidance(run) {
3806
4612
  const phase = deriveOrchestratorRunPhase(run);
3807
4613
  const detectedCategory = run.attentionMessage !== null ? parseAttentionMessageCategory(run.attentionMessage) : null;
3808
- const reportStep = phase === "awaiting_ide_approval" ? null : `report_agent_attention(${run.id}, message) \u2014 use formatAttentionMessage(category, detail); replaces prior message on same ACK run`;
3809
- const clearStep = run.requiresAttention ? `clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}` : null;
3810
- return {
3811
- phase,
3812
- reportStep,
3813
- clearStep,
3814
- detectedCategory,
3815
- templateExamples: ALL_TEMPLATE_CATEGORIES
3816
- };
3817
- }
3818
-
3819
- // src/tools/orchestrator-hints.ts
3820
- var SLEEPER_POLICY = {
3821
- singleBlockingPoll: true,
3822
- noParallelPoll: true,
3823
- noDuplicateWake: true,
3824
- guidance: "Run exactly one blocking poll (run_orchestrator_once or wait_for_agent_runs) per workspace at a time; do not overlap polls or stack duplicate automation wakes."
3825
- };
3826
- var IDLE_INFLIGHT_CHECK_STEP = "Checked inflight runs (DISPATCHED/ACKNOWLEDGED); none found \u2014 true idle.";
3827
- var IDLE_NO_DUPLICATE_WAKE_STEP = "Schedule at most one follow-up automation wake; do not stack duplicate sleepers while a poll is in flight.";
3828
- var PROCESSING_NO_PARALLEL_POLL_STEP = "Do not start another poll until this batch finishes.";
3829
- var INFLIGHT_DISPATCHED_RECOVERY_STEP = "Orphan DISPATCHED \u2014 resume Task+ack; avoid duplicate Task if subagent already running in this session";
3830
- var IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION = "Do not clear_agent_attention on timeout; badge remains until human resolves in IDE";
3831
- var IDLE_TIMEOUT_NEXT_STEPS = [
3832
- "Call sync_git_releases(projectId) to sync work-item commits",
3833
- IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION,
3834
- "Call wait_for_agent_runs again (or run_orchestrator_once) to continue monitoring"
3835
- ];
3836
- var IDE_ATTENTION_NOT_IN_AWAITING = "IDE attention is not in-awaiting; do not move column; replace attention message on re-report";
3837
- var PROCESSING_NEXT_STEPS = [
3838
- "Call sync_project_subagents(projectId) before Task dispatch when the project uses custom subagent slugs",
3839
- "Agent runs only for STORY items assigned to \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user); assign via update_work_item before moving to agent columns",
3840
- "When a subagent is blocked, call report_agent_attention(agentRunId, message); clear with clear_agent_attention after resolution",
3841
- "After processing all runs: local git commit with work-item:{uuid} tag (always; no push on master/main)",
3842
- "On feature branch (not master/main): git push origin HEAD then sync_git_releases(projectId)",
3843
- "Parallel Task runs OK for same workItemId when agentRole differs (e.g. FRONTEND_DEVELOPER + DEVELOPER)",
3844
- "Claim priority: in-development > in-qa > in-analysis; in-analysis blocked while SERVICE-assigned stories exist in dev/qa",
3845
- "Call run_orchestrator_once or wait_for_agent_runs to continue monitoring"
3846
- ];
3847
- var ATTENTION_PROCESSING_NEXT_STEPS = [
3848
- "Resolve requiresAttention runs before dispatching new Task subagents",
3849
- IDE_ATTENTION_NOT_IN_AWAITING,
3850
- "Call clear_agent_attention(agentRunId) after each blocker is resolved"
3851
- ];
3852
- var RUN_LIFECYCLE_DOC = {
3853
- phases: ["dispatched", "acknowledged", "awaiting_ide_approval", "ready_to_complete"],
3854
- terminalPhase: "complete",
3855
- ideAttentionVsWorkflowAwaiting: "IDE attention (report_agent_attention on ACKNOWLEDGED runs) is not in-awaiting \u2014 do not move the story column for shell/git/network/MCP/Smart Mode approvals. Use complete_agent_run(NEEDS_CLARIFICATION) and in-awaiting only for workflow clarifications."
3856
- };
3857
- function buildTaskStep(run) {
3858
- const subagent = run.payload.suggestedSubagentType ?? "subagent";
3859
- return `Task(subagent_type=${subagent}, prompt=payload.contextSummary)`;
3860
- }
3861
- function buildCompleteStep(run) {
3862
- if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ARCHITECT) {
3863
- return "complete_agent_run after work; architect may set workItemPatch.designerRequired (boolean, default false)";
3864
- }
3865
- return "complete_agent_run after work";
3866
- }
3867
- function buildReportAttentionStep(run) {
3868
- return `report_agent_attention(${run.id}, message) if blocked awaiting human input \u2014 ${ATTENTION_MESSAGE_TEMPLATES.shell.messagePattern.replace("{detail}", "<detail>")}; replaces prior message on re-report`;
3869
- }
3870
- function buildProcessRunAttentionHint(run) {
3871
- const guidance = buildAttentionGuidance(run);
3872
- return {
3873
- phase: guidance.phase,
3874
- requiresAttention: run.requiresAttention,
3875
- message: run.attentionMessage,
3876
- detectedCategory: guidance.detectedCategory,
3877
- reportGuidance: `report_agent_attention(${run.id}, message) using formatAttentionMessage(category, detail); replaces prior message on same ACK run`,
3878
- clearGuidance: guidance.clearStep,
3879
- templateCategories: guidance.templateExamples
3880
- };
3881
- }
3882
- function buildDispatchedProcessRunSteps(run) {
3883
- return [
3884
- "sync_project_subagents(projectId) when payload.suggestedSubagentType is a project custom slug",
3885
- `list_work_item_attachments(${run.workItemId})`,
3886
- "download_work_item_attachments if needed",
3887
- buildTaskStep(run),
3888
- `ack_agent_run(${run.id})`,
3889
- buildReportAttentionStep(run),
3890
- `clear_agent_attention(${run.id}) after human resolves in IDE`,
3891
- buildCompleteStep(run),
3892
- "git commit with work-item tag (local; push only on feature branch)"
3893
- ];
3894
- }
3895
- function buildAwaitingIdeApprovalProcessRunSteps(run) {
3896
- return [
3897
- `clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}`,
3898
- buildCompleteStep(run),
3899
- "git commit with work-item tag (local; push only on feature branch)"
3900
- ];
3901
- }
3902
- function buildAcknowledgedProcessRunSteps(run) {
3903
- return [
3904
- "Resume subagent work (Task may already be running in this session)",
3905
- buildReportAttentionStep(run),
3906
- `clear_agent_attention(${run.id}) after human resolves in IDE`,
3907
- buildCompleteStep(run),
3908
- "git commit with work-item tag (local; push only on feature branch)"
3909
- ];
3910
- }
3911
- function buildProcessRunHint(run, source) {
3912
- const phase = deriveOrchestratorRunPhase(run);
3913
- if (phase === "awaiting_ide_approval") {
3914
- return {
3915
- agentRunId: run.id,
3916
- workItemId: run.workItemId,
3917
- phase,
3918
- source,
3919
- steps: buildAwaitingIdeApprovalProcessRunSteps(run),
3920
- attention: buildProcessRunAttentionHint(run)
3921
- };
3922
- }
3923
- if (phase === "acknowledged") {
3924
- return {
3925
- agentRunId: run.id,
3926
- workItemId: run.workItemId,
3927
- phase,
3928
- source,
3929
- steps: buildAcknowledgedProcessRunSteps(run)
3930
- };
3931
- }
3932
- const steps = buildDispatchedProcessRunSteps(run);
3933
- if (source === "inflight") {
3934
- return {
3935
- agentRunId: run.id,
3936
- workItemId: run.workItemId,
3937
- phase,
3938
- source,
3939
- steps: [INFLIGHT_DISPATCHED_RECOVERY_STEP, ...steps]
3940
- };
3941
- }
3942
- return {
3943
- agentRunId: run.id,
3944
- workItemId: run.workItemId,
3945
- phase,
3946
- source,
3947
- steps
3948
- };
3949
- }
3950
- function buildProcessRuns(items, inflightRuns) {
3951
- const hints = [];
3952
- for (const run of items) {
3953
- if (run.requiresAttention) {
3954
- hints.push(buildProcessRunHint(run, "claimed"));
3955
- }
3956
- }
3957
- for (const run of inflightRuns) {
3958
- if (run.requiresAttention) {
3959
- hints.push(buildProcessRunHint(run, "inflight"));
3960
- }
3961
- }
3962
- for (const run of inflightRuns) {
3963
- if (!run.requiresAttention) {
3964
- hints.push(buildProcessRunHint(run, "inflight"));
3965
- }
3966
- }
3967
- for (const run of items) {
3968
- if (!run.requiresAttention) {
3969
- hints.push(buildProcessRunHint(run, "claimed"));
3970
- }
3971
- }
3972
- return hints;
3973
- }
3974
- function buildAttentionSummary(runs) {
3975
- const blockedRuns = runs.filter((run) => run.requiresAttention);
3976
- return {
3977
- blockedRunCount: blockedRuns.length,
3978
- blockedAgentRunIds: blockedRuns.map((run) => run.id),
3979
- resolveBeforeNewDispatch: blockedRuns.length > 0
3980
- };
3981
- }
3982
- function buildInflightSummary(inflightRuns) {
3983
- if (inflightRuns.length === 0) {
3984
- return void 0;
3985
- }
3986
- return {
3987
- dispatchedCount: inflightRuns.filter((run) => run.status === import_agent_run_status2.AGENT_RUN_STATUS.DISPATCHED).length,
3988
- acknowledgedCount: inflightRuns.filter((run) => run.status === import_agent_run_status2.AGENT_RUN_STATUS.ACKNOWLEDGED).length,
3989
- requiresAttentionCount: inflightRuns.filter((run) => run.requiresAttention).length,
3990
- agentRunIds: inflightRuns.map((run) => run.id)
3991
- };
3992
- }
3993
- function derivePollResultSource(items, inflightRuns) {
3994
- if (items.length > 0) {
3995
- return "claimed";
3996
- }
3997
- if (inflightRuns.length > 0) {
3998
- return "inflight";
3999
- }
4000
- return "idle";
4001
- }
4002
- function buildOrchestratorHints(items, inflightRuns, timedOut) {
4003
- const pollResultSource = derivePollResultSource(items, inflightRuns);
4004
- const allRuns = [...items, ...inflightRuns];
4005
- if (timedOut) {
4006
- return {
4007
- phase: "idle",
4008
- pollResultSource,
4009
- sleeperPolicy: SLEEPER_POLICY,
4010
- nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
4011
- };
4012
- }
4013
- if (allRuns.length === 0) {
4014
- return {
4015
- phase: "idle",
4016
- pollResultSource,
4017
- sleeperPolicy: SLEEPER_POLICY,
4018
- nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
4019
- };
4020
- }
4021
- const hasAttentionRuns = allRuns.some((run) => run.requiresAttention);
4022
- return {
4023
- phase: "processing",
4024
- pollResultSource,
4025
- sleeperPolicy: SLEEPER_POLICY,
4026
- inflightSummary: buildInflightSummary(inflightRuns),
4027
- processRuns: buildProcessRuns(items, inflightRuns),
4028
- attentionSummary: buildAttentionSummary(allRuns),
4029
- runLifecycle: RUN_LIFECYCLE_DOC,
4030
- nextSteps: hasAttentionRuns ? [PROCESSING_NO_PARALLEL_POLL_STEP, ...ATTENTION_PROCESSING_NEXT_STEPS, ...PROCESSING_NEXT_STEPS] : [PROCESSING_NO_PARALLEL_POLL_STEP, ...PROCESSING_NEXT_STEPS]
4031
- };
4032
- }
4033
- function enrichPollResult(items, inflightRuns, timedOut) {
4614
+ const reportStep = phase === "awaiting_ide_approval" ? null : `report_agent_attention(${run.id}, message) \u2014 use formatAttentionMessage(category, detail); replaces prior message on same ACK run`;
4615
+ const clearStep = run.requiresAttention ? `clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}` : null;
4034
4616
  return {
4035
- items,
4036
- inflightRuns,
4037
- timedOut,
4038
- orchestrator: buildOrchestratorHints(items, inflightRuns, timedOut)
4617
+ phase,
4618
+ reportStep,
4619
+ clearStep,
4620
+ detectedCategory,
4621
+ templateExamples: ALL_TEMPLATE_CATEGORIES
4039
4622
  };
4040
4623
  }
4041
4624
 
4042
- // src/tools/tool-utils.ts
4043
- var import_shared2 = __toESM(require_build2(), 1);
4044
-
4045
- // src/workspace/workspace-error.ts
4046
- var WorkspaceError = class extends Error {
4047
- constructor(code, message) {
4048
- super(message);
4049
- this.code = code;
4050
- this.name = "WorkspaceError";
4625
+ // src/attention/shell-category.util.ts
4626
+ var READONLY_SHELL_PATTERN = /^\s*(?:ls(?:\s|$)|cat\s|pwd(?:\s|$)|echo\s|git\s+status(?:\s|$)|git\s+diff(?:\s|$)|git\s+log(?:\s|$))/i;
4627
+ var GIT_DESTRUCTIVE_PATTERN = /\bgit\b.*\b(?:push|commit|checkout|reset|rebase|merge|tag|stash\s+pop|clean|fetch|pull|clone|remote|submodule)\b|\b(?:push|commit|checkout|reset|rebase|merge|tag|stash\s+pop|clean|fetch|pull|clone|remote|submodule)\b.*\bgit\b/i;
4628
+ var NETWORK_PATTERN = /\b(?:curl|wget|npm\s+(?:install|ci|publish|login)|pnpm\s+(?:install|add|i)|yarn\s+(?:install|add)|pip3?\s+install|docker\s+(?:pull|push|run|build|compose)|gh\s+api|npx\s+-y|nc\s|telnet\s|ssh\s)\b/i;
4629
+ function truncateCommand(command, maxLength) {
4630
+ const trimmed = command.trim();
4631
+ if (trimmed.length <= maxLength) {
4632
+ return trimmed;
4051
4633
  }
4052
- };
4634
+ return `${trimmed.slice(0, maxLength - 1)}\u2026`;
4635
+ }
4636
+ function classifyShellCommand(command) {
4637
+ const trimmed = command.trim();
4638
+ if (trimmed === "") {
4639
+ return null;
4640
+ }
4641
+ if (READONLY_SHELL_PATTERN.test(trimmed)) {
4642
+ return null;
4643
+ }
4644
+ if (GIT_DESTRUCTIVE_PATTERN.test(trimmed)) {
4645
+ return "git";
4646
+ }
4647
+ if (NETWORK_PATTERN.test(trimmed)) {
4648
+ return "network";
4649
+ }
4650
+ return "shell";
4651
+ }
4053
4652
 
4054
- // src/tools/tool-utils.ts
4055
- function jsonResult(data) {
4056
- return {
4057
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
4058
- };
4653
+ // src/attention/attention-message.util.ts
4654
+ function buildShellAttentionMessage(category, command) {
4655
+ const truncated = truncateCommand(command, 200);
4656
+ switch (category) {
4657
+ case "git":
4658
+ return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.GIT, `Approve git operation: ${truncated}`);
4659
+ case "network":
4660
+ return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.NETWORK, `Approve network access: ${truncated}`);
4661
+ case "shell":
4662
+ return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.SHELL, `Approve shell command: ${truncated}`);
4663
+ }
4059
4664
  }
4060
- function toolError(code, message) {
4061
- const sanitizedMessage = shouldSanitizeResponses() ? (0, import_shared2.sanitizeMcpErrorMessage)(message) : message;
4062
- return {
4063
- isError: true,
4064
- content: [{ type: "text", text: JSON.stringify({ code, message: sanitizedMessage }) }]
4065
- };
4665
+ function buildMcpAttentionMessage(server, toolName) {
4666
+ const detail = `${server}/${toolName}`.trim();
4667
+ return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.MCP, `Approve MCP / API action: ${detail}`);
4066
4668
  }
4067
- function toToolError(error) {
4068
- if (error instanceof RestClientError || error instanceof WorkspaceError) {
4069
- return toolError(error.code, error.message);
4669
+ function buildSmartModeAttentionMessage(toolName, reason) {
4670
+ const detailReason = reason !== void 0 && reason !== null && reason.trim() !== "" ? reason.trim() : "elevated permissions required";
4671
+ return formatAttentionMessage(
4672
+ ATTENTION_MESSAGE_CATEGORY.SMART_MODE,
4673
+ `Approve Smart Mode: ${toolName} \u2014 ${detailReason}`
4674
+ );
4675
+ }
4676
+
4677
+ // src/attention/hook-payload.util.ts
4678
+ function isRecord2(value) {
4679
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4680
+ }
4681
+ function readString(record, key) {
4682
+ const value = record[key];
4683
+ if (typeof value !== "string" || value.trim() === "") {
4684
+ return null;
4070
4685
  }
4071
- const message = error instanceof Error ? error.message : "Unknown error";
4072
- return toolError("INTERNAL_ERROR", message);
4686
+ return value;
4073
4687
  }
4074
- async function runTool(handler, options) {
4688
+ function parseHookPayloadJson(raw) {
4689
+ const trimmed = raw.trim();
4690
+ if (trimmed === "") {
4691
+ return null;
4692
+ }
4075
4693
  try {
4076
- let data = await handler();
4077
- if (shouldSanitizeResponses()) {
4078
- data = (0, import_shared2.sanitizeMcpToolResult)(data, { workspaceRoot: options?.workspaceRoot });
4694
+ const parsed = JSON.parse(trimmed);
4695
+ if (!isRecord2(parsed)) {
4696
+ return null;
4079
4697
  }
4080
- return jsonResult(data);
4081
- } catch (error) {
4082
- return toToolError(error);
4698
+ return parsed;
4699
+ } catch {
4700
+ return null;
4083
4701
  }
4084
4702
  }
4085
-
4086
- // src/tools/wait-for-agent-runs.ts
4087
- var import_agent_runs = __toESM(require_agent_runs_api(), 1);
4088
- var import_agent_run_status3 = __toESM(require_agent_run_status_enum(), 1);
4089
- var DEFAULT_RETRY_BASE_MS = 500;
4090
- var DEFAULT_RETRY_MAX_MS = 3e4;
4091
- var DEFAULT_INFLIGHT_LIMIT = 10;
4092
- function sleep(ms) {
4093
- return new Promise((resolve4) => {
4094
- setTimeout(resolve4, ms);
4095
- });
4703
+ function extractShellCommand(payload) {
4704
+ return readString(payload, "command");
4096
4705
  }
4097
- function isTransientClaimError(error) {
4098
- if (!(error instanceof RestClientError)) {
4099
- return false;
4706
+ function extractMcpExecution(payload) {
4707
+ const server = readString(payload, "server") ?? readString(payload, "serverName") ?? readString(payload, "mcpServer");
4708
+ const toolName = readString(payload, "toolName") ?? readString(payload, "tool") ?? readString(payload, "mcpToolName");
4709
+ if (server === null || toolName === null) {
4710
+ return null;
4100
4711
  }
4101
- if (error.status === void 0) {
4102
- return error.code === "HTTP_ERROR";
4712
+ return { server, toolName };
4713
+ }
4714
+ function extractPreToolUse(payload) {
4715
+ const toolName = readString(payload, "tool_name") ?? readString(payload, "toolName") ?? readString(payload, "tool") ?? readString(payload, "name");
4716
+ if (toolName === null) {
4717
+ return null;
4103
4718
  }
4104
- return error.status >= 500;
4719
+ const smartModeReason = readString(payload, "block_reason") ?? readString(payload, "smartModeBlockReason") ?? readString(payload, "smart_mode_block_reason") ?? readString(payload, "reason");
4720
+ const permission = readString(payload, "permission");
4721
+ const smartMode = payload.smart_mode ?? payload.smartMode;
4722
+ const hasSmartModeSignal = smartModeReason !== null || permission === "ask" || smartMode === true || smartMode === "blocked";
4723
+ if (!hasSmartModeSignal) {
4724
+ return null;
4725
+ }
4726
+ return { toolName, smartModeReason };
4105
4727
  }
4106
- function computeBackoffMs(consecutiveFailures, baseMs, maxMs) {
4107
- const exponential = baseMs * 2 ** (consecutiveFailures - 1);
4108
- return Math.min(exponential, maxMs);
4728
+ function isAfterHookEvent(event) {
4729
+ return event === "afterShellExecution" || event === "afterMCPExecution" || event === "postToolUse";
4109
4730
  }
4110
- function compareInflightRuns(a, b) {
4111
- if (a.requiresAttention !== b.requiresAttention) {
4112
- return a.requiresAttention ? -1 : 1;
4731
+
4732
+ // src/attention/attention-cli.ts
4733
+ function defaultReadStdin() {
4734
+ return new Promise((resolve4, reject) => {
4735
+ const chunks = [];
4736
+ process.stdin.setEncoding("utf8");
4737
+ process.stdin.on("data", (chunk) => {
4738
+ chunks.push(Buffer.from(chunk));
4739
+ });
4740
+ process.stdin.on("end", () => {
4741
+ resolve4(Buffer.concat(chunks).toString("utf8"));
4742
+ });
4743
+ process.stdin.on("error", reject);
4744
+ });
4745
+ }
4746
+ function resolveBridgeWorkspaceRoot(config) {
4747
+ try {
4748
+ return resolveWorkspaceRoot(void 0, config.workspaceRoot);
4749
+ } catch {
4750
+ return null;
4113
4751
  }
4114
- const aDispatched = a.dispatchedAt ?? "";
4115
- const bDispatched = b.dispatchedAt ?? "";
4116
- return aDispatched.localeCompare(bDispatched);
4117
4752
  }
4118
- async function fetchInflightAgentRuns(client, options) {
4119
- const limit = options.limit ?? DEFAULT_INFLIGHT_LIMIT;
4120
- const page = await client.get(import_agent_runs.AGENT_RUNS_ROUTES.listAgentRuns(), {
4121
- projectId: options.projectId,
4122
- activeOnly: "true",
4123
- limit: String(limit)
4124
- });
4125
- const filtered = page.items.filter(
4126
- (run) => run.status === import_agent_run_status3.AGENT_RUN_STATUS.DISPATCHED || run.status === import_agent_run_status3.AGENT_RUN_STATUS.ACKNOWLEDGED
4127
- );
4128
- filtered.sort(compareInflightRuns);
4129
- return filtered.slice(0, limit);
4753
+ function resolveProjectId(workspaceRoot) {
4754
+ return parseTaskBoardsYaml(workspaceRoot);
4130
4755
  }
4131
- async function pollAgentRuns(client, options, deps) {
4132
- const { projectId, timeoutMs, pollIntervalMs, limit } = options;
4133
- if (pollIntervalMs > timeoutMs) {
4134
- throw new WorkspaceError("VALIDATION_ERROR", "pollInterval must not exceed timeout");
4756
+ function writeActiveRunFromAck(config, run) {
4757
+ if (config.workspaceRoot === void 0) {
4758
+ return;
4135
4759
  }
4136
- const sleepFn = deps?.sleep ?? sleep;
4137
- const deadline = Date.now() + timeoutMs;
4138
- const claimLimit = limit ?? 1;
4139
- const retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
4140
- const retryMaxMs = options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS;
4141
- let consecutiveFailures = 0;
4142
- while (true) {
4143
- let response;
4144
- try {
4145
- response = await client.post(import_agent_runs.AGENT_RUNS_ROUTES.claimAgentRuns(), {
4146
- projectId,
4147
- limit: claimLimit
4148
- });
4149
- consecutiveFailures = 0;
4150
- } catch (error) {
4151
- if (!isTransientClaimError(error)) {
4152
- throw error;
4153
- }
4154
- if (Date.now() >= deadline) {
4155
- return { items: [], inflightRuns: [], timedOut: true };
4156
- }
4157
- consecutiveFailures += 1;
4158
- const backoffMs = computeBackoffMs(consecutiveFailures, retryBaseMs, retryMaxMs);
4159
- const remainingMs2 = deadline - Date.now();
4160
- await sleepFn(Math.min(backoffMs, remainingMs2));
4161
- continue;
4162
- }
4163
- if (response.items.length > 0) {
4164
- return { items: response.items, inflightRuns: [], timedOut: false };
4165
- }
4166
- const inflightRuns = await fetchInflightAgentRuns(client, { projectId, limit: claimLimit });
4167
- if (inflightRuns.length > 0) {
4168
- return { items: [], inflightRuns, timedOut: false };
4169
- }
4170
- if (Date.now() >= deadline) {
4171
- return { items: [], inflightRuns: [], timedOut: true };
4172
- }
4173
- const remainingMs = deadline - Date.now();
4174
- const waitMs = Math.min(pollIntervalMs, remainingMs);
4175
- await sleepFn(waitMs);
4760
+ const workspaceRoot = resolveBridgeWorkspaceRoot(config);
4761
+ if (workspaceRoot === null) {
4762
+ return;
4176
4763
  }
4764
+ const projectId = resolveProjectId(workspaceRoot);
4765
+ if (projectId === null) {
4766
+ return;
4767
+ }
4768
+ const acknowledgedAt = run.acknowledgedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4769
+ writeActiveRunState(workspaceRoot, {
4770
+ agentRunId: run.id,
4771
+ workItemId: run.workItemId,
4772
+ projectId,
4773
+ acknowledgedAt
4774
+ });
4177
4775
  }
4178
-
4179
- // src/tools/agent-runs.ts
4180
- var agentRunStatusValues = Object.values(import_agent_run_status4.AGENT_RUN_STATUS);
4181
- var agentRunOutcomeValues = Object.values(import_agent_run_outcome.AGENT_RUN_OUTCOME);
4182
- var AGENTIC_SDLC_COLUMNS_SUMMARY = "AGENTIC_SDLC has 7 columns: backlog, in-analysis (PRODUCT\u2192ANALYST\u2192ARCHITECT handoffs), in-development (DESIGNER optional, then parallel FRONTEND_DEVELOPER+DEVELOPER), in-qa, in-awaiting, done, released.";
4183
- var COMPLETE_AGENT_RUN_OUTCOME_SUMMARY = "Prerequisite: STORY assignee must be \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user) \u2014 otherwise no agent_run is enqueued or claimed. in-analysis: PRODUCT/ANALYST DEFAULT \u2192 HANDOFF next phase (skip unbound roles); ANALYST SKIP_DESIGN \u2192 in-development; ARCHITECT DEFAULT \u2192 in-development (set workItemPatch.designerRequired). in-development: DESIGNER DEFAULT \u2192 DEVELOPMENT; dev role DEFAULT removes role from pendingDevRoles, moves to in-qa when empty; SKIP_DESIGN skips DESIGNER. in-qa: DEFAULT \u2192 done; HAS_BUGS \u2192 in-development (re-init pendingDevRoles). NEEDS_CLARIFICATION \u2192 in-awaiting. FAILED \u2192 no move. Claim priority: in-development > in-qa > in-analysis (SERVICE-assigned stories only block analysis).";
4184
- function registerAgentRunTools(server, client) {
4185
- server.registerTool(
4186
- "list_agent_runs",
4187
- {
4188
- description: "List agent runs for a project (default status PENDING). Prefer wait_for_agent_runs for orchestrator long-polling.",
4189
- inputSchema: {
4190
- projectId: z.string().uuid().describe("Project UUID (required)"),
4191
- status: z.enum(agentRunStatusValues).optional().describe("Filter by agent run status")
4192
- }
4193
- },
4194
- async ({ status, projectId }) => runTool(async () => {
4195
- const effectiveStatus = status ?? import_agent_run_status4.AGENT_RUN_STATUS.PENDING;
4196
- return client.get(import_agent_runs2.AGENT_RUNS_ROUTES.listAgentRuns(), {
4197
- status: effectiveStatus,
4198
- projectId
4199
- });
4200
- })
4201
- );
4202
- server.registerTool(
4203
- "wait_for_agent_runs",
4204
- {
4205
- description: "Long-poll agent runs for a project: atomically claims PENDING runs (POST /agent-runs/claim). On empty claim, immediately lists inflight DISPATCHED/ACKNOWLEDGED runs (GET activeOnly) for orphan recovery before sleeping. Returns items (newly claimed), inflightRuns (resume candidates), and orchestrator hints. First claim is immediate; subsequent polls sleep pollInterval seconds only when both claim and inflight are empty.",
4206
- inputSchema: {
4207
- projectId: z.string().uuid().describe("Project UUID"),
4208
- timeout: z.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
4209
- pollInterval: z.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
4210
- limit: z.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)")
4211
- }
4212
- },
4213
- async ({ projectId, timeout, pollInterval, limit }) => runTool(async () => {
4214
- const pollResult = await pollAgentRuns(client, {
4215
- projectId,
4216
- timeoutMs: timeout * 1e3,
4217
- pollIntervalMs: pollInterval * 1e3,
4218
- limit
4219
- });
4220
- return enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
4221
- })
4222
- );
4223
- server.registerTool(
4224
- "ack_agent_run",
4225
- {
4226
- description: "Acknowledge agent run delivery after Task subagent has been dispatched. Requires status DISPATCHED (DISPATCHED \u2192 ACKNOWLEDGED).",
4227
- inputSchema: {
4228
- agentRunId: z.string().uuid().describe("Agent run UUID"),
4229
- note: z.string().nullable().optional().describe("Optional acknowledgment note")
4230
- }
4231
- },
4232
- async ({ agentRunId, note }) => runTool(async () => {
4233
- const body = note !== void 0 ? { note } : void 0;
4234
- return client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.acknowledgeAgentRun(agentRunId), body);
4235
- })
4236
- );
4237
- server.registerTool(
4238
- "report_agent_attention",
4239
- {
4240
- description: "Signal that the in-flight subagent needs human attention while the run is ACKNOWLEDGED. Sets requiresAttention on the agent run for orchestrator/UI visibility.",
4241
- inputSchema: {
4242
- agentRunId: z.string().uuid().describe("Agent run UUID"),
4243
- message: z.string().min(1).describe("Why human attention is needed")
4244
- }
4245
- },
4246
- async ({ agentRunId, message }) => runTool(async () => {
4247
- const body = {
4248
- requiresAttention: true,
4249
- attentionMessage: message
4250
- };
4251
- return client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
4252
- })
4253
- );
4254
- server.registerTool(
4255
- "clear_agent_attention",
4256
- {
4257
- description: "Clear the human-attention flag after the blocker is resolved. Requires the run to still be ACKNOWLEDGED.",
4258
- inputSchema: {
4259
- agentRunId: z.string().uuid().describe("Agent run UUID")
4260
- }
4261
- },
4262
- async ({ agentRunId }) => runTool(async () => {
4263
- const body = {
4264
- requiresAttention: false
4265
- };
4266
- return client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
4267
- })
4268
- );
4269
- server.registerTool(
4270
- "complete_agent_run",
4271
- {
4272
- description: `Complete agent run: apply AGENTIC_SDLC workflow transition and optional work item patch. ${AGENTIC_SDLC_COLUMNS_SUMMARY} Outcomes: ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`,
4273
- inputSchema: {
4274
- agentRunId: z.string().uuid().describe("Agent run UUID"),
4275
- outcome: z.enum(agentRunOutcomeValues).optional().describe(
4276
- `Completion outcome (DEFAULT, SKIP_DESIGN, HAS_BUGS, NEEDS_CLARIFICATION, FAILED). ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`
4277
- ),
4278
- note: z.string().nullable().optional().describe("Optional completion note"),
4279
- workItemPatch: z.object({
4280
- description: z.string().nullable().optional().describe("Updated work item description"),
4281
- acceptanceCriteria: z.string().nullable().optional().describe("Updated acceptance criteria"),
4282
- designerRequired: z.boolean().optional().describe("Architect only: whether DESIGNER phase runs before development")
4283
- }).nullable().optional().describe("Optional work item field updates")
4284
- }
4285
- },
4286
- async ({ agentRunId, outcome, note, workItemPatch }) => runTool(async () => {
4287
- const body = {};
4288
- if (outcome !== void 0) {
4289
- body.outcome = outcome;
4290
- }
4291
- if (note !== void 0) {
4292
- body.note = note;
4293
- }
4294
- if (workItemPatch !== void 0 && workItemPatch !== null) {
4295
- body.workItemPatch = workItemPatch;
4296
- }
4297
- const hasBody = outcome !== void 0 || note !== void 0 || workItemPatch !== void 0 && workItemPatch !== null;
4298
- return client.post(
4299
- import_agent_runs2.AGENT_RUNS_ROUTES.completeAgentRun(agentRunId),
4300
- hasBody ? body : void 0
4301
- );
4302
- })
4303
- );
4304
- }
4305
-
4306
- // src/tools/orchestrator.ts
4307
- import { z as z2 } from "zod";
4308
-
4309
- // src/workspace/resolve-project.ts
4310
- var import_projects = __toESM(require_projects_api(), 1);
4311
- import { basename } from "node:path";
4312
-
4313
- // src/workspace/normalize-token.ts
4314
- function normalizeToken(value) {
4315
- return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "");
4776
+ function clearActiveRunAfterComplete(config) {
4777
+ if (config.workspaceRoot === void 0) {
4778
+ return;
4779
+ }
4780
+ const workspaceRoot = resolveBridgeWorkspaceRoot(config);
4781
+ if (workspaceRoot === null) {
4782
+ return;
4783
+ }
4784
+ clearActiveRunState(workspaceRoot);
4785
+ clearAttentionPendingState(workspaceRoot);
4316
4786
  }
4317
-
4318
- // src/workspace/auto-match-project.ts
4319
- function toCandidate(project, matchedBy) {
4320
- return {
4321
- projectId: project.id,
4322
- name: project.name,
4323
- key: project.key,
4324
- presetCode: project.presetCode,
4325
- matchedBy
4326
- };
4787
+ function logCliError(deps, message) {
4788
+ if (deps.logError !== void 0) {
4789
+ deps.logError(message);
4790
+ return;
4791
+ }
4792
+ console.error(message);
4327
4793
  }
4328
- function autoMatchProject(folderToken, projects) {
4329
- const normalizedFolder = normalizeToken(folderToken);
4330
- if (normalizedFolder.length === 0) {
4331
- return { kind: "none" };
4794
+ function readFlagValue(args, flag) {
4795
+ const index = args.indexOf(flag);
4796
+ if (index === -1 || index + 1 >= args.length) {
4797
+ return null;
4332
4798
  }
4333
- const keyMatches = [];
4334
- const nameMatches = [];
4335
- for (const project of projects) {
4336
- const input = {
4337
- id: project.id,
4338
- name: project.name,
4339
- key: project.key,
4340
- presetCode: project.presetCode
4341
- };
4342
- if (normalizeToken(project.key) === normalizedFolder) {
4343
- keyMatches.push(input);
4344
- }
4345
- if (normalizeToken(project.name) === normalizedFolder) {
4346
- nameMatches.push(input);
4799
+ return args[index + 1] ?? null;
4800
+ }
4801
+ function removeConsumedFlags(args) {
4802
+ const flagsWithValues = /* @__PURE__ */ new Set([
4803
+ "--category",
4804
+ "--detail",
4805
+ "--event",
4806
+ "--agent-run-id",
4807
+ "--work-item-id",
4808
+ "--project-id",
4809
+ "--acknowledged-at"
4810
+ ]);
4811
+ const result = [];
4812
+ for (let index = 0; index < args.length; index += 1) {
4813
+ const arg = args[index];
4814
+ if (flagsWithValues.has(arg)) {
4815
+ index += 1;
4816
+ continue;
4347
4817
  }
4818
+ result.push(arg);
4348
4819
  }
4349
- const uniqueById = /* @__PURE__ */ new Map();
4350
- for (const project of keyMatches) {
4351
- uniqueById.set(project.id, { project, matchedBy: "key" });
4820
+ return result;
4821
+ }
4822
+ async function reportAttention(deps, agentRunId, message, workspaceRoot) {
4823
+ const transport = createOrchestratorTransport(deps.client);
4824
+ await transport.setAttention(agentRunId, true, message);
4825
+ writeAttentionPendingState(workspaceRoot, agentRunId);
4826
+ }
4827
+ async function clearAttention(deps, workspaceRoot) {
4828
+ const pending = readAttentionPendingState(workspaceRoot);
4829
+ if (pending === null) {
4830
+ return;
4352
4831
  }
4353
- for (const project of nameMatches) {
4354
- if (!uniqueById.has(project.id)) {
4355
- uniqueById.set(project.id, { project, matchedBy: "name" });
4356
- }
4832
+ const active = readActiveRunState(workspaceRoot);
4833
+ if (!isActiveRunStateUsable(active) || active.agentRunId !== pending.agentRunId) {
4834
+ return;
4357
4835
  }
4358
- const matches = [...uniqueById.values()];
4359
- if (matches.length === 1) {
4360
- const match = matches[0];
4361
- if (match === void 0) {
4362
- return { kind: "none" };
4363
- }
4364
- return { kind: "single", project: match.project, matchedBy: match.matchedBy };
4836
+ const transport = createOrchestratorTransport(deps.client);
4837
+ try {
4838
+ await transport.setAttention(active.agentRunId, false);
4839
+ } catch (error) {
4840
+ const message = error instanceof Error ? error.message : "Failed to clear agent attention";
4841
+ logCliError(deps, message);
4842
+ } finally {
4843
+ clearAttentionPendingState(workspaceRoot);
4365
4844
  }
4366
- if (matches.length > 1) {
4367
- return {
4368
- kind: "candidates",
4369
- candidates: matches.map((match) => toCandidate(match.project, match.matchedBy))
4370
- };
4845
+ }
4846
+ async function handleAttentionReport(deps, args) {
4847
+ const category = readFlagValue(args, "--category");
4848
+ const detail = readFlagValue(args, "--detail");
4849
+ if (category === null || detail === null) {
4850
+ logCliError(deps, "attention report requires --category and --detail");
4851
+ return 0;
4852
+ }
4853
+ const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
4854
+ if (workspaceRoot === null) {
4855
+ return 0;
4856
+ }
4857
+ const active = readActiveRunState(workspaceRoot);
4858
+ if (!isActiveRunStateUsable(active)) {
4859
+ return 0;
4860
+ }
4861
+ const message = `[${category}] ${detail.trim()}`;
4862
+ try {
4863
+ await reportAttention(deps, active.agentRunId, message, workspaceRoot);
4864
+ } catch (error) {
4865
+ const messageText = error instanceof Error ? error.message : "Failed to report agent attention";
4866
+ logCliError(deps, messageText);
4371
4867
  }
4372
- return { kind: "none" };
4868
+ return 0;
4373
4869
  }
4374
-
4375
- // src/workspace/parse-ide-rules.ts
4376
- import { existsSync, readdirSync, readFileSync } from "node:fs";
4377
- import { join } from "node:path";
4378
- var PROJECT_ID_LINE_PATTERN = /projectId\s*[:=]\s*["']?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi;
4379
- var TASK_BOARDS_UUID_PATTERN = /task-boards[^\n\r]*?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi;
4380
- function extractUuids(content) {
4381
- const ids = /* @__PURE__ */ new Set();
4382
- for (const pattern of [PROJECT_ID_LINE_PATTERN, TASK_BOARDS_UUID_PATTERN]) {
4383
- pattern.lastIndex = 0;
4384
- let match = pattern.exec(content);
4385
- while (match !== null) {
4386
- const id = match[1];
4387
- if (id !== void 0) {
4388
- ids.add(id.toLowerCase());
4389
- }
4390
- match = pattern.exec(content);
4870
+ async function handleAttentionReportFromHook(deps, args) {
4871
+ const eventRaw = readFlagValue(args, "--event");
4872
+ if (eventRaw === null) {
4873
+ logCliError(deps, "attention report-from-hook requires --event");
4874
+ return 0;
4875
+ }
4876
+ const event = eventRaw;
4877
+ if (isAfterHookEvent(event)) {
4878
+ return 0;
4879
+ }
4880
+ const readStdin = deps.readStdin ?? defaultReadStdin;
4881
+ const stdin = await readStdin();
4882
+ const payload = parseHookPayloadJson(stdin);
4883
+ if (payload === null) {
4884
+ return 0;
4885
+ }
4886
+ const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
4887
+ if (workspaceRoot === null) {
4888
+ return 0;
4889
+ }
4890
+ const active = readActiveRunState(workspaceRoot);
4891
+ if (!isActiveRunStateUsable(active)) {
4892
+ return 0;
4893
+ }
4894
+ let message = null;
4895
+ if (event === "beforeShellExecution") {
4896
+ const command = extractShellCommand(payload);
4897
+ if (command === null) {
4898
+ return 0;
4391
4899
  }
4900
+ const category = classifyShellCommand(command);
4901
+ if (category === null) {
4902
+ return 0;
4903
+ }
4904
+ message = buildShellAttentionMessage(category, command);
4905
+ } else if (event === "beforeMCPExecution") {
4906
+ const mcp = extractMcpExecution(payload);
4907
+ if (mcp === null) {
4908
+ return 0;
4909
+ }
4910
+ message = buildMcpAttentionMessage(mcp.server, mcp.toolName);
4911
+ } else if (event === "preToolUse") {
4912
+ const preTool = extractPreToolUse(payload);
4913
+ if (preTool === null) {
4914
+ return 0;
4915
+ }
4916
+ message = buildSmartModeAttentionMessage(preTool.toolName, preTool.smartModeReason);
4392
4917
  }
4393
- return ids;
4918
+ if (message === null) {
4919
+ return 0;
4920
+ }
4921
+ try {
4922
+ await reportAttention(deps, active.agentRunId, message, workspaceRoot);
4923
+ } catch (error) {
4924
+ const messageText = error instanceof Error ? error.message : "Failed to report agent attention from hook";
4925
+ logCliError(deps, messageText);
4926
+ }
4927
+ return 0;
4394
4928
  }
4395
- function parseIdeRules(workspaceRoot) {
4396
- const rulesDir = join(workspaceRoot, ".cursor", "rules");
4397
- if (!existsSync(rulesDir)) {
4398
- return { status: "missing" };
4929
+ async function handleAttentionClear(deps) {
4930
+ const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
4931
+ if (workspaceRoot === null) {
4932
+ return 0;
4399
4933
  }
4400
- const entries = readdirSync(rulesDir, { withFileTypes: true });
4401
- const allIds = /* @__PURE__ */ new Set();
4402
- for (const entry of entries) {
4403
- if (!entry.isFile()) {
4404
- continue;
4934
+ await clearAttention(deps, workspaceRoot);
4935
+ return 0;
4936
+ }
4937
+ function handleOrchestratorWriteActiveRun(deps, args) {
4938
+ const agentRunId = readFlagValue(args, "--agent-run-id");
4939
+ const workItemId = readFlagValue(args, "--work-item-id");
4940
+ const projectId = readFlagValue(args, "--project-id");
4941
+ const acknowledgedAt = readFlagValue(args, "--acknowledged-at") ?? (/* @__PURE__ */ new Date()).toISOString();
4942
+ if (agentRunId === null || workItemId === null || projectId === null) {
4943
+ logCliError(deps, "orchestrator write-active-run requires --agent-run-id, --work-item-id, --project-id");
4944
+ return 0;
4945
+ }
4946
+ const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
4947
+ if (workspaceRoot === null) {
4948
+ return 0;
4949
+ }
4950
+ writeActiveRunState(workspaceRoot, {
4951
+ agentRunId,
4952
+ workItemId,
4953
+ projectId,
4954
+ acknowledgedAt
4955
+ });
4956
+ return 0;
4957
+ }
4958
+ function handleOrchestratorClearActiveRun(deps) {
4959
+ const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
4960
+ if (workspaceRoot === null) {
4961
+ return 0;
4962
+ }
4963
+ clearOrchestratorBridgeState(workspaceRoot);
4964
+ return 0;
4965
+ }
4966
+ async function runAttentionCli(argv, deps) {
4967
+ const args = removeConsumedFlags(argv);
4968
+ const topLevel = args[0];
4969
+ if (topLevel === "attention") {
4970
+ const subcommand = args[1];
4971
+ if (subcommand === "report") {
4972
+ return handleAttentionReport(deps, argv);
4405
4973
  }
4406
- if (!entry.name.endsWith(".mdc") && !entry.name.endsWith(".md")) {
4407
- continue;
4974
+ if (subcommand === "report-from-hook") {
4975
+ return handleAttentionReportFromHook(deps, argv);
4408
4976
  }
4409
- const content = readFileSync(join(rulesDir, entry.name), "utf8");
4410
- for (const id of extractUuids(content)) {
4411
- allIds.add(id);
4977
+ if (subcommand === "clear") {
4978
+ return handleAttentionClear(deps);
4412
4979
  }
4980
+ logCliError(deps, `Unknown attention subcommand: ${subcommand ?? "(missing)"}`);
4981
+ return 0;
4413
4982
  }
4414
- if (allIds.size === 0) {
4415
- return { status: "missing" };
4416
- }
4417
- if (allIds.size === 1) {
4418
- const projectId = [...allIds][0];
4419
- if (projectId === void 0) {
4420
- return { status: "missing" };
4983
+ if (topLevel === "orchestrator") {
4984
+ const subcommand = args[1];
4985
+ if (subcommand === "write-active-run") {
4986
+ return handleOrchestratorWriteActiveRun(deps, argv);
4421
4987
  }
4422
- return { status: "found", projectId };
4988
+ if (subcommand === "clear-active-run") {
4989
+ return handleOrchestratorClearActiveRun(deps);
4990
+ }
4991
+ logCliError(deps, `Unknown orchestrator subcommand: ${subcommand ?? "(missing)"}`);
4992
+ return 0;
4423
4993
  }
4424
- return { status: "ambiguous" };
4994
+ return 1;
4995
+ }
4996
+ function isAttentionCliInvocation(argv) {
4997
+ const topLevel = argv[0];
4998
+ return topLevel === "attention" || topLevel === "orchestrator";
4425
4999
  }
4426
5000
 
4427
- // src/workspace/parse-task-boards-yaml.ts
4428
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
4429
- import { join as join2 } from "node:path";
4430
- var TASK_BOARDS_FILE = ".task-boards.yaml";
4431
- var VERSION_PATTERN = /^\s*version\s*:\s*1\s*$/m;
4432
- var PROJECT_ID_PATTERN = /^\s*projectId\s*:\s*["']?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})["']?\s*$/im;
4433
- function parseTaskBoardsYaml(workspaceRoot) {
4434
- const filePath = join2(workspaceRoot, TASK_BOARDS_FILE);
4435
- if (!existsSync2(filePath)) {
4436
- return null;
5001
+ // src/config.ts
5002
+ var import_api = __toESM(require_build(), 1);
5003
+ function resolveSanitizeResponses() {
5004
+ const raw = process.env.TASK_BOARDS_MCP_SANITIZE;
5005
+ if (raw === void 0 || raw.trim() === "") {
5006
+ return true;
4437
5007
  }
4438
- const content = readFileSync2(filePath, "utf8");
4439
- if (!VERSION_PATTERN.test(content)) {
4440
- return null;
5008
+ const normalized = raw.trim().toLowerCase();
5009
+ return normalized !== "false" && normalized !== "0" && normalized !== "no";
5010
+ }
5011
+ function resolveBlockSensitiveAttachments() {
5012
+ const raw = process.env.TASK_BOARDS_MCP_BLOCK_SENSITIVE_ATTACHMENTS;
5013
+ if (raw === void 0 || raw.trim() === "") {
5014
+ return true;
4441
5015
  }
4442
- const match = PROJECT_ID_PATTERN.exec(content);
4443
- if (match === null) {
4444
- return null;
5016
+ const normalized = raw.trim().toLowerCase();
5017
+ return normalized !== "false" && normalized !== "0" && normalized !== "no";
5018
+ }
5019
+ function loadConfig() {
5020
+ const apiUrl = process.env.TASK_BOARDS_API_URL ?? "http://localhost:3000";
5021
+ const apiToken = process.env.TASK_BOARDS_API_TOKEN;
5022
+ const workspaceRoot = process.env.WORKSPACE_ROOT;
5023
+ if (process.env.NODE_ENV === "production" && (apiToken === void 0 || apiToken.trim() === "")) {
5024
+ console.error("WARNING: TASK_BOARDS_API_TOKEN is not set. MCP server cannot authenticate to the Task Boards API.");
4445
5025
  }
4446
- return match[1] ?? null;
5026
+ return {
5027
+ apiUrl: `${apiUrl.replace(/\/$/, "")}${import_api.API_V1_PREFIX}`,
5028
+ apiToken,
5029
+ workspaceRoot: workspaceRoot !== void 0 && workspaceRoot.trim() !== "" ? workspaceRoot : void 0,
5030
+ sanitizeResponses: resolveSanitizeResponses(),
5031
+ blockSensitiveAttachments: resolveBlockSensitiveAttachments()
5032
+ };
5033
+ }
5034
+
5035
+ // src/index.ts
5036
+ import { createRequire } from "node:module";
5037
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5038
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5039
+
5040
+ // src/tools/agent-runs.ts
5041
+ var import_agent_runs3 = __toESM(require_agent_runs_api(), 1);
5042
+ var import_agent_run_outcome = __toESM(require_agent_run_outcome_enum(), 1);
5043
+ var import_agent_run_status4 = __toESM(require_agent_run_status_enum(), 1);
5044
+ import { z as z3 } from "zod";
5045
+
5046
+ // src/tools/orchestrator-hints.ts
5047
+ var import_agent_run_status3 = __toESM(require_agent_run_status_enum(), 1);
5048
+ var import_subagent_role = __toESM(require_subagent_role_enum(), 1);
5049
+ var SLEEPER_POLICY = {
5050
+ singleBlockingPoll: true,
5051
+ noParallelPoll: true,
5052
+ noDuplicateWake: true,
5053
+ guidance: "Run exactly one blocking poll (run_orchestrator_once or wait_for_agent_runs) per workspace at a time; do not overlap polls or stack duplicate automation wakes."
5054
+ };
5055
+ var IDLE_INFLIGHT_CHECK_STEP = "Checked inflight runs (DISPATCHED/ACKNOWLEDGED); none found \u2014 true idle.";
5056
+ var IDLE_NO_DUPLICATE_WAKE_STEP = "Schedule at most one follow-up automation wake; do not stack duplicate sleepers while a poll is in flight.";
5057
+ var PROCESSING_NO_PARALLEL_POLL_STEP = "Do not start another poll until this batch finishes.";
5058
+ var INFLIGHT_DISPATCHED_RECOVERY_STEP = "Orphan DISPATCHED \u2014 resume Task+ack; avoid duplicate Task if subagent already running in this session";
5059
+ var IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION = "Do not clear_agent_attention on timeout; badge remains until human resolves in IDE";
5060
+ var IDLE_TIMEOUT_NEXT_STEPS = [
5061
+ "Call sync_git_releases(projectId) to sync work-item commits",
5062
+ IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION,
5063
+ "Call wait_for_agent_runs again (or run_orchestrator_once) to continue monitoring"
5064
+ ];
5065
+ var IDE_ATTENTION_NOT_IN_AWAITING = "IDE attention is not in-awaiting; do not move column; replace attention message on re-report";
5066
+ var PROCESSING_NEXT_STEPS = [
5067
+ "Call sync_project_subagents(projectId) before Task dispatch when the project uses custom subagent slugs",
5068
+ "Agent runs only for STORY items assigned to \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user); assign via update_work_item before moving to agent columns",
5069
+ "When a subagent is blocked, call report_agent_attention(agentRunId, message); clear with clear_agent_attention after resolution",
5070
+ "After processing all runs: local git commit with work-item:{uuid} tag (skip commit and sync_git_releases when any run completed with SKIP_DEV)",
5071
+ "On feature branch (not master/main): git push origin HEAD then sync_git_releases(projectId)",
5072
+ "Parallel Task runs OK for same workItemId when agentRole differs (e.g. FRONTEND_DEVELOPER + DEVELOPER)",
5073
+ "Claim priority: in-development > in-qa > in-analysis; in-analysis blocked while SERVICE-assigned stories exist in dev/qa",
5074
+ "Call run_orchestrator_once or wait_for_agent_runs to continue monitoring"
5075
+ ];
5076
+ var ATTENTION_PROCESSING_NEXT_STEPS = [
5077
+ "Resolve requiresAttention runs before dispatching new Task subagents",
5078
+ IDE_ATTENTION_NOT_IN_AWAITING,
5079
+ "Call clear_agent_attention(agentRunId) after each blocker is resolved"
5080
+ ];
5081
+ var RUN_LIFECYCLE_DOC = {
5082
+ phases: ["dispatched", "acknowledged", "awaiting_ide_approval", "ready_to_complete"],
5083
+ terminalPhase: "complete",
5084
+ ideAttentionVsWorkflowAwaiting: "IDE attention (report_agent_attention on ACKNOWLEDGED runs) is not in-awaiting \u2014 do not move the story column for shell/git/network/MCP/Smart Mode approvals. Use complete_agent_run(NEEDS_CLARIFICATION) and in-awaiting only for workflow clarifications."
5085
+ };
5086
+ function buildTaskStep(run) {
5087
+ const subagent = run.payload.suggestedSubagentType ?? "subagent";
5088
+ return `Task(subagent_type=${subagent}, prompt=payload.contextSummary)`;
5089
+ }
5090
+ function buildCompleteStep(run) {
5091
+ if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ARCHITECT) {
5092
+ return "complete_agent_run after work; architect may set workItemPatch.designerRequired (boolean, default false)";
5093
+ }
5094
+ if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ANALYST) {
5095
+ return "complete_agent_run after work; use outcome SKIP_DEV when codeChangesRequired=false (released without git commit)";
5096
+ }
5097
+ return "complete_agent_run after work";
5098
+ }
5099
+ function buildGitCommitStep(run) {
5100
+ if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ANALYST) {
5101
+ return "skip git commit and sync_git_releases when outcome SKIP_DEV; otherwise git commit with work-item tag (local; push only on feature branch)";
5102
+ }
5103
+ return "git commit with work-item tag (local; push only on feature branch)";
5104
+ }
5105
+ function buildReportAttentionStep(run) {
5106
+ return `report_agent_attention(${run.id}, message) if blocked awaiting human input \u2014 ${ATTENTION_MESSAGE_TEMPLATES.shell.messagePattern.replace("{detail}", "<detail>")}; replaces prior message on re-report`;
5107
+ }
5108
+ function buildProcessRunAttentionHint(run) {
5109
+ const guidance = buildAttentionGuidance(run);
5110
+ return {
5111
+ phase: guidance.phase,
5112
+ requiresAttention: run.requiresAttention,
5113
+ message: run.attentionMessage,
5114
+ detectedCategory: guidance.detectedCategory,
5115
+ reportGuidance: `report_agent_attention(${run.id}, message) using formatAttentionMessage(category, detail); replaces prior message on same ACK run`,
5116
+ clearGuidance: guidance.clearStep,
5117
+ templateCategories: guidance.templateExamples
5118
+ };
5119
+ }
5120
+ function buildDispatchedProcessRunSteps(run) {
5121
+ return [
5122
+ "sync_project_subagents(projectId) when payload.suggestedSubagentType is a project custom slug",
5123
+ `list_work_item_attachments(${run.workItemId})`,
5124
+ "download_work_item_attachments if needed",
5125
+ buildTaskStep(run),
5126
+ `ack_agent_run(${run.id})`,
5127
+ buildReportAttentionStep(run),
5128
+ `clear_agent_attention(${run.id}) after human resolves in IDE`,
5129
+ buildCompleteStep(run),
5130
+ buildGitCommitStep(run)
5131
+ ];
4447
5132
  }
4448
-
4449
- // src/workspace/resolve-workspace-root.ts
4450
- import { existsSync as existsSync3, statSync } from "node:fs";
4451
- import { dirname, isAbsolute, join as join3, relative, resolve } from "node:path";
4452
- var TASK_BOARDS_FILE2 = ".task-boards.yaml";
4453
- var MAX_UPWARD_LEVELS = 20;
4454
- function assertDirectory(path, source) {
4455
- const absolutePath = resolve(path);
4456
- if (!existsSync3(absolutePath)) {
4457
- throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} does not exist: ${absolutePath}`);
4458
- }
4459
- const stats = statSync(absolutePath);
4460
- if (!stats.isDirectory()) {
4461
- throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} is not a directory: ${absolutePath}`);
4462
- }
4463
- return absolutePath;
5133
+ function buildAwaitingIdeApprovalProcessRunSteps(run) {
5134
+ return [
5135
+ `clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}`,
5136
+ buildCompleteStep(run),
5137
+ buildGitCommitStep(run)
5138
+ ];
4464
5139
  }
4465
- function assertWithinAllowedRoot(target, allowedRoot) {
4466
- const relativePath = relative(allowedRoot, target);
4467
- const isNested = relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath);
4468
- if (target !== allowedRoot && !isNested) {
4469
- throw new WorkspaceError(
4470
- "WORKSPACE_OUT_OF_BOUNDS",
4471
- `workspaceRoot is outside the allowed WORKSPACE_ROOT: ${target}`
4472
- );
4473
- }
5140
+ function buildAcknowledgedProcessRunSteps(run) {
5141
+ return [
5142
+ "Resume subagent work (Task may already be running in this session)",
5143
+ buildReportAttentionStep(run),
5144
+ `clear_agent_attention(${run.id}) after human resolves in IDE`,
5145
+ buildCompleteStep(run),
5146
+ buildGitCommitStep(run)
5147
+ ];
4474
5148
  }
4475
- function assertGitRepository(absolutePath) {
4476
- if (!existsSync3(join3(absolutePath, ".git"))) {
4477
- throw new WorkspaceError("WORKSPACE_NOT_GIT_REPO", `workspaceRoot is not a git repository: ${absolutePath}`);
5149
+ function buildProcessRunHint(run, source) {
5150
+ const phase = deriveOrchestratorRunPhase(run);
5151
+ if (phase === "awaiting_ide_approval") {
5152
+ return {
5153
+ agentRunId: run.id,
5154
+ workItemId: run.workItemId,
5155
+ phase,
5156
+ source,
5157
+ steps: buildAwaitingIdeApprovalProcessRunSteps(run),
5158
+ attention: buildProcessRunAttentionHint(run)
5159
+ };
5160
+ }
5161
+ if (phase === "acknowledged") {
5162
+ return {
5163
+ agentRunId: run.id,
5164
+ workItemId: run.workItemId,
5165
+ phase,
5166
+ source,
5167
+ steps: buildAcknowledgedProcessRunSteps(run)
5168
+ };
5169
+ }
5170
+ const steps = buildDispatchedProcessRunSteps(run);
5171
+ if (source === "inflight") {
5172
+ return {
5173
+ agentRunId: run.id,
5174
+ workItemId: run.workItemId,
5175
+ phase,
5176
+ source,
5177
+ steps: [INFLIGHT_DISPATCHED_RECOVERY_STEP, ...steps]
5178
+ };
4478
5179
  }
5180
+ return {
5181
+ agentRunId: run.id,
5182
+ workItemId: run.workItemId,
5183
+ phase,
5184
+ source,
5185
+ steps
5186
+ };
4479
5187
  }
4480
- function findWorkspaceRootUpward(startDir) {
4481
- let current = resolve(startDir);
4482
- for (let level = 0; level <= MAX_UPWARD_LEVELS; level += 1) {
4483
- const markerPath = join3(current, TASK_BOARDS_FILE2);
4484
- if (existsSync3(markerPath)) {
4485
- return current;
4486
- }
4487
- const parent = dirname(current);
4488
- if (parent === current) {
4489
- break;
5188
+ function buildProcessRuns(items, inflightRuns) {
5189
+ const hints = [];
5190
+ for (const run of items) {
5191
+ if (run.requiresAttention) {
5192
+ hints.push(buildProcessRunHint(run, "claimed"));
4490
5193
  }
4491
- current = parent;
4492
5194
  }
4493
- return null;
4494
- }
4495
- function resolveWorkspaceRoot(explicitOverride, envWorkspaceRoot) {
4496
- const allowedRoot = envWorkspaceRoot !== void 0 && envWorkspaceRoot.trim() !== "" ? assertDirectory(envWorkspaceRoot, "WORKSPACE_ROOT") : void 0;
4497
- if (explicitOverride !== void 0 && explicitOverride.trim() !== "") {
4498
- const resolved = assertDirectory(explicitOverride, "workspaceRoot");
4499
- if (allowedRoot !== void 0) {
4500
- assertWithinAllowedRoot(resolved, allowedRoot);
4501
- } else {
4502
- assertGitRepository(resolved);
5195
+ for (const run of inflightRuns) {
5196
+ if (run.requiresAttention) {
5197
+ hints.push(buildProcessRunHint(run, "inflight"));
4503
5198
  }
4504
- return resolved;
4505
5199
  }
4506
- if (allowedRoot !== void 0) {
4507
- return allowedRoot;
5200
+ for (const run of inflightRuns) {
5201
+ if (!run.requiresAttention) {
5202
+ hints.push(buildProcessRunHint(run, "inflight"));
5203
+ }
4508
5204
  }
4509
- const fromMarker = findWorkspaceRootUpward(process.cwd());
4510
- if (fromMarker !== null) {
4511
- return fromMarker;
5205
+ for (const run of items) {
5206
+ if (!run.requiresAttention) {
5207
+ hints.push(buildProcessRunHint(run, "claimed"));
5208
+ }
4512
5209
  }
4513
- return resolve(process.cwd());
5210
+ return hints;
4514
5211
  }
4515
-
4516
- // src/workspace/resolve-project.ts
4517
- function emptyResponse(workspaceRoot) {
5212
+ function buildAttentionSummary(runs) {
5213
+ const blockedRuns = runs.filter((run) => run.requiresAttention);
4518
5214
  return {
4519
- resolutionSource: "ambiguous",
4520
- workspaceRoot,
4521
- projectId: null,
4522
- name: null,
4523
- key: null,
4524
- presetCode: null,
4525
- hint: "No project binding found. Add .task-boards.yaml, an IDE rule (.cursor/rules) with projectId, or rename the folder to match a project key/name."
5215
+ blockedRunCount: blockedRuns.length,
5216
+ blockedAgentRunIds: blockedRuns.map((run) => run.id),
5217
+ resolveBeforeNewDispatch: blockedRuns.length > 0
4526
5218
  };
4527
5219
  }
4528
- function fromProject(workspaceRoot, resolutionSource, project) {
5220
+ function buildInflightSummary(inflightRuns) {
5221
+ if (inflightRuns.length === 0) {
5222
+ return void 0;
5223
+ }
4529
5224
  return {
4530
- resolutionSource,
4531
- workspaceRoot,
4532
- projectId: project.id,
4533
- name: project.name,
4534
- key: project.key,
4535
- presetCode: project.presetCode
5225
+ dispatchedCount: inflightRuns.filter((run) => run.status === import_agent_run_status3.AGENT_RUN_STATUS.DISPATCHED).length,
5226
+ acknowledgedCount: inflightRuns.filter((run) => run.status === import_agent_run_status3.AGENT_RUN_STATUS.ACKNOWLEDGED).length,
5227
+ requiresAttentionCount: inflightRuns.filter((run) => run.requiresAttention).length,
5228
+ agentRunIds: inflightRuns.map((run) => run.id)
4536
5229
  };
4537
5230
  }
4538
- async function enrichProject(client, workspaceRoot, resolutionSource, projectId) {
4539
- const project = await client.get(import_projects.PROJECTS_ROUTES.getProject(projectId));
4540
- return fromProject(workspaceRoot, resolutionSource, project);
4541
- }
4542
- async function resolveProject(client, options = {}) {
4543
- const workspaceRoot = resolveWorkspaceRoot(options.workspaceRootOverride, options.envWorkspaceRoot);
4544
- const yamlProjectId = parseTaskBoardsYaml(workspaceRoot);
4545
- if (yamlProjectId !== null) {
4546
- return enrichProject(client, workspaceRoot, "yaml", yamlProjectId);
5231
+ function derivePollResultSource(items, inflightRuns) {
5232
+ if (items.length > 0) {
5233
+ return "claimed";
4547
5234
  }
4548
- const rulesResult = parseIdeRules(workspaceRoot);
4549
- if (rulesResult.status === "ambiguous") {
5235
+ if (inflightRuns.length > 0) {
5236
+ return "inflight";
5237
+ }
5238
+ return "idle";
5239
+ }
5240
+ function buildOrchestratorHints(items, inflightRuns, timedOut) {
5241
+ const pollResultSource = derivePollResultSource(items, inflightRuns);
5242
+ const allRuns = [...items, ...inflightRuns];
5243
+ if (timedOut) {
4550
5244
  return {
4551
- ...emptyResponse(workspaceRoot),
4552
- hint: "Multiple distinct projectId values found in .cursor/rules. Use .task-boards.yaml or set WORKSPACE_ROOT to a single-project workspace."
5245
+ phase: "idle",
5246
+ pollResultSource,
5247
+ sleeperPolicy: SLEEPER_POLICY,
5248
+ nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
4553
5249
  };
4554
5250
  }
4555
- if (rulesResult.status === "found") {
4556
- return enrichProject(client, workspaceRoot, "rule", rulesResult.projectId);
4557
- }
4558
- const folderToken = basename(workspaceRoot);
4559
- const listResponse = await client.get(import_projects.PROJECTS_ROUTES.listProjects());
4560
- const autoMatch = autoMatchProject(folderToken, listResponse.items);
4561
- if (autoMatch.kind === "single") {
4562
- return enrichProject(client, workspaceRoot, "auto_match", autoMatch.project.id);
4563
- }
4564
- if (autoMatch.kind === "candidates") {
5251
+ if (allRuns.length === 0) {
4565
5252
  return {
4566
- resolutionSource: "ambiguous",
4567
- workspaceRoot,
4568
- projectId: null,
4569
- name: null,
4570
- key: null,
4571
- presetCode: null,
4572
- candidates: autoMatch.candidates,
4573
- hint: `Multiple projects match folder name "${folderToken}". Pick one candidate or add .task-boards.yaml.`
5253
+ phase: "idle",
5254
+ pollResultSource,
5255
+ sleeperPolicy: SLEEPER_POLICY,
5256
+ nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
4574
5257
  };
4575
5258
  }
4576
- return emptyResponse(workspaceRoot);
5259
+ const hasAttentionRuns = allRuns.some((run) => run.requiresAttention);
5260
+ return {
5261
+ phase: "processing",
5262
+ pollResultSource,
5263
+ sleeperPolicy: SLEEPER_POLICY,
5264
+ inflightSummary: buildInflightSummary(inflightRuns),
5265
+ processRuns: buildProcessRuns(items, inflightRuns),
5266
+ attentionSummary: buildAttentionSummary(allRuns),
5267
+ runLifecycle: RUN_LIFECYCLE_DOC,
5268
+ nextSteps: hasAttentionRuns ? [PROCESSING_NO_PARALLEL_POLL_STEP, ...ATTENTION_PROCESSING_NEXT_STEPS, ...PROCESSING_NEXT_STEPS] : [PROCESSING_NO_PARALLEL_POLL_STEP, ...PROCESSING_NEXT_STEPS]
5269
+ };
4577
5270
  }
4578
-
4579
- // src/tools/orchestrator.ts
4580
- async function runOrchestratorOnce(client, options) {
4581
- let projectId = options.projectId;
4582
- let resolutionSource = "explicit";
4583
- if (projectId === void 0) {
4584
- const resolved = await resolveProject(client, {
4585
- workspaceRootOverride: options.workspaceRootOverride,
4586
- envWorkspaceRoot: options.envWorkspaceRoot
4587
- });
4588
- if (resolved.projectId === null) {
4589
- const hint = resolved.hint ?? "Could not resolve project for workspace.";
4590
- throw new WorkspaceError("PROJECT_NOT_FOUND", hint);
4591
- }
4592
- projectId = resolved.projectId;
4593
- resolutionSource = resolved.resolutionSource;
4594
- }
4595
- const pollResult = await pollAgentRuns(client, {
4596
- projectId,
4597
- timeoutMs: options.timeoutMs,
4598
- pollIntervalMs: options.pollIntervalMs,
4599
- limit: options.limit
4600
- });
4601
- const enriched = enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
5271
+ function enrichPollResult(items, inflightRuns, timedOut) {
4602
5272
  return {
4603
- projectId,
4604
- resolutionSource,
4605
- items: enriched.items,
4606
- inflightRuns: enriched.inflightRuns,
4607
- timedOut: enriched.timedOut,
4608
- orchestrator: enriched.orchestrator
5273
+ items,
5274
+ inflightRuns,
5275
+ timedOut,
5276
+ orchestrator: buildOrchestratorHints(items, inflightRuns, timedOut)
4609
5277
  };
4610
5278
  }
4611
- function registerOrchestratorTools(server, client, config) {
5279
+
5280
+ // src/tools/agent-runs.ts
5281
+ var agentRunStatusValues = Object.values(import_agent_run_status4.AGENT_RUN_STATUS);
5282
+ var agentRunOutcomeValues = Object.values(import_agent_run_outcome.AGENT_RUN_OUTCOME);
5283
+ var AGENTIC_SDLC_COLUMNS_SUMMARY = "AGENTIC_SDLC has 7 columns: backlog, in-analysis (PRODUCT\u2192ANALYST\u2192ARCHITECT handoffs), in-development (DESIGNER optional, then parallel FRONTEND_DEVELOPER+DEVELOPER), in-qa, in-awaiting, done, released.";
5284
+ var COMPLETE_AGENT_RUN_OUTCOME_SUMMARY = "Prerequisite: STORY assignee must be \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user) \u2014 otherwise no agent_run is enqueued or claimed. in-analysis: PRODUCT/ANALYST DEFAULT \u2192 HANDOFF next phase (skip unbound roles); ANALYST SKIP_DESIGN \u2192 in-development; ANALYST SKIP_DEV \u2192 released when codeChangesRequired=false (no git commit); ARCHITECT DEFAULT \u2192 in-development (set workItemPatch.designerRequired). in-development: DESIGNER DEFAULT \u2192 DEVELOPMENT; dev role DEFAULT removes role from pendingDevRoles, moves to in-qa when empty; SKIP_DESIGN skips DESIGNER. in-qa: DEFAULT \u2192 done; HAS_BUGS \u2192 in-development (re-init pendingDevRoles). NEEDS_CLARIFICATION \u2192 in-awaiting. FAILED \u2192 no move. Claim priority: in-development > in-qa > in-analysis (SERVICE-assigned stories only block analysis).";
5285
+ function registerAgentRunTools(server, client, config) {
5286
+ server.registerTool(
5287
+ "list_agent_runs",
5288
+ {
5289
+ description: "List agent runs for a project (default status PENDING). Prefer wait_for_agent_runs for orchestrator long-polling.",
5290
+ inputSchema: {
5291
+ projectId: z3.string().uuid().describe("Project UUID (required)"),
5292
+ status: z3.enum(agentRunStatusValues).optional().describe("Filter by agent run status")
5293
+ }
5294
+ },
5295
+ async ({ status, projectId }) => runTool(async () => {
5296
+ const effectiveStatus = status ?? import_agent_run_status4.AGENT_RUN_STATUS.PENDING;
5297
+ return client.get(import_agent_runs3.AGENT_RUNS_ROUTES.listAgentRuns(), {
5298
+ status: effectiveStatus,
5299
+ projectId
5300
+ });
5301
+ })
5302
+ );
4612
5303
  server.registerTool(
4613
- "run_orchestrator_once",
5304
+ "wait_for_agent_runs",
4614
5305
  {
4615
- description: "Resolve project (if needed), long-poll agent runs with atomic claim, and return orchestrator hints. Claims only STORY items assigned to \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user). Does not call sync_git_releases \u2014 the orchestrator agent decides when to sync.",
5306
+ description: "Long-poll agent runs for a project: atomically claims PENDING runs (POST /agent-runs/claim). On empty claim, immediately lists inflight DISPATCHED/ACKNOWLEDGED runs (GET activeOnly) for orphan recovery before sleeping. Returns items (newly claimed), inflightRuns (resume candidates), and orchestrator hints. First claim is immediate; subsequent polls sleep pollInterval seconds only when both claim and inflight are empty.",
4616
5307
  inputSchema: {
4617
- projectId: z2.string().uuid().optional().describe("Project UUID; when omitted, resolves via .task-boards.yaml / .cursor/rules / folder name"),
4618
- timeout: z2.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
4619
- pollInterval: z2.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
4620
- limit: z2.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)"),
4621
- workspaceRoot: z2.string().optional().describe("Optional absolute workspace root; defaults to WORKSPACE_ROOT or upward search from cwd")
5308
+ projectId: z3.string().uuid().describe("Project UUID"),
5309
+ timeout: z3.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
5310
+ pollInterval: z3.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
5311
+ limit: z3.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)")
4622
5312
  }
4623
5313
  },
4624
- async ({ projectId, timeout, pollInterval, limit, workspaceRoot }) => runTool(
4625
- async () => runOrchestratorOnce(client, {
5314
+ async ({ projectId, timeout, pollInterval, limit }) => runTool(async () => {
5315
+ const pollResult = await pollAgentRuns(client, {
4626
5316
  projectId,
4627
5317
  timeoutMs: timeout * 1e3,
4628
5318
  pollIntervalMs: pollInterval * 1e3,
4629
- limit,
4630
- workspaceRootOverride: workspaceRoot,
4631
- envWorkspaceRoot: config.workspaceRoot
4632
- })
4633
- )
5319
+ limit
5320
+ });
5321
+ return enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
5322
+ })
5323
+ );
5324
+ server.registerTool(
5325
+ "ack_agent_run",
5326
+ {
5327
+ description: "Acknowledge agent run delivery after Task subagent has been dispatched. Requires status DISPATCHED (DISPATCHED \u2192 ACKNOWLEDGED).",
5328
+ inputSchema: {
5329
+ agentRunId: z3.string().uuid().describe("Agent run UUID"),
5330
+ note: z3.string().nullable().optional().describe("Optional acknowledgment note")
5331
+ }
5332
+ },
5333
+ async ({ agentRunId, note }) => runTool(async () => {
5334
+ const body = note !== void 0 ? { note } : void 0;
5335
+ const response = await client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.acknowledgeAgentRun(agentRunId), body);
5336
+ writeActiveRunFromAck(config, response);
5337
+ return response;
5338
+ })
5339
+ );
5340
+ server.registerTool(
5341
+ "report_agent_attention",
5342
+ {
5343
+ description: "Signal that the in-flight subagent needs human attention while the run is ACKNOWLEDGED. Sets requiresAttention on the agent run for orchestrator/UI visibility.",
5344
+ inputSchema: {
5345
+ agentRunId: z3.string().uuid().describe("Agent run UUID"),
5346
+ message: z3.string().min(1).describe("Why human attention is needed")
5347
+ }
5348
+ },
5349
+ async ({ agentRunId, message }) => runTool(async () => {
5350
+ const body = {
5351
+ requiresAttention: true,
5352
+ attentionMessage: message
5353
+ };
5354
+ return client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
5355
+ })
5356
+ );
5357
+ server.registerTool(
5358
+ "clear_agent_attention",
5359
+ {
5360
+ description: "Clear the human-attention flag after the blocker is resolved. Requires the run to still be ACKNOWLEDGED.",
5361
+ inputSchema: {
5362
+ agentRunId: z3.string().uuid().describe("Agent run UUID")
5363
+ }
5364
+ },
5365
+ async ({ agentRunId }) => runTool(async () => {
5366
+ const body = {
5367
+ requiresAttention: false
5368
+ };
5369
+ return client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
5370
+ })
5371
+ );
5372
+ server.registerTool(
5373
+ "complete_agent_run",
5374
+ {
5375
+ description: `Complete agent run: apply AGENTIC_SDLC workflow transition and optional work item patch. ${AGENTIC_SDLC_COLUMNS_SUMMARY} Outcomes: ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`,
5376
+ inputSchema: {
5377
+ agentRunId: z3.string().uuid().describe("Agent run UUID"),
5378
+ outcome: z3.enum(agentRunOutcomeValues).optional().describe(
5379
+ `Completion outcome (DEFAULT, SKIP_DESIGN, SKIP_DEV, HAS_BUGS, NEEDS_CLARIFICATION, FAILED). ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`
5380
+ ),
5381
+ note: z3.string().nullable().optional().describe("Optional completion note"),
5382
+ workItemPatch: z3.object({
5383
+ description: z3.string().nullable().optional().describe("Updated work item description"),
5384
+ acceptanceCriteria: z3.string().nullable().optional().describe("Updated acceptance criteria"),
5385
+ designerRequired: z3.boolean().optional().describe("Architect only: whether DESIGNER phase runs before development")
5386
+ }).nullable().optional().describe("Optional work item field updates")
5387
+ }
5388
+ },
5389
+ async ({ agentRunId, outcome, note, workItemPatch }) => runTool(async () => {
5390
+ const body = {};
5391
+ if (outcome !== void 0) {
5392
+ body.outcome = outcome;
5393
+ }
5394
+ if (note !== void 0) {
5395
+ body.note = note;
5396
+ }
5397
+ if (workItemPatch !== void 0 && workItemPatch !== null) {
5398
+ body.workItemPatch = workItemPatch;
5399
+ }
5400
+ const hasBody = outcome !== void 0 || note !== void 0 || workItemPatch !== void 0 && workItemPatch !== null;
5401
+ const response = await client.post(
5402
+ import_agent_runs3.AGENT_RUNS_ROUTES.completeAgentRun(agentRunId),
5403
+ hasBody ? body : void 0
5404
+ );
5405
+ clearActiveRunAfterComplete(config);
5406
+ return response;
5407
+ })
4634
5408
  );
4635
5409
  }
4636
5410
 
4637
- // src/tools/attachments.ts
4638
- var import_attachments = __toESM(require_attachments_api(), 1);
4639
- var import_shared3 = __toESM(require_build2(), 1);
4640
- import { existsSync as existsSync5 } from "node:fs";
4641
- import { z as z3 } from "zod";
5411
+ // src/tools/orchestrator.ts
5412
+ import { z as z4 } from "zod";
4642
5413
 
4643
- // src/attachments/detect-mime-type.ts
4644
- import { basename as basename2 } from "node:path";
4645
- import { lookup as lookupMimeType } from "mime-types";
4646
- function detectMimeType(filePath) {
4647
- const mimeType = lookupMimeType(basename2(filePath));
4648
- return typeof mimeType === "string" ? mimeType : "application/octet-stream";
4649
- }
5414
+ // src/workspace/resolve-project.ts
5415
+ var import_projects = __toESM(require_projects_api(), 1);
5416
+ import { basename as basename3 } from "node:path";
4650
5417
 
4651
- // src/attachments/staging.util.ts
4652
- import { mkdir, writeFile } from "node:fs/promises";
4653
- import { dirname as dirname2, resolve as resolve2 } from "node:path";
4654
- var MAX_FILE_NAME_LENGTH = 200;
4655
- function sanitizeFileName(name) {
4656
- const baseName = name.split(/[/\\]/).pop() ?? name;
4657
- const sanitized = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
4658
- return sanitized.length > MAX_FILE_NAME_LENGTH ? sanitized.slice(0, MAX_FILE_NAME_LENGTH) : sanitized;
4659
- }
4660
- function buildStagingPath(workspaceRoot, workItemId, attachmentId, fileName) {
4661
- const sanitizedFileName = sanitizeFileName(fileName);
4662
- return resolve2(workspaceRoot, ".task-boards", "attachments", workItemId, `${attachmentId}_${sanitizedFileName}`);
4663
- }
4664
- function buildStagingDir(workspaceRoot, workItemId) {
4665
- return resolve2(workspaceRoot, ".task-boards", "attachments", workItemId);
4666
- }
4667
- async function writeStagingFile(path, data) {
4668
- const absolutePath = resolve2(path);
4669
- await mkdir(dirname2(absolutePath), { recursive: true });
4670
- await writeFile(absolutePath, data);
5418
+ // src/workspace/normalize-token.ts
5419
+ function normalizeToken(value) {
5420
+ return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "");
4671
5421
  }
4672
5422
 
4673
- // src/workspace/confine-file-path.ts
4674
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
4675
- import { basename as basename3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
4676
- function confineFilePath(workspaceRoot, filePath) {
4677
- const resolvedRoot = resolve3(workspaceRoot);
4678
- const resolvedPath = isAbsolute2(filePath) ? resolve3(filePath) : resolve3(resolvedRoot, filePath);
4679
- const relativePath = relative2(resolvedRoot, resolvedPath);
4680
- if (relativePath.startsWith("..") || isAbsolute2(relativePath)) {
4681
- throw new WorkspaceError(
4682
- "FILE_OUT_OF_BOUNDS",
4683
- `filePath is outside workspaceRoot: ${basename3(filePath) || filePath}`
4684
- );
4685
- }
4686
- if (!existsSync4(resolvedPath)) {
4687
- throw new WorkspaceError("FILE_NOT_FOUND", `filePath does not exist: ${basename3(filePath) || filePath}`);
4688
- }
4689
- const stats = statSync2(resolvedPath);
4690
- if (!stats.isFile()) {
4691
- throw new WorkspaceError("FILE_NOT_FOUND", `filePath is not a file: ${basename3(filePath) || filePath}`);
4692
- }
4693
- return resolvedPath;
4694
- }
4695
- function readConfinedWorkspaceFile(workspaceRoot, filePath) {
4696
- const absolutePath = confineFilePath(workspaceRoot, filePath);
4697
- const stats = statSync2(absolutePath);
4698
- const buffer = readFileSync3(absolutePath);
5423
+ // src/workspace/auto-match-project.ts
5424
+ function toCandidate(project, matchedBy) {
4699
5425
  return {
4700
- absolutePath,
4701
- fileName: basename3(absolutePath),
4702
- sizeBytes: stats.size,
4703
- buffer
5426
+ projectId: project.id,
5427
+ name: project.name,
5428
+ key: project.key,
5429
+ presetCode: project.presetCode,
5430
+ matchedBy
4704
5431
  };
4705
5432
  }
4706
-
4707
- // src/tools/attachments.ts
4708
- var MAX_ATTACHMENT_COUNT = 20;
4709
- var MAX_TOTAL_BYTES = 200 * 1024 * 1024;
4710
- async function fetchAttachments(client, workItemId) {
4711
- return client.get(import_attachments.ATTACHMENTS_ROUTES.listAttachments(workItemId));
4712
- }
4713
- function filterAttachments(items, attachmentIds) {
4714
- if (attachmentIds === void 0 || attachmentIds.length === 0) {
4715
- return items;
5433
+ function autoMatchProject(folderToken, projects) {
5434
+ const normalizedFolder = normalizeToken(folderToken);
5435
+ if (normalizedFolder.length === 0) {
5436
+ return { kind: "none" };
4716
5437
  }
4717
- const idSet = new Set(attachmentIds);
4718
- return items.filter((item) => idSet.has(item.id));
4719
- }
4720
- function assertDownloadLimits(attachments) {
4721
- if (attachments.length > MAX_ATTACHMENT_COUNT) {
4722
- throw new RestClientError(
4723
- "ATTACHMENT_LIMIT_EXCEEDED",
4724
- `Cannot download more than ${MAX_ATTACHMENT_COUNT} attachments at once (requested ${attachments.length})`
4725
- );
5438
+ const keyMatches = [];
5439
+ const nameMatches = [];
5440
+ for (const project of projects) {
5441
+ const input = {
5442
+ id: project.id,
5443
+ name: project.name,
5444
+ key: project.key,
5445
+ presetCode: project.presetCode
5446
+ };
5447
+ if (normalizeToken(project.key) === normalizedFolder) {
5448
+ keyMatches.push(input);
5449
+ }
5450
+ if (normalizeToken(project.name) === normalizedFolder) {
5451
+ nameMatches.push(input);
5452
+ }
4726
5453
  }
4727
- const totalBytes = attachments.reduce((sum, attachment) => sum + attachment.sizeBytes, 0);
4728
- if (totalBytes > MAX_TOTAL_BYTES) {
4729
- throw new RestClientError(
4730
- "ATTACHMENT_SIZE_LIMIT_EXCEEDED",
4731
- `Total attachment size ${totalBytes} bytes exceeds limit of ${MAX_TOTAL_BYTES} bytes`
4732
- );
5454
+ const uniqueById = /* @__PURE__ */ new Map();
5455
+ for (const project of keyMatches) {
5456
+ uniqueById.set(project.id, { project, matchedBy: "key" });
5457
+ }
5458
+ for (const project of nameMatches) {
5459
+ if (!uniqueById.has(project.id)) {
5460
+ uniqueById.set(project.id, { project, matchedBy: "name" });
5461
+ }
5462
+ }
5463
+ const matches = [...uniqueById.values()];
5464
+ if (matches.length === 1) {
5465
+ const match = matches[0];
5466
+ if (match === void 0) {
5467
+ return { kind: "none" };
5468
+ }
5469
+ return { kind: "single", project: match.project, matchedBy: match.matchedBy };
5470
+ }
5471
+ if (matches.length > 1) {
5472
+ return {
5473
+ kind: "candidates",
5474
+ candidates: matches.map((match) => toCandidate(match.project, match.matchedBy))
5475
+ };
4733
5476
  }
5477
+ return { kind: "none" };
4734
5478
  }
4735
- async function downloadWorkItemAttachments(client, params) {
4736
- const { workItemId, workspaceRoot, attachmentIds, overwrite = false, blockSensitiveAttachments = true } = params;
4737
- const listResponse = await fetchAttachments(client, workItemId);
4738
- const selected = filterAttachments(listResponse.items, attachmentIds);
4739
- assertDownloadLimits(selected);
4740
- const stagingDir = buildStagingDir(workspaceRoot, workItemId);
4741
- const downloaded = [];
4742
- const skipped = [];
4743
- if (attachmentIds !== void 0 && attachmentIds.length > 0) {
4744
- const selectedIds = new Set(selected.map((item) => item.id));
4745
- for (const attachmentId of attachmentIds) {
4746
- if (!selectedIds.has(attachmentId)) {
4747
- skipped.push({ attachmentId, reason: "not found on work item" });
5479
+
5480
+ // src/workspace/parse-ide-rules.ts
5481
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync4 } from "node:fs";
5482
+ import { join as join5 } from "node:path";
5483
+ var PROJECT_ID_LINE_PATTERN = /projectId\s*[:=]\s*["']?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi;
5484
+ var TASK_BOARDS_UUID_PATTERN = /task-boards[^\n\r]*?([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi;
5485
+ function extractUuids(content) {
5486
+ const ids = /* @__PURE__ */ new Set();
5487
+ for (const pattern of [PROJECT_ID_LINE_PATTERN, TASK_BOARDS_UUID_PATTERN]) {
5488
+ pattern.lastIndex = 0;
5489
+ let match = pattern.exec(content);
5490
+ while (match !== null) {
5491
+ const id = match[1];
5492
+ if (id !== void 0) {
5493
+ ids.add(id.toLowerCase());
4748
5494
  }
5495
+ match = pattern.exec(content);
4749
5496
  }
4750
5497
  }
4751
- for (const attachment of selected) {
4752
- if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(attachment.fileName)) {
4753
- skipped.push({ attachmentId: attachment.id, reason: "blocked_sensitive_filename" });
5498
+ return ids;
5499
+ }
5500
+ function parseIdeRules(workspaceRoot) {
5501
+ const rulesDir = join5(workspaceRoot, ".cursor", "rules");
5502
+ if (!existsSync6(rulesDir)) {
5503
+ return { status: "missing" };
5504
+ }
5505
+ const entries = readdirSync(rulesDir, { withFileTypes: true });
5506
+ const allIds = /* @__PURE__ */ new Set();
5507
+ for (const entry of entries) {
5508
+ if (!entry.isFile()) {
4754
5509
  continue;
4755
5510
  }
4756
- const stagingPath = buildStagingPath(workspaceRoot, workItemId, attachment.id, attachment.fileName);
4757
- if (!overwrite && existsSync5(stagingPath)) {
4758
- skipped.push({ attachmentId: attachment.id, reason: "file already exists (set overwrite=true to replace)" });
5511
+ if (!entry.name.endsWith(".mdc") && !entry.name.endsWith(".md")) {
4759
5512
  continue;
4760
5513
  }
4761
- const { data } = await client.downloadBinary(import_attachments.ATTACHMENTS_ROUTES.downloadAttachment(workItemId, attachment.id));
4762
- await writeStagingFile(stagingPath, data);
4763
- downloaded.push({
4764
- attachmentId: attachment.id,
4765
- fileName: attachment.fileName,
4766
- path: stagingPath,
4767
- sizeBytes: data.length
4768
- });
5514
+ const content = readFileSync4(join5(rulesDir, entry.name), "utf8");
5515
+ for (const id of extractUuids(content)) {
5516
+ allIds.add(id);
5517
+ }
5518
+ }
5519
+ if (allIds.size === 0) {
5520
+ return { status: "missing" };
5521
+ }
5522
+ if (allIds.size === 1) {
5523
+ const projectId = [...allIds][0];
5524
+ if (projectId === void 0) {
5525
+ return { status: "missing" };
5526
+ }
5527
+ return { status: "found", projectId };
4769
5528
  }
5529
+ return { status: "ambiguous" };
5530
+ }
5531
+
5532
+ // src/workspace/resolve-project.ts
5533
+ function emptyResponse(workspaceRoot) {
4770
5534
  return {
4771
- workItemId,
4772
- downloaded,
4773
- skipped,
4774
- stagingDir
5535
+ resolutionSource: "ambiguous",
5536
+ workspaceRoot,
5537
+ projectId: null,
5538
+ name: null,
5539
+ key: null,
5540
+ presetCode: null,
5541
+ hint: "No project binding found. Add .task-boards.yaml, an IDE rule (.cursor/rules) with projectId, or rename the folder to match a project key/name."
4775
5542
  };
4776
5543
  }
4777
- async function uploadWorkItemAttachment(client, params) {
4778
- const { workItemId, workspaceRoot, filePath, blockSensitiveAttachments = true } = params;
4779
- const confinedFile = readConfinedWorkspaceFile(workspaceRoot, filePath);
4780
- if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(confinedFile.fileName)) {
4781
- throw new RestClientError(
4782
- "BLOCKED_SENSITIVE_FILENAME",
4783
- `Attachment filename is blocked for MCP upload: ${confinedFile.fileName}`
4784
- );
5544
+ function fromProject(workspaceRoot, resolutionSource, project) {
5545
+ return {
5546
+ resolutionSource,
5547
+ workspaceRoot,
5548
+ projectId: project.id,
5549
+ name: project.name,
5550
+ key: project.key,
5551
+ presetCode: project.presetCode
5552
+ };
5553
+ }
5554
+ async function enrichProject(client, workspaceRoot, resolutionSource, projectId) {
5555
+ const project = await client.get(import_projects.PROJECTS_ROUTES.getProject(projectId));
5556
+ return fromProject(workspaceRoot, resolutionSource, project);
5557
+ }
5558
+ async function resolveProject(client, options = {}) {
5559
+ const workspaceRoot = resolveWorkspaceRoot(options.workspaceRootOverride, options.envWorkspaceRoot);
5560
+ const yamlProjectId = parseTaskBoardsYaml(workspaceRoot);
5561
+ if (yamlProjectId !== null) {
5562
+ return enrichProject(client, workspaceRoot, "yaml", yamlProjectId);
4785
5563
  }
4786
- const mimeType = detectMimeType(confinedFile.absolutePath);
4787
- const attachment = await client.uploadMultipart(
4788
- import_attachments.ATTACHMENTS_ROUTES.uploadAttachment(workItemId),
4789
- "file",
4790
- {
4791
- buffer: confinedFile.buffer,
4792
- fileName: confinedFile.fileName,
4793
- mimeType
5564
+ const rulesResult = parseIdeRules(workspaceRoot);
5565
+ if (rulesResult.status === "ambiguous") {
5566
+ return {
5567
+ ...emptyResponse(workspaceRoot),
5568
+ hint: "Multiple distinct projectId values found in .cursor/rules. Use .task-boards.yaml or set WORKSPACE_ROOT to a single-project workspace."
5569
+ };
5570
+ }
5571
+ if (rulesResult.status === "found") {
5572
+ return enrichProject(client, workspaceRoot, "rule", rulesResult.projectId);
5573
+ }
5574
+ const folderToken = basename3(workspaceRoot);
5575
+ const listResponse = await client.get(import_projects.PROJECTS_ROUTES.listProjects());
5576
+ const autoMatch = autoMatchProject(folderToken, listResponse.items);
5577
+ if (autoMatch.kind === "single") {
5578
+ return enrichProject(client, workspaceRoot, "auto_match", autoMatch.project.id);
5579
+ }
5580
+ if (autoMatch.kind === "candidates") {
5581
+ return {
5582
+ resolutionSource: "ambiguous",
5583
+ workspaceRoot,
5584
+ projectId: null,
5585
+ name: null,
5586
+ key: null,
5587
+ presetCode: null,
5588
+ candidates: autoMatch.candidates,
5589
+ hint: `Multiple projects match folder name "${folderToken}". Pick one candidate or add .task-boards.yaml.`
5590
+ };
5591
+ }
5592
+ return emptyResponse(workspaceRoot);
5593
+ }
5594
+
5595
+ // src/tools/orchestrator.ts
5596
+ async function runOrchestratorOnce(client, options) {
5597
+ let projectId = options.projectId;
5598
+ let resolutionSource = "explicit";
5599
+ if (projectId === void 0) {
5600
+ const resolved = await resolveProject(client, {
5601
+ workspaceRootOverride: options.workspaceRootOverride,
5602
+ envWorkspaceRoot: options.envWorkspaceRoot
5603
+ });
5604
+ if (resolved.projectId === null) {
5605
+ const hint = resolved.hint ?? "Could not resolve project for workspace.";
5606
+ throw new WorkspaceError("PROJECT_NOT_FOUND", hint);
4794
5607
  }
4795
- );
5608
+ projectId = resolved.projectId;
5609
+ resolutionSource = resolved.resolutionSource;
5610
+ }
5611
+ const pollResult = await pollAgentRuns(client, {
5612
+ projectId,
5613
+ timeoutMs: options.timeoutMs,
5614
+ pollIntervalMs: options.pollIntervalMs,
5615
+ limit: options.limit
5616
+ });
5617
+ const enriched = enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
4796
5618
  return {
4797
- workItemId,
4798
- attachment,
4799
- sourcePath: confinedFile.absolutePath
5619
+ projectId,
5620
+ resolutionSource,
5621
+ items: enriched.items,
5622
+ inflightRuns: enriched.inflightRuns,
5623
+ timedOut: enriched.timedOut,
5624
+ orchestrator: enriched.orchestrator
4800
5625
  };
4801
5626
  }
4802
- function registerAttachmentTools(server, client, config) {
4803
- server.registerTool(
4804
- "list_work_item_attachments",
4805
- {
4806
- description: "List file attachments for a work item.",
4807
- inputSchema: {
4808
- workItemId: z3.string().uuid().describe("Work item UUID")
4809
- }
4810
- },
4811
- async ({ workItemId }) => runTool(async () => fetchAttachments(client, workItemId))
4812
- );
4813
- server.registerTool(
4814
- "download_work_item_attachments",
4815
- {
4816
- description: "Download work item attachments into the local workspace staging directory (.task-boards/attachments).",
4817
- inputSchema: {
4818
- workItemId: z3.string().uuid().describe("Work item UUID"),
4819
- attachmentIds: z3.array(z3.string().uuid()).optional().describe("Optional subset of attachment UUIDs; downloads all when omitted"),
4820
- overwrite: z3.boolean().optional().default(false).describe("Replace existing staged files when true"),
4821
- workspaceRoot: z3.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
4822
- }
4823
- },
4824
- async ({ workItemId, attachmentIds, overwrite, workspaceRoot }) => {
4825
- const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
4826
- return runTool(
4827
- async () => downloadWorkItemAttachments(client, {
4828
- workItemId,
4829
- workspaceRoot: resolvedWorkspaceRoot,
4830
- attachmentIds,
4831
- overwrite,
4832
- blockSensitiveAttachments: config.blockSensitiveAttachments
4833
- }),
4834
- { workspaceRoot: resolvedWorkspaceRoot }
4835
- );
4836
- }
4837
- );
5627
+ function registerOrchestratorTools(server, client, config) {
4838
5628
  server.registerTool(
4839
- "upload_work_item_attachment",
5629
+ "run_orchestrator_once",
4840
5630
  {
4841
- description: "Upload a local workspace file as a work item attachment (multipart field file).",
5631
+ description: "Resolve project (if needed), long-poll agent runs with atomic claim, and return orchestrator hints. Claims only STORY items assigned to \u0410\u0433\u0435\u043D\u0442 \u0418\u0418 (SERVICE user). Does not call sync_git_releases \u2014 the orchestrator agent decides when to sync.",
4842
5632
  inputSchema: {
4843
- workItemId: z3.string().uuid().describe("Work item UUID"),
4844
- filePath: z3.string().describe("Absolute or workspace-relative path to the file; must stay within workspaceRoot"),
4845
- workspaceRoot: z3.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
5633
+ projectId: z4.string().uuid().optional().describe("Project UUID; when omitted, resolves via .task-boards.yaml / .cursor/rules / folder name"),
5634
+ timeout: z4.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
5635
+ pollInterval: z4.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
5636
+ limit: z4.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)"),
5637
+ workspaceRoot: z4.string().optional().describe("Optional absolute workspace root; defaults to WORKSPACE_ROOT or upward search from cwd")
4846
5638
  }
4847
5639
  },
4848
- async ({ workItemId, filePath, workspaceRoot }) => {
4849
- const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
4850
- return runTool(
4851
- async () => uploadWorkItemAttachment(client, {
4852
- workItemId,
4853
- workspaceRoot: resolvedWorkspaceRoot,
4854
- filePath,
4855
- blockSensitiveAttachments: config.blockSensitiveAttachments
4856
- }),
4857
- { workspaceRoot: resolvedWorkspaceRoot }
4858
- );
4859
- }
5640
+ async ({ projectId, timeout, pollInterval, limit, workspaceRoot }) => runTool(
5641
+ async () => runOrchestratorOnce(client, {
5642
+ projectId,
5643
+ timeoutMs: timeout * 1e3,
5644
+ pollIntervalMs: pollInterval * 1e3,
5645
+ limit,
5646
+ workspaceRootOverride: workspaceRoot,
5647
+ envWorkspaceRoot: config.workspaceRoot
5648
+ })
5649
+ )
4860
5650
  );
4861
5651
  }
4862
5652
 
4863
5653
  // src/tools/board.ts
4864
5654
  var import_boards = __toESM(require_boards_api(), 1);
4865
- import { z as z4 } from "zod";
5655
+ import { z as z5 } from "zod";
4866
5656
  function registerBoardTools(server, client) {
4867
5657
  server.registerTool(
4868
5658
  "get_board",
4869
5659
  {
4870
5660
  description: "Get board projection for a project (columns and on-board work items).",
4871
5661
  inputSchema: {
4872
- projectId: z4.string().uuid().describe("Project UUID")
5662
+ projectId: z5.string().uuid().describe("Project UUID")
4873
5663
  }
4874
5664
  },
4875
5665
  async ({ projectId }) => runTool(async () => client.get(import_boards.BOARDS_ROUTES.getBoard(projectId)))
4876
5666
  );
4877
5667
  }
4878
5668
 
4879
- // src/tools/comments.ts
4880
- var import_comments = __toESM(require_comments_api(), 1);
4881
- import { z as z5 } from "zod";
4882
- async function fetchComments(client, workItemId) {
4883
- return client.get(import_comments.COMMENTS_ROUTES.listComments(workItemId));
4884
- }
4885
- async function postComment(client, workItemId, body) {
4886
- const request = { body };
4887
- return client.post(import_comments.COMMENTS_ROUTES.createComment(workItemId), request);
4888
- }
4889
- function registerCommentTools(server, client) {
4890
- server.registerTool(
4891
- "list_comments",
4892
- {
4893
- description: "List comments for a work item (newest last).",
4894
- inputSchema: {
4895
- workItemId: z5.string().uuid().describe("Work item UUID")
4896
- }
4897
- },
4898
- async ({ workItemId }) => runTool(async () => fetchComments(client, workItemId))
4899
- );
4900
- server.registerTool(
4901
- "create_comment",
4902
- {
4903
- description: "Create a comment on a work item. Use for orchestrator NEEDS_CLARIFICATION questions before complete_agent_run.",
4904
- inputSchema: {
4905
- workItemId: z5.string().uuid().describe("Work item UUID"),
4906
- body: z5.string().min(1).describe("Comment body (markdown supported)")
4907
- }
4908
- },
4909
- async ({ workItemId, body }) => runTool(async () => postComment(client, workItemId, body))
4910
- );
4911
- }
4912
-
4913
5669
  // src/tools/labels.ts
4914
5670
  var import_label_color = __toESM(require_label_color_enum(), 1);
4915
5671
  var import_labels = __toESM(require_labels_api(), 1);
@@ -5306,44 +6062,6 @@ function registerSyncGitReleasesTools(server, client, config) {
5306
6062
 
5307
6063
  // src/tools/sync-project-subagents.ts
5308
6064
  import { z as z10 } from "zod";
5309
-
5310
- // src/subagents/sync-project-subagents.ts
5311
- var import_shared4 = __toESM(require_build2(), 1);
5312
- import { mkdirSync, writeFileSync } from "node:fs";
5313
- import { join as join4 } from "node:path";
5314
- var SAFE_SUBAGENT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5315
- var MAX_SUBAGENT_SLUG_LENGTH = 64;
5316
- function isSyncableSlug(slug) {
5317
- const trimmed = slug.trim();
5318
- return trimmed.length > 0 && trimmed.length <= MAX_SUBAGENT_SLUG_LENGTH && SAFE_SUBAGENT_SLUG_PATTERN.test(trimmed);
5319
- }
5320
- function buildFileContent(subagent) {
5321
- return (0, import_shared4.buildSubagentFileContentFromBody)({
5322
- slug: subagent.slug,
5323
- description: subagent.description,
5324
- bodyMarkdown: subagent.bodyMarkdown
5325
- });
5326
- }
5327
- async function syncProjectSubagents(client, params) {
5328
- const { projectId, workspaceRoot } = params;
5329
- const listResponse = await client.get(import_shared4.PROJECT_SUBAGENTS_ROUTES.listSubagents(projectId));
5330
- const directory = join4(workspaceRoot, ".cursor", "agents");
5331
- mkdirSync(directory, { recursive: true });
5332
- const synced = [];
5333
- const skipped = [];
5334
- for (const subagent of listResponse.items) {
5335
- if (!isSyncableSlug(subagent.slug)) {
5336
- skipped.push(subagent.slug);
5337
- continue;
5338
- }
5339
- const filePath = join4(directory, `${subagent.slug}.md`);
5340
- writeFileSync(filePath, buildFileContent(subagent), "utf8");
5341
- synced.push(subagent.slug);
5342
- }
5343
- return { synced, skipped, directory };
5344
- }
5345
-
5346
- // src/tools/sync-project-subagents.ts
5347
6065
  function registerSyncProjectSubagentsTools(server, client, config) {
5348
6066
  server.registerTool(
5349
6067
  "sync_project_subagents",
@@ -5499,6 +6217,8 @@ function registerWorkItemTools(server, client) {
5499
6217
  assigneeUserId: z11.string().uuid().nullable().optional().describe(
5500
6218
  "Project member user id assignee; set to \xAB\u0410\u0433\u0435\u043D\u0442 \u0418\u0418\xBB (SERVICE user) for AI orchestrator to process the STORY. Changing assignee from SERVICE to a human cancels pending agent runs for that work item."
5501
6219
  ),
6220
+ codeChangesRequired: z11.boolean().optional().describe("STORY only: when false, ANALYST may complete with SKIP_DEV to release without code/git"),
6221
+ completed: z11.boolean().optional().describe("SUBTASK | EPIC: toggle completion (sets or clears releasedAt)"),
5502
6222
  labelIds: z11.array(z11.string().uuid()).max(10).optional().describe("Replace work item labels (up to 10)")
5503
6223
  }
5504
6224
  },
@@ -5513,6 +6233,8 @@ function registerWorkItemTools(server, client) {
5513
6233
  storyPoints,
5514
6234
  estimate,
5515
6235
  assigneeUserId,
6236
+ codeChangesRequired,
6237
+ completed,
5516
6238
  labelIds
5517
6239
  }) => runTool(async () => {
5518
6240
  const current = await client.get(import_work_items.WORK_ITEMS_ROUTES.getWorkItem(workItemId));
@@ -5526,6 +6248,8 @@ function registerWorkItemTools(server, client) {
5526
6248
  storyPoints,
5527
6249
  estimate,
5528
6250
  assigneeUserId,
6251
+ codeChangesRequired,
6252
+ completed,
5529
6253
  labelIds,
5530
6254
  version: current.version
5531
6255
  };
@@ -5584,7 +6308,7 @@ function registerTools(server, client, config) {
5584
6308
  registerLabelTools(server, client);
5585
6309
  registerAttachmentTools(server, client, config);
5586
6310
  registerBoardTools(server, client);
5587
- registerAgentRunTools(server, client);
6311
+ registerAgentRunTools(server, client, config);
5588
6312
  registerOrchestratorTools(server, client, config);
5589
6313
  registerSyncGitReleasesTools(server, client, config);
5590
6314
  registerSyncProjectSubagentsTools(server, client, config);
@@ -5607,7 +6331,17 @@ async function main() {
5607
6331
  }
5608
6332
 
5609
6333
  // src/cli.ts
5610
- main().catch((error) => {
6334
+ async function main2() {
6335
+ const argv = process.argv.slice(2);
6336
+ if (isAttentionCliInvocation(argv)) {
6337
+ const config = loadConfig();
6338
+ const client = new RestClient(config.apiUrl, config.apiToken);
6339
+ const exitCode = await runAttentionCli(argv, { config, client });
6340
+ process.exit(exitCode);
6341
+ }
6342
+ await main();
6343
+ }
6344
+ main2().catch((error) => {
5611
6345
  const message = error instanceof Error ? error.message : "Failed to start MCP server";
5612
6346
  console.error(message);
5613
6347
  process.exit(1);