@task-boards/mcp-server 0.15.0 → 0.16.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.
- package/build/cli.js +1735 -1085
- package/build/cli.js.map +4 -4
- 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"
|
|
@@ -915,25 +935,6 @@ var require_notifications = __commonJS({
|
|
|
915
935
|
}
|
|
916
936
|
});
|
|
917
937
|
|
|
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
938
|
// ../api/build/agent-runs/agent-runs.types.js
|
|
938
939
|
var require_agent_runs_types = __commonJS({
|
|
939
940
|
"../api/build/agent-runs/agent-runs.types.js"(exports) {
|
|
@@ -1682,7 +1683,10 @@ var require_exception_codes = __commonJS({
|
|
|
1682
1683
|
ADMIN_NOTIFICATION_SETTINGS_NOT_FOUND: "ADMIN_NOTIFICATION_SETTINGS_NOT_FOUND",
|
|
1683
1684
|
ADMIN_NOTIFICATION_CHANNEL_INVALID: "ADMIN_NOTIFICATION_CHANNEL_INVALID",
|
|
1684
1685
|
ADMIN_NOTIFICATION_EMAIL_RECIPIENT_REQUIRED: "ADMIN_NOTIFICATION_EMAIL_RECIPIENT_REQUIRED",
|
|
1685
|
-
ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE: "ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE"
|
|
1686
|
+
ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE: "ADMIN_NOTIFICATION_TELEGRAM_CONFIG_INCOMPLETE",
|
|
1687
|
+
PAYMENTS_DISABLED: "PAYMENTS_DISABLED",
|
|
1688
|
+
PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED",
|
|
1689
|
+
PAYMENTS_PROBE_FAILED: "PAYMENTS_PROBE_FAILED"
|
|
1686
1690
|
};
|
|
1687
1691
|
}
|
|
1688
1692
|
});
|
|
@@ -1695,7 +1699,8 @@ var require_health_api = __commonJS({
|
|
|
1695
1699
|
exports.HEALTH_ROUTES = exports.HEALTH_PREFIX = void 0;
|
|
1696
1700
|
exports.HEALTH_PREFIX = "/health";
|
|
1697
1701
|
exports.HEALTH_ROUTES = {
|
|
1698
|
-
getHealth: () => exports.HEALTH_PREFIX
|
|
1702
|
+
getHealth: () => exports.HEALTH_PREFIX,
|
|
1703
|
+
getReadiness: () => `${exports.HEALTH_PREFIX}/ready`
|
|
1699
1704
|
};
|
|
1700
1705
|
}
|
|
1701
1706
|
});
|
|
@@ -1705,10 +1710,18 @@ var require_health_types = __commonJS({
|
|
|
1705
1710
|
"../shared/build/internal/health/health.types.js"(exports) {
|
|
1706
1711
|
"use strict";
|
|
1707
1712
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1708
|
-
exports.HEALTH_STATUS = void 0;
|
|
1713
|
+
exports.READINESS_CHECK_STATUS = exports.READINESS_STATUS = exports.HEALTH_STATUS = void 0;
|
|
1709
1714
|
exports.HEALTH_STATUS = {
|
|
1710
1715
|
OK: "ok"
|
|
1711
1716
|
};
|
|
1717
|
+
exports.READINESS_STATUS = {
|
|
1718
|
+
OK: "ok",
|
|
1719
|
+
ERROR: "error"
|
|
1720
|
+
};
|
|
1721
|
+
exports.READINESS_CHECK_STATUS = {
|
|
1722
|
+
UP: "up",
|
|
1723
|
+
DOWN: "down"
|
|
1724
|
+
};
|
|
1712
1725
|
}
|
|
1713
1726
|
});
|
|
1714
1727
|
|
|
@@ -1873,6 +1886,36 @@ var require_admin = __commonJS({
|
|
|
1873
1886
|
}
|
|
1874
1887
|
});
|
|
1875
1888
|
|
|
1889
|
+
// ../shared/build/internal/payments/admin-payments.api.js
|
|
1890
|
+
var require_admin_payments_api = __commonJS({
|
|
1891
|
+
"../shared/build/internal/payments/admin-payments.api.js"(exports) {
|
|
1892
|
+
"use strict";
|
|
1893
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1894
|
+
exports.ADMIN_PAYMENTS_ROUTES = exports.ADMIN_PAYMENTS_PREFIX = void 0;
|
|
1895
|
+
var admin_api_1 = require_admin_api();
|
|
1896
|
+
exports.ADMIN_PAYMENTS_PREFIX = `${admin_api_1.ADMIN_PREFIX}/payments`;
|
|
1897
|
+
exports.ADMIN_PAYMENTS_ROUTES = {
|
|
1898
|
+
probePaymentsConnectivity: () => `${exports.ADMIN_PAYMENTS_PREFIX}/probe`
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1903
|
+
// ../shared/build/internal/payments/index.js
|
|
1904
|
+
var require_payments = __commonJS({
|
|
1905
|
+
"../shared/build/internal/payments/index.js"(exports) {
|
|
1906
|
+
"use strict";
|
|
1907
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1908
|
+
exports.ADMIN_PAYMENTS_ROUTES = exports.ADMIN_PAYMENTS_PREFIX = void 0;
|
|
1909
|
+
var admin_payments_api_1 = require_admin_payments_api();
|
|
1910
|
+
Object.defineProperty(exports, "ADMIN_PAYMENTS_PREFIX", { enumerable: true, get: function() {
|
|
1911
|
+
return admin_payments_api_1.ADMIN_PAYMENTS_PREFIX;
|
|
1912
|
+
} });
|
|
1913
|
+
Object.defineProperty(exports, "ADMIN_PAYMENTS_ROUTES", { enumerable: true, get: function() {
|
|
1914
|
+
return admin_payments_api_1.ADMIN_PAYMENTS_ROUTES;
|
|
1915
|
+
} });
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1876
1919
|
// ../shared/build/internal/subscriptions/subscription-source.enum.js
|
|
1877
1920
|
var require_subscription_source_enum = __commonJS({
|
|
1878
1921
|
"../shared/build/internal/subscriptions/subscription-source.enum.js"(exports) {
|
|
@@ -2090,6 +2133,7 @@ var require_internal = __commonJS({
|
|
|
2090
2133
|
__exportStar(require_exception_codes(), exports);
|
|
2091
2134
|
__exportStar(require_health(), exports);
|
|
2092
2135
|
__exportStar(require_admin(), exports);
|
|
2136
|
+
__exportStar(require_payments(), exports);
|
|
2093
2137
|
__exportStar(require_subscriptions2(), exports);
|
|
2094
2138
|
}
|
|
2095
2139
|
});
|
|
@@ -2428,7 +2472,8 @@ var require_agentic_sdlc_workflow = __commonJS({
|
|
|
2428
2472
|
"in-analysis": {
|
|
2429
2473
|
defaultNextSlug: "in-development",
|
|
2430
2474
|
outcomes: {
|
|
2431
|
-
[agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN]: "in-development"
|
|
2475
|
+
[agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN]: "in-development",
|
|
2476
|
+
[agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DEV]: "released"
|
|
2432
2477
|
}
|
|
2433
2478
|
},
|
|
2434
2479
|
"in-development": {
|
|
@@ -2470,7 +2515,7 @@ var require_agentic_sdlc_workflow = __commonJS({
|
|
|
2470
2515
|
nextAgentRole
|
|
2471
2516
|
};
|
|
2472
2517
|
}
|
|
2473
|
-
function resolveInAnalysisTransition(outcome, workflowPhase) {
|
|
2518
|
+
function resolveInAnalysisTransition(outcome, workflowPhase, context) {
|
|
2474
2519
|
if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.NEEDS_CLARIFICATION) {
|
|
2475
2520
|
return moveTransition(exports.IN_AWAITING_COLUMN_SLUG, workflowPhase, null);
|
|
2476
2521
|
}
|
|
@@ -2491,6 +2536,12 @@ var require_agentic_sdlc_workflow = __commonJS({
|
|
|
2491
2536
|
if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DESIGN) {
|
|
2492
2537
|
return moveTransition("in-development", workflow_phase_enum_1.WORKFLOW_PHASE.DEVELOPMENT, null);
|
|
2493
2538
|
}
|
|
2539
|
+
if (outcome === agent_run_outcome_enum_1.AGENT_RUN_OUTCOME.SKIP_DEV) {
|
|
2540
|
+
if (context.codeChangesRequired !== false) {
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
return moveTransition("released", null, null);
|
|
2544
|
+
}
|
|
2494
2545
|
return null;
|
|
2495
2546
|
}
|
|
2496
2547
|
if (workflowPhase === workflow_phase_enum_1.WORKFLOW_PHASE.ARCHITECT) {
|
|
@@ -2566,7 +2617,7 @@ var require_agentic_sdlc_workflow = __commonJS({
|
|
|
2566
2617
|
}
|
|
2567
2618
|
if (currentColumnSlug === "in-analysis") {
|
|
2568
2619
|
const phase = context.workflowPhase ?? (0, workflow_phase_subagent_role_map_1.subagentRoleToWorkflowPhase)(context.agentRole);
|
|
2569
|
-
return resolveInAnalysisTransition(outcome, phase);
|
|
2620
|
+
return resolveInAnalysisTransition(outcome, phase, context);
|
|
2570
2621
|
}
|
|
2571
2622
|
if (currentColumnSlug === "in-development") {
|
|
2572
2623
|
return resolveInDevelopmentTransition(outcome, context);
|
|
@@ -3326,7 +3377,8 @@ var require_mcp_sensitive_data_util = __commonJS({
|
|
|
3326
3377
|
exports.MCP_REDACTED_HOST = "[REDACTED_HOST]";
|
|
3327
3378
|
exports.MCP_REDACTED_PORT = "[REDACTED_PORT]";
|
|
3328
3379
|
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;
|
|
3380
|
+
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;
|
|
3381
|
+
var PAN_PATTERN = /\b(?:\d[ -]*?){13,19}\b/g;
|
|
3330
3382
|
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
3383
|
var JWT_PATTERN = /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
3332
3384
|
var BEARER_PATTERN = /Bearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
@@ -3373,6 +3425,7 @@ var require_mcp_sensitive_data_util = __commonJS({
|
|
|
3373
3425
|
result = result.replace(HTTPS_REMOTE_URL_PATTERN, `${exports.MCP_REDACTED_HOST}/...`);
|
|
3374
3426
|
result = result.replace(ENV_ASSIGNMENT_PATTERN, `${exports.MCP_REDACTED}_ENV`);
|
|
3375
3427
|
result = result.replace(NPM_TOKEN_PATTERN, `NpmToken.${exports.MCP_REDACTED}`);
|
|
3428
|
+
result = result.replace(PAN_PATTERN, exports.MCP_REDACTED);
|
|
3376
3429
|
return result;
|
|
3377
3430
|
}
|
|
3378
3431
|
function redactSensitiveValueDeep(value, workspaceRoot) {
|
|
@@ -3551,43 +3604,44 @@ var require_build2 = __commonJS({
|
|
|
3551
3604
|
}
|
|
3552
3605
|
});
|
|
3553
3606
|
|
|
3554
|
-
// src/
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3607
|
+
// src/orchestrator/orchestrator-transport.ts
|
|
3608
|
+
var import_agent_runs2 = __toESM(require_agent_runs_api(), 1);
|
|
3609
|
+
var import_shared5 = __toESM(require_build2(), 1);
|
|
3558
3610
|
|
|
3559
|
-
// src/
|
|
3560
|
-
var
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3611
|
+
// src/tools/attachments.ts
|
|
3612
|
+
var import_attachments = __toESM(require_attachments_api(), 1);
|
|
3613
|
+
var import_shared3 = __toESM(require_build2(), 1);
|
|
3614
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
3615
|
+
import { z } from "zod";
|
|
3616
|
+
|
|
3617
|
+
// src/attachments/detect-mime-type.ts
|
|
3618
|
+
import { basename } from "node:path";
|
|
3619
|
+
import { lookup as lookupMimeType } from "mime-types";
|
|
3620
|
+
function detectMimeType(filePath) {
|
|
3621
|
+
const mimeType = lookupMimeType(basename(filePath));
|
|
3622
|
+
return typeof mimeType === "string" ? mimeType : "application/octet-stream";
|
|
3568
3623
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3624
|
+
|
|
3625
|
+
// src/attachments/staging.util.ts
|
|
3626
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3627
|
+
import { dirname, resolve } from "node:path";
|
|
3628
|
+
var MAX_FILE_NAME_LENGTH = 200;
|
|
3629
|
+
function sanitizeFileName(name) {
|
|
3630
|
+
const baseName = name.split(/[/\\]/).pop() ?? name;
|
|
3631
|
+
const sanitized = baseName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
3632
|
+
return sanitized.length > MAX_FILE_NAME_LENGTH ? sanitized.slice(0, MAX_FILE_NAME_LENGTH) : sanitized;
|
|
3576
3633
|
}
|
|
3577
|
-
function
|
|
3578
|
-
const
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
sanitizeResponses: resolveSanitizeResponses(),
|
|
3589
|
-
blockSensitiveAttachments: resolveBlockSensitiveAttachments()
|
|
3590
|
-
};
|
|
3634
|
+
function buildStagingPath(workspaceRoot, workItemId, attachmentId, fileName) {
|
|
3635
|
+
const sanitizedFileName = sanitizeFileName(fileName);
|
|
3636
|
+
return resolve(workspaceRoot, ".task-boards", "attachments", workItemId, `${attachmentId}_${sanitizedFileName}`);
|
|
3637
|
+
}
|
|
3638
|
+
function buildStagingDir(workspaceRoot, workItemId) {
|
|
3639
|
+
return resolve(workspaceRoot, ".task-boards", "attachments", workItemId);
|
|
3640
|
+
}
|
|
3641
|
+
async function writeStagingFile(path, data) {
|
|
3642
|
+
const absolutePath = resolve(path);
|
|
3643
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
3644
|
+
await writeFile(absolutePath, data);
|
|
3591
3645
|
}
|
|
3592
3646
|
|
|
3593
3647
|
// src/rest-client.ts
|
|
@@ -3694,6 +3748,121 @@ var RestClient = class {
|
|
|
3694
3748
|
}
|
|
3695
3749
|
};
|
|
3696
3750
|
|
|
3751
|
+
// src/workspace/confine-file-path.ts
|
|
3752
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3753
|
+
import { basename as basename2, isAbsolute, relative, resolve as resolve2 } from "node:path";
|
|
3754
|
+
|
|
3755
|
+
// src/workspace/workspace-error.ts
|
|
3756
|
+
var WorkspaceError = class extends Error {
|
|
3757
|
+
constructor(code, message) {
|
|
3758
|
+
super(message);
|
|
3759
|
+
this.code = code;
|
|
3760
|
+
this.name = "WorkspaceError";
|
|
3761
|
+
}
|
|
3762
|
+
};
|
|
3763
|
+
|
|
3764
|
+
// src/workspace/confine-file-path.ts
|
|
3765
|
+
function confineFilePath(workspaceRoot, filePath) {
|
|
3766
|
+
const resolvedRoot = resolve2(workspaceRoot);
|
|
3767
|
+
const resolvedPath = isAbsolute(filePath) ? resolve2(filePath) : resolve2(resolvedRoot, filePath);
|
|
3768
|
+
const relativePath = relative(resolvedRoot, resolvedPath);
|
|
3769
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
3770
|
+
throw new WorkspaceError(
|
|
3771
|
+
"FILE_OUT_OF_BOUNDS",
|
|
3772
|
+
`filePath is outside workspaceRoot: ${basename2(filePath) || filePath}`
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3775
|
+
if (!existsSync(resolvedPath)) {
|
|
3776
|
+
throw new WorkspaceError("FILE_NOT_FOUND", `filePath does not exist: ${basename2(filePath) || filePath}`);
|
|
3777
|
+
}
|
|
3778
|
+
const stats = statSync(resolvedPath);
|
|
3779
|
+
if (!stats.isFile()) {
|
|
3780
|
+
throw new WorkspaceError("FILE_NOT_FOUND", `filePath is not a file: ${basename2(filePath) || filePath}`);
|
|
3781
|
+
}
|
|
3782
|
+
return resolvedPath;
|
|
3783
|
+
}
|
|
3784
|
+
function readConfinedWorkspaceFile(workspaceRoot, filePath) {
|
|
3785
|
+
const absolutePath = confineFilePath(workspaceRoot, filePath);
|
|
3786
|
+
const stats = statSync(absolutePath);
|
|
3787
|
+
const buffer = readFileSync(absolutePath);
|
|
3788
|
+
return {
|
|
3789
|
+
absolutePath,
|
|
3790
|
+
fileName: basename2(absolutePath),
|
|
3791
|
+
sizeBytes: stats.size,
|
|
3792
|
+
buffer
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
// src/workspace/resolve-workspace-root.ts
|
|
3797
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "node:fs";
|
|
3798
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve3 } from "node:path";
|
|
3799
|
+
var TASK_BOARDS_FILE = ".task-boards.yaml";
|
|
3800
|
+
var MAX_UPWARD_LEVELS = 20;
|
|
3801
|
+
function assertDirectory(path, source) {
|
|
3802
|
+
const absolutePath = resolve3(path);
|
|
3803
|
+
if (!existsSync2(absolutePath)) {
|
|
3804
|
+
throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} does not exist: ${absolutePath}`);
|
|
3805
|
+
}
|
|
3806
|
+
const stats = statSync2(absolutePath);
|
|
3807
|
+
if (!stats.isDirectory()) {
|
|
3808
|
+
throw new WorkspaceError("WORKSPACE_NOT_FOUND", `${source} is not a directory: ${absolutePath}`);
|
|
3809
|
+
}
|
|
3810
|
+
return absolutePath;
|
|
3811
|
+
}
|
|
3812
|
+
function assertWithinAllowedRoot(target, allowedRoot) {
|
|
3813
|
+
const relativePath = relative2(allowedRoot, target);
|
|
3814
|
+
const isNested = relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute2(relativePath);
|
|
3815
|
+
if (target !== allowedRoot && !isNested) {
|
|
3816
|
+
throw new WorkspaceError(
|
|
3817
|
+
"WORKSPACE_OUT_OF_BOUNDS",
|
|
3818
|
+
`workspaceRoot is outside the allowed WORKSPACE_ROOT: ${target}`
|
|
3819
|
+
);
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
function assertGitRepository(absolutePath) {
|
|
3823
|
+
if (!existsSync2(join(absolutePath, ".git"))) {
|
|
3824
|
+
throw new WorkspaceError("WORKSPACE_NOT_GIT_REPO", `workspaceRoot is not a git repository: ${absolutePath}`);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function findWorkspaceRootUpward(startDir) {
|
|
3828
|
+
let current = resolve3(startDir);
|
|
3829
|
+
for (let level = 0; level <= MAX_UPWARD_LEVELS; level += 1) {
|
|
3830
|
+
const markerPath = join(current, TASK_BOARDS_FILE);
|
|
3831
|
+
if (existsSync2(markerPath)) {
|
|
3832
|
+
return current;
|
|
3833
|
+
}
|
|
3834
|
+
const parent = dirname2(current);
|
|
3835
|
+
if (parent === current) {
|
|
3836
|
+
break;
|
|
3837
|
+
}
|
|
3838
|
+
current = parent;
|
|
3839
|
+
}
|
|
3840
|
+
return null;
|
|
3841
|
+
}
|
|
3842
|
+
function resolveWorkspaceRoot(explicitOverride, envWorkspaceRoot) {
|
|
3843
|
+
const allowedRoot = envWorkspaceRoot !== void 0 && envWorkspaceRoot.trim() !== "" ? assertDirectory(envWorkspaceRoot, "WORKSPACE_ROOT") : void 0;
|
|
3844
|
+
if (explicitOverride !== void 0 && explicitOverride.trim() !== "") {
|
|
3845
|
+
const resolved = assertDirectory(explicitOverride, "workspaceRoot");
|
|
3846
|
+
if (allowedRoot !== void 0) {
|
|
3847
|
+
assertWithinAllowedRoot(resolved, allowedRoot);
|
|
3848
|
+
} else {
|
|
3849
|
+
assertGitRepository(resolved);
|
|
3850
|
+
}
|
|
3851
|
+
return resolved;
|
|
3852
|
+
}
|
|
3853
|
+
if (allowedRoot !== void 0) {
|
|
3854
|
+
return allowedRoot;
|
|
3855
|
+
}
|
|
3856
|
+
const fromMarker = findWorkspaceRootUpward(process.cwd());
|
|
3857
|
+
if (fromMarker !== null) {
|
|
3858
|
+
return fromMarker;
|
|
3859
|
+
}
|
|
3860
|
+
return resolve3(process.cwd());
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
// src/tools/tool-utils.ts
|
|
3864
|
+
var import_shared2 = __toESM(require_build2(), 1);
|
|
3865
|
+
|
|
3697
3866
|
// src/tool-runtime.ts
|
|
3698
3867
|
var serverConfig = null;
|
|
3699
3868
|
function initToolRuntime(config) {
|
|
@@ -3706,24 +3875,577 @@ function shouldSanitizeResponses() {
|
|
|
3706
3875
|
return serverConfig.sanitizeResponses !== false;
|
|
3707
3876
|
}
|
|
3708
3877
|
|
|
3709
|
-
// src/tools/
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3878
|
+
// src/tools/tool-utils.ts
|
|
3879
|
+
function jsonResult(data) {
|
|
3880
|
+
return {
|
|
3881
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
function toolError(code, message) {
|
|
3885
|
+
const sanitizedMessage = shouldSanitizeResponses() ? (0, import_shared2.sanitizeMcpErrorMessage)(message) : message;
|
|
3886
|
+
return {
|
|
3887
|
+
isError: true,
|
|
3888
|
+
content: [{ type: "text", text: JSON.stringify({ code, message: sanitizedMessage }) }]
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
function toToolError(error) {
|
|
3892
|
+
if (error instanceof RestClientError || error instanceof WorkspaceError) {
|
|
3893
|
+
return toolError(error.code, error.message);
|
|
3894
|
+
}
|
|
3895
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3896
|
+
return toolError("INTERNAL_ERROR", message);
|
|
3897
|
+
}
|
|
3898
|
+
async function runTool(handler, options) {
|
|
3899
|
+
try {
|
|
3900
|
+
let data = await handler();
|
|
3901
|
+
if (shouldSanitizeResponses()) {
|
|
3902
|
+
data = (0, import_shared2.sanitizeMcpToolResult)(data, { workspaceRoot: options?.workspaceRoot });
|
|
3903
|
+
}
|
|
3904
|
+
return jsonResult(data);
|
|
3905
|
+
} catch (error) {
|
|
3906
|
+
return toToolError(error);
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3718
3909
|
|
|
3719
|
-
// src/tools/
|
|
3720
|
-
var
|
|
3721
|
-
var
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3910
|
+
// src/tools/attachments.ts
|
|
3911
|
+
var MAX_ATTACHMENT_COUNT = 20;
|
|
3912
|
+
var MAX_TOTAL_BYTES = 200 * 1024 * 1024;
|
|
3913
|
+
async function fetchAttachments(client, workItemId) {
|
|
3914
|
+
return client.get(import_attachments.ATTACHMENTS_ROUTES.listAttachments(workItemId));
|
|
3915
|
+
}
|
|
3916
|
+
function filterAttachments(items, attachmentIds) {
|
|
3917
|
+
if (attachmentIds === void 0 || attachmentIds.length === 0) {
|
|
3918
|
+
return items;
|
|
3919
|
+
}
|
|
3920
|
+
const idSet = new Set(attachmentIds);
|
|
3921
|
+
return items.filter((item) => idSet.has(item.id));
|
|
3922
|
+
}
|
|
3923
|
+
function assertDownloadLimits(attachments) {
|
|
3924
|
+
if (attachments.length > MAX_ATTACHMENT_COUNT) {
|
|
3925
|
+
throw new RestClientError(
|
|
3926
|
+
"ATTACHMENT_LIMIT_EXCEEDED",
|
|
3927
|
+
`Cannot download more than ${MAX_ATTACHMENT_COUNT} attachments at once (requested ${attachments.length})`
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
const totalBytes = attachments.reduce((sum, attachment) => sum + attachment.sizeBytes, 0);
|
|
3931
|
+
if (totalBytes > MAX_TOTAL_BYTES) {
|
|
3932
|
+
throw new RestClientError(
|
|
3933
|
+
"ATTACHMENT_SIZE_LIMIT_EXCEEDED",
|
|
3934
|
+
`Total attachment size ${totalBytes} bytes exceeds limit of ${MAX_TOTAL_BYTES} bytes`
|
|
3935
|
+
);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
async function downloadWorkItemAttachments(client, params) {
|
|
3939
|
+
const { workItemId, workspaceRoot, attachmentIds, overwrite = false, blockSensitiveAttachments = true } = params;
|
|
3940
|
+
const listResponse = await fetchAttachments(client, workItemId);
|
|
3941
|
+
const selected = filterAttachments(listResponse.items, attachmentIds);
|
|
3942
|
+
assertDownloadLimits(selected);
|
|
3943
|
+
const stagingDir = buildStagingDir(workspaceRoot, workItemId);
|
|
3944
|
+
const downloaded = [];
|
|
3945
|
+
const skipped = [];
|
|
3946
|
+
if (attachmentIds !== void 0 && attachmentIds.length > 0) {
|
|
3947
|
+
const selectedIds = new Set(selected.map((item) => item.id));
|
|
3948
|
+
for (const attachmentId of attachmentIds) {
|
|
3949
|
+
if (!selectedIds.has(attachmentId)) {
|
|
3950
|
+
skipped.push({ attachmentId, reason: "not found on work item" });
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
for (const attachment of selected) {
|
|
3955
|
+
if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(attachment.fileName)) {
|
|
3956
|
+
skipped.push({ attachmentId: attachment.id, reason: "blocked_sensitive_filename" });
|
|
3957
|
+
continue;
|
|
3958
|
+
}
|
|
3959
|
+
const stagingPath = buildStagingPath(workspaceRoot, workItemId, attachment.id, attachment.fileName);
|
|
3960
|
+
if (!overwrite && existsSync3(stagingPath)) {
|
|
3961
|
+
skipped.push({ attachmentId: attachment.id, reason: "file already exists (set overwrite=true to replace)" });
|
|
3962
|
+
continue;
|
|
3963
|
+
}
|
|
3964
|
+
const { data } = await client.downloadBinary(import_attachments.ATTACHMENTS_ROUTES.downloadAttachment(workItemId, attachment.id));
|
|
3965
|
+
await writeStagingFile(stagingPath, data);
|
|
3966
|
+
downloaded.push({
|
|
3967
|
+
attachmentId: attachment.id,
|
|
3968
|
+
fileName: attachment.fileName,
|
|
3969
|
+
path: stagingPath,
|
|
3970
|
+
sizeBytes: data.length
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
return {
|
|
3974
|
+
workItemId,
|
|
3975
|
+
downloaded,
|
|
3976
|
+
skipped,
|
|
3977
|
+
stagingDir
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
async function uploadWorkItemAttachment(client, params) {
|
|
3981
|
+
const { workItemId, workspaceRoot, filePath, blockSensitiveAttachments = true } = params;
|
|
3982
|
+
const confinedFile = readConfinedWorkspaceFile(workspaceRoot, filePath);
|
|
3983
|
+
if (blockSensitiveAttachments && (0, import_shared3.isBlockedMcpAttachmentFileName)(confinedFile.fileName)) {
|
|
3984
|
+
throw new RestClientError(
|
|
3985
|
+
"BLOCKED_SENSITIVE_FILENAME",
|
|
3986
|
+
`Attachment filename is blocked for MCP upload: ${confinedFile.fileName}`
|
|
3987
|
+
);
|
|
3988
|
+
}
|
|
3989
|
+
const mimeType = detectMimeType(confinedFile.absolutePath);
|
|
3990
|
+
const attachment = await client.uploadMultipart(
|
|
3991
|
+
import_attachments.ATTACHMENTS_ROUTES.uploadAttachment(workItemId),
|
|
3992
|
+
"file",
|
|
3993
|
+
{
|
|
3994
|
+
buffer: confinedFile.buffer,
|
|
3995
|
+
fileName: confinedFile.fileName,
|
|
3996
|
+
mimeType
|
|
3997
|
+
}
|
|
3998
|
+
);
|
|
3999
|
+
return {
|
|
4000
|
+
workItemId,
|
|
4001
|
+
attachment,
|
|
4002
|
+
sourcePath: confinedFile.absolutePath
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
function registerAttachmentTools(server, client, config) {
|
|
4006
|
+
server.registerTool(
|
|
4007
|
+
"list_work_item_attachments",
|
|
4008
|
+
{
|
|
4009
|
+
description: "List file attachments for a work item.",
|
|
4010
|
+
inputSchema: {
|
|
4011
|
+
workItemId: z.string().uuid().describe("Work item UUID")
|
|
4012
|
+
}
|
|
4013
|
+
},
|
|
4014
|
+
async ({ workItemId }) => runTool(async () => fetchAttachments(client, workItemId))
|
|
4015
|
+
);
|
|
4016
|
+
server.registerTool(
|
|
4017
|
+
"download_work_item_attachments",
|
|
4018
|
+
{
|
|
4019
|
+
description: "Download work item attachments into the local workspace staging directory (.task-boards/attachments).",
|
|
4020
|
+
inputSchema: {
|
|
4021
|
+
workItemId: z.string().uuid().describe("Work item UUID"),
|
|
4022
|
+
attachmentIds: z.array(z.string().uuid()).optional().describe("Optional subset of attachment UUIDs; downloads all when omitted"),
|
|
4023
|
+
overwrite: z.boolean().optional().default(false).describe("Replace existing staged files when true"),
|
|
4024
|
+
workspaceRoot: z.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
|
|
4025
|
+
}
|
|
4026
|
+
},
|
|
4027
|
+
async ({ workItemId, attachmentIds, overwrite, workspaceRoot }) => {
|
|
4028
|
+
const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
|
|
4029
|
+
return runTool(
|
|
4030
|
+
async () => downloadWorkItemAttachments(client, {
|
|
4031
|
+
workItemId,
|
|
4032
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
4033
|
+
attachmentIds,
|
|
4034
|
+
overwrite,
|
|
4035
|
+
blockSensitiveAttachments: config.blockSensitiveAttachments
|
|
4036
|
+
}),
|
|
4037
|
+
{ workspaceRoot: resolvedWorkspaceRoot }
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
);
|
|
4041
|
+
server.registerTool(
|
|
4042
|
+
"upload_work_item_attachment",
|
|
4043
|
+
{
|
|
4044
|
+
description: "Upload a local workspace file as a work item attachment (multipart field file).",
|
|
4045
|
+
inputSchema: {
|
|
4046
|
+
workItemId: z.string().uuid().describe("Work item UUID"),
|
|
4047
|
+
filePath: z.string().describe("Absolute or workspace-relative path to the file; must stay within workspaceRoot"),
|
|
4048
|
+
workspaceRoot: z.string().optional().describe("Optional absolute path to git repo root; defaults to WORKSPACE_ROOT or upward search from cwd")
|
|
4049
|
+
}
|
|
4050
|
+
},
|
|
4051
|
+
async ({ workItemId, filePath, workspaceRoot }) => {
|
|
4052
|
+
const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot, config.workspaceRoot);
|
|
4053
|
+
return runTool(
|
|
4054
|
+
async () => uploadWorkItemAttachment(client, {
|
|
4055
|
+
workItemId,
|
|
4056
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
4057
|
+
filePath,
|
|
4058
|
+
blockSensitiveAttachments: config.blockSensitiveAttachments
|
|
4059
|
+
}),
|
|
4060
|
+
{ workspaceRoot: resolvedWorkspaceRoot }
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
);
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// src/tools/comments.ts
|
|
4067
|
+
var import_comments = __toESM(require_comments_api(), 1);
|
|
4068
|
+
import { z as z2 } from "zod";
|
|
4069
|
+
async function fetchComments(client, workItemId) {
|
|
4070
|
+
return client.get(import_comments.COMMENTS_ROUTES.listComments(workItemId));
|
|
4071
|
+
}
|
|
4072
|
+
async function postComment(client, workItemId, body) {
|
|
4073
|
+
const request = { body };
|
|
4074
|
+
return client.post(import_comments.COMMENTS_ROUTES.createComment(workItemId), request);
|
|
4075
|
+
}
|
|
4076
|
+
function registerCommentTools(server, client) {
|
|
4077
|
+
server.registerTool(
|
|
4078
|
+
"list_comments",
|
|
4079
|
+
{
|
|
4080
|
+
description: "List comments for a work item (newest last).",
|
|
4081
|
+
inputSchema: {
|
|
4082
|
+
workItemId: z2.string().uuid().describe("Work item UUID")
|
|
4083
|
+
}
|
|
4084
|
+
},
|
|
4085
|
+
async ({ workItemId }) => runTool(async () => fetchComments(client, workItemId))
|
|
4086
|
+
);
|
|
4087
|
+
server.registerTool(
|
|
4088
|
+
"create_comment",
|
|
4089
|
+
{
|
|
4090
|
+
description: "Create a comment on a work item. Use for orchestrator NEEDS_CLARIFICATION questions before complete_agent_run.",
|
|
4091
|
+
inputSchema: {
|
|
4092
|
+
workItemId: z2.string().uuid().describe("Work item UUID"),
|
|
4093
|
+
body: z2.string().min(1).describe("Comment body (markdown supported)")
|
|
4094
|
+
}
|
|
4095
|
+
},
|
|
4096
|
+
async ({ workItemId, body }) => runTool(async () => postComment(client, workItemId, body))
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
// src/subagents/sync-project-subagents.ts
|
|
4101
|
+
var import_shared4 = __toESM(require_build2(), 1);
|
|
4102
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4103
|
+
import { join as join2 } from "node:path";
|
|
4104
|
+
var SAFE_SUBAGENT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4105
|
+
var MAX_SUBAGENT_SLUG_LENGTH = 64;
|
|
4106
|
+
function isSyncableSlug(slug) {
|
|
4107
|
+
const trimmed = slug.trim();
|
|
4108
|
+
return trimmed.length > 0 && trimmed.length <= MAX_SUBAGENT_SLUG_LENGTH && SAFE_SUBAGENT_SLUG_PATTERN.test(trimmed);
|
|
4109
|
+
}
|
|
4110
|
+
function buildFileContent(subagent) {
|
|
4111
|
+
return (0, import_shared4.buildSubagentFileContentFromBody)({
|
|
4112
|
+
slug: subagent.slug,
|
|
4113
|
+
description: subagent.description,
|
|
4114
|
+
bodyMarkdown: subagent.bodyMarkdown
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
async function syncProjectSubagents(client, params) {
|
|
4118
|
+
const { projectId, workspaceRoot } = params;
|
|
4119
|
+
const listResponse = await client.get(import_shared4.PROJECT_SUBAGENTS_ROUTES.listSubagents(projectId));
|
|
4120
|
+
const directory = join2(workspaceRoot, ".cursor", "agents");
|
|
4121
|
+
mkdirSync(directory, { recursive: true });
|
|
4122
|
+
const synced = [];
|
|
4123
|
+
const skipped = [];
|
|
4124
|
+
for (const subagent of listResponse.items) {
|
|
4125
|
+
if (!isSyncableSlug(subagent.slug)) {
|
|
4126
|
+
skipped.push(subagent.slug);
|
|
4127
|
+
continue;
|
|
4128
|
+
}
|
|
4129
|
+
const filePath = join2(directory, `${subagent.slug}.md`);
|
|
4130
|
+
writeFileSync(filePath, buildFileContent(subagent), "utf8");
|
|
4131
|
+
synced.push(subagent.slug);
|
|
4132
|
+
}
|
|
4133
|
+
return { synced, skipped, directory };
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
// src/tools/wait-for-agent-runs.ts
|
|
4137
|
+
var import_agent_runs = __toESM(require_agent_runs_api(), 1);
|
|
4138
|
+
var import_agent_run_status = __toESM(require_agent_run_status_enum(), 1);
|
|
4139
|
+
var DEFAULT_RETRY_BASE_MS = 500;
|
|
4140
|
+
var DEFAULT_RETRY_MAX_MS = 3e4;
|
|
4141
|
+
var DEFAULT_INFLIGHT_LIMIT = 10;
|
|
4142
|
+
function sleep(ms) {
|
|
4143
|
+
return new Promise((resolve4) => {
|
|
4144
|
+
setTimeout(resolve4, ms);
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
function isTransientClaimError(error) {
|
|
4148
|
+
if (!(error instanceof RestClientError)) {
|
|
4149
|
+
return false;
|
|
4150
|
+
}
|
|
4151
|
+
if (error.status === void 0) {
|
|
4152
|
+
return error.code === "HTTP_ERROR";
|
|
4153
|
+
}
|
|
4154
|
+
return error.status >= 500;
|
|
4155
|
+
}
|
|
4156
|
+
function computeBackoffMs(consecutiveFailures, baseMs, maxMs) {
|
|
4157
|
+
const exponential = baseMs * 2 ** (consecutiveFailures - 1);
|
|
4158
|
+
return Math.min(exponential, maxMs);
|
|
4159
|
+
}
|
|
4160
|
+
function compareInflightRuns(a, b) {
|
|
4161
|
+
if (a.requiresAttention !== b.requiresAttention) {
|
|
4162
|
+
return a.requiresAttention ? -1 : 1;
|
|
4163
|
+
}
|
|
4164
|
+
const aDispatched = a.dispatchedAt ?? "";
|
|
4165
|
+
const bDispatched = b.dispatchedAt ?? "";
|
|
4166
|
+
return aDispatched.localeCompare(bDispatched);
|
|
4167
|
+
}
|
|
4168
|
+
async function fetchInflightAgentRuns(client, options) {
|
|
4169
|
+
const limit = options.limit ?? DEFAULT_INFLIGHT_LIMIT;
|
|
4170
|
+
const page = await client.get(import_agent_runs.AGENT_RUNS_ROUTES.listAgentRuns(), {
|
|
4171
|
+
projectId: options.projectId,
|
|
4172
|
+
activeOnly: "true",
|
|
4173
|
+
limit: String(limit)
|
|
4174
|
+
});
|
|
4175
|
+
const filtered = page.items.filter(
|
|
4176
|
+
(run) => run.status === import_agent_run_status.AGENT_RUN_STATUS.DISPATCHED || run.status === import_agent_run_status.AGENT_RUN_STATUS.ACKNOWLEDGED
|
|
4177
|
+
);
|
|
4178
|
+
filtered.sort(compareInflightRuns);
|
|
4179
|
+
return filtered.slice(0, limit);
|
|
4180
|
+
}
|
|
4181
|
+
async function pollAgentRuns(client, options, deps) {
|
|
4182
|
+
const { projectId, timeoutMs, pollIntervalMs, limit } = options;
|
|
4183
|
+
if (pollIntervalMs > timeoutMs) {
|
|
4184
|
+
throw new WorkspaceError("VALIDATION_ERROR", "pollInterval must not exceed timeout");
|
|
4185
|
+
}
|
|
4186
|
+
const sleepFn = deps?.sleep ?? sleep;
|
|
4187
|
+
const deadline = Date.now() + timeoutMs;
|
|
4188
|
+
const claimLimit = limit ?? 1;
|
|
4189
|
+
const retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
4190
|
+
const retryMaxMs = options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS;
|
|
4191
|
+
let consecutiveFailures = 0;
|
|
4192
|
+
while (true) {
|
|
4193
|
+
let response;
|
|
4194
|
+
try {
|
|
4195
|
+
response = await client.post(import_agent_runs.AGENT_RUNS_ROUTES.claimAgentRuns(), {
|
|
4196
|
+
projectId,
|
|
4197
|
+
limit: claimLimit
|
|
4198
|
+
});
|
|
4199
|
+
consecutiveFailures = 0;
|
|
4200
|
+
} catch (error) {
|
|
4201
|
+
if (!isTransientClaimError(error)) {
|
|
4202
|
+
throw error;
|
|
4203
|
+
}
|
|
4204
|
+
if (Date.now() >= deadline) {
|
|
4205
|
+
return { items: [], inflightRuns: [], timedOut: true };
|
|
4206
|
+
}
|
|
4207
|
+
consecutiveFailures += 1;
|
|
4208
|
+
const backoffMs = computeBackoffMs(consecutiveFailures, retryBaseMs, retryMaxMs);
|
|
4209
|
+
const remainingMs2 = deadline - Date.now();
|
|
4210
|
+
await sleepFn(Math.min(backoffMs, remainingMs2));
|
|
4211
|
+
continue;
|
|
4212
|
+
}
|
|
4213
|
+
if (response.items.length > 0) {
|
|
4214
|
+
return { items: response.items, inflightRuns: [], timedOut: false };
|
|
4215
|
+
}
|
|
4216
|
+
const inflightRuns = await fetchInflightAgentRuns(client, { projectId, limit: claimLimit });
|
|
4217
|
+
if (inflightRuns.length > 0) {
|
|
4218
|
+
return { items: [], inflightRuns, timedOut: false };
|
|
4219
|
+
}
|
|
4220
|
+
if (Date.now() >= deadline) {
|
|
4221
|
+
return { items: [], inflightRuns: [], timedOut: true };
|
|
4222
|
+
}
|
|
4223
|
+
const remainingMs = deadline - Date.now();
|
|
4224
|
+
const waitMs = Math.min(pollIntervalMs, remainingMs);
|
|
4225
|
+
await sleepFn(waitMs);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
// src/orchestrator/orchestrator-transport.ts
|
|
4230
|
+
var OrchestratorTransport = class {
|
|
4231
|
+
constructor(client, options = {}) {
|
|
4232
|
+
this.client = client;
|
|
4233
|
+
this.options = options;
|
|
4234
|
+
}
|
|
4235
|
+
async claimAndPoll(options, deps) {
|
|
4236
|
+
return pollAgentRuns(this.client, options, deps);
|
|
4237
|
+
}
|
|
4238
|
+
async fetchInflightRuns(projectId, limit) {
|
|
4239
|
+
return fetchInflightAgentRuns(this.client, { projectId, limit });
|
|
4240
|
+
}
|
|
4241
|
+
async ackRun(agentRunId, note) {
|
|
4242
|
+
const body = note !== void 0 ? { note } : void 0;
|
|
4243
|
+
return this.client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.acknowledgeAgentRun(agentRunId), body);
|
|
4244
|
+
}
|
|
4245
|
+
async setAttention(agentRunId, requiresAttention, message) {
|
|
4246
|
+
const body = requiresAttention ? { requiresAttention: true, attentionMessage: message ?? "" } : { requiresAttention: false };
|
|
4247
|
+
return this.client.patch(import_agent_runs2.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
|
|
4248
|
+
}
|
|
4249
|
+
async completeRun(agentRunId, body) {
|
|
4250
|
+
return this.client.post(import_agent_runs2.AGENT_RUNS_ROUTES.completeAgentRun(agentRunId), body);
|
|
4251
|
+
}
|
|
4252
|
+
async listAttachments(workItemId) {
|
|
4253
|
+
return fetchAttachments(this.client, workItemId);
|
|
4254
|
+
}
|
|
4255
|
+
async downloadAttachments(workItemId, workspaceRoot, attachmentIds) {
|
|
4256
|
+
return downloadWorkItemAttachments(this.client, {
|
|
4257
|
+
workItemId,
|
|
4258
|
+
workspaceRoot,
|
|
4259
|
+
attachmentIds,
|
|
4260
|
+
blockSensitiveAttachments: this.options.blockSensitiveAttachments ?? true
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
async syncSubagents(projectId, workspaceRoot) {
|
|
4264
|
+
return syncProjectSubagents(this.client, { projectId, workspaceRoot });
|
|
4265
|
+
}
|
|
4266
|
+
async createComment(workItemId, body) {
|
|
4267
|
+
return postComment(this.client, workItemId, body);
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
function createOrchestratorTransport(client, config) {
|
|
4271
|
+
return new OrchestratorTransport(client, {
|
|
4272
|
+
workspaceRoot: config?.workspaceRoot,
|
|
4273
|
+
blockSensitiveAttachments: config?.blockSensitiveAttachments
|
|
4274
|
+
});
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
// src/orchestrator/active-run-state.ts
|
|
4278
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4279
|
+
import { join as join3 } from "node:path";
|
|
4280
|
+
var ACTIVE_RUN_STATE_RELATIVE_PATH = ".cursor/orchestrator-active-run.json";
|
|
4281
|
+
var ATTENTION_PENDING_RELATIVE_PATH = ".cursor/orchestrator-attention-pending.json";
|
|
4282
|
+
var ACTIVE_RUN_LEASE_MINUTES = 30;
|
|
4283
|
+
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;
|
|
4284
|
+
function isRecord(value) {
|
|
4285
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4286
|
+
}
|
|
4287
|
+
function isUuid(value) {
|
|
4288
|
+
return UUID_PATTERN.test(value);
|
|
4289
|
+
}
|
|
4290
|
+
function isIsoDateTimeString(value) {
|
|
4291
|
+
return !Number.isNaN(Date.parse(value));
|
|
4292
|
+
}
|
|
4293
|
+
function getActiveRunStatePath(workspaceRoot) {
|
|
4294
|
+
return join3(workspaceRoot, ACTIVE_RUN_STATE_RELATIVE_PATH);
|
|
4295
|
+
}
|
|
4296
|
+
function getAttentionPendingPath(workspaceRoot) {
|
|
4297
|
+
return join3(workspaceRoot, ATTENTION_PENDING_RELATIVE_PATH);
|
|
4298
|
+
}
|
|
4299
|
+
function computeExpiresAt(acknowledgedAt, leaseMinutes = ACTIVE_RUN_LEASE_MINUTES) {
|
|
4300
|
+
const expiresMs = Date.parse(acknowledgedAt) + leaseMinutes * 60 * 1e3;
|
|
4301
|
+
return new Date(expiresMs).toISOString();
|
|
4302
|
+
}
|
|
4303
|
+
function ensureCursorDir(workspaceRoot) {
|
|
4304
|
+
const cursorDir = join3(workspaceRoot, ".cursor");
|
|
4305
|
+
if (!existsSync4(cursorDir)) {
|
|
4306
|
+
mkdirSync2(cursorDir, { recursive: true });
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function parseActiveRunState(raw) {
|
|
4310
|
+
if (!isRecord(raw)) {
|
|
4311
|
+
return null;
|
|
4312
|
+
}
|
|
4313
|
+
const version = raw.version;
|
|
4314
|
+
const agentRunId = raw.agentRunId;
|
|
4315
|
+
const workItemId = raw.workItemId;
|
|
4316
|
+
const projectId = raw.projectId;
|
|
4317
|
+
const acknowledgedAt = raw.acknowledgedAt;
|
|
4318
|
+
const expiresAt = raw.expiresAt;
|
|
4319
|
+
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)) {
|
|
4320
|
+
return null;
|
|
4321
|
+
}
|
|
4322
|
+
return {
|
|
4323
|
+
version: 1,
|
|
4324
|
+
agentRunId,
|
|
4325
|
+
workItemId,
|
|
4326
|
+
projectId,
|
|
4327
|
+
acknowledgedAt,
|
|
4328
|
+
expiresAt
|
|
4329
|
+
};
|
|
4330
|
+
}
|
|
4331
|
+
function parseAttentionPendingState(raw) {
|
|
4332
|
+
if (!isRecord(raw)) {
|
|
4333
|
+
return null;
|
|
4334
|
+
}
|
|
4335
|
+
const agentRunId = raw.agentRunId;
|
|
4336
|
+
const reportedAt = raw.reportedAt;
|
|
4337
|
+
if (typeof agentRunId !== "string" || typeof reportedAt !== "string" || !isUuid(agentRunId) || !isIsoDateTimeString(reportedAt)) {
|
|
4338
|
+
return null;
|
|
4339
|
+
}
|
|
4340
|
+
return { agentRunId, reportedAt };
|
|
4341
|
+
}
|
|
4342
|
+
function isActiveRunStateUsable(state, nowMs = Date.now()) {
|
|
4343
|
+
if (state === null) {
|
|
4344
|
+
return false;
|
|
4345
|
+
}
|
|
4346
|
+
const expiresMs = Date.parse(state.expiresAt);
|
|
4347
|
+
if (Number.isNaN(expiresMs)) {
|
|
4348
|
+
return false;
|
|
4349
|
+
}
|
|
4350
|
+
return nowMs < expiresMs;
|
|
4351
|
+
}
|
|
4352
|
+
function readActiveRunState(workspaceRoot) {
|
|
4353
|
+
const filePath = getActiveRunStatePath(workspaceRoot);
|
|
4354
|
+
if (!existsSync4(filePath)) {
|
|
4355
|
+
return null;
|
|
4356
|
+
}
|
|
4357
|
+
try {
|
|
4358
|
+
const content = readFileSync2(filePath, "utf8");
|
|
4359
|
+
const parsed = JSON.parse(content);
|
|
4360
|
+
return parseActiveRunState(parsed);
|
|
4361
|
+
} catch {
|
|
4362
|
+
return null;
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
function writeActiveRunState(workspaceRoot, input) {
|
|
4366
|
+
ensureCursorDir(workspaceRoot);
|
|
4367
|
+
const state = {
|
|
4368
|
+
version: 1,
|
|
4369
|
+
agentRunId: input.agentRunId,
|
|
4370
|
+
workItemId: input.workItemId,
|
|
4371
|
+
projectId: input.projectId,
|
|
4372
|
+
acknowledgedAt: input.acknowledgedAt,
|
|
4373
|
+
expiresAt: computeExpiresAt(input.acknowledgedAt)
|
|
4374
|
+
};
|
|
4375
|
+
writeFileSync2(getActiveRunStatePath(workspaceRoot), `${JSON.stringify(state, null, 2)}
|
|
4376
|
+
`, "utf8");
|
|
4377
|
+
return state;
|
|
4378
|
+
}
|
|
4379
|
+
function clearActiveRunState(workspaceRoot) {
|
|
4380
|
+
const filePath = getActiveRunStatePath(workspaceRoot);
|
|
4381
|
+
if (existsSync4(filePath)) {
|
|
4382
|
+
rmSync(filePath, { force: true });
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
function readAttentionPendingState(workspaceRoot) {
|
|
4386
|
+
const filePath = getAttentionPendingPath(workspaceRoot);
|
|
4387
|
+
if (!existsSync4(filePath)) {
|
|
4388
|
+
return null;
|
|
4389
|
+
}
|
|
4390
|
+
try {
|
|
4391
|
+
const content = readFileSync2(filePath, "utf8");
|
|
4392
|
+
const parsed = JSON.parse(content);
|
|
4393
|
+
return parseAttentionPendingState(parsed);
|
|
4394
|
+
} catch {
|
|
4395
|
+
return null;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
function writeAttentionPendingState(workspaceRoot, agentRunId) {
|
|
4399
|
+
ensureCursorDir(workspaceRoot);
|
|
4400
|
+
const pending = {
|
|
4401
|
+
agentRunId,
|
|
4402
|
+
reportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4403
|
+
};
|
|
4404
|
+
writeFileSync2(getAttentionPendingPath(workspaceRoot), `${JSON.stringify(pending, null, 2)}
|
|
4405
|
+
`, "utf8");
|
|
4406
|
+
return pending;
|
|
4407
|
+
}
|
|
4408
|
+
function clearAttentionPendingState(workspaceRoot) {
|
|
4409
|
+
const filePath = getAttentionPendingPath(workspaceRoot);
|
|
4410
|
+
if (existsSync4(filePath)) {
|
|
4411
|
+
rmSync(filePath, { force: true });
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
function clearOrchestratorBridgeState(workspaceRoot) {
|
|
4415
|
+
clearActiveRunState(workspaceRoot);
|
|
4416
|
+
clearAttentionPendingState(workspaceRoot);
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
// src/workspace/parse-task-boards-yaml.ts
|
|
4420
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
4421
|
+
import { join as join4 } from "node:path";
|
|
4422
|
+
var TASK_BOARDS_FILE2 = ".task-boards.yaml";
|
|
4423
|
+
var VERSION_PATTERN = /^\s*version\s*:\s*1\s*$/m;
|
|
4424
|
+
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;
|
|
4425
|
+
function parseTaskBoardsYaml(workspaceRoot) {
|
|
4426
|
+
const filePath = join4(workspaceRoot, TASK_BOARDS_FILE2);
|
|
4427
|
+
if (!existsSync5(filePath)) {
|
|
4428
|
+
return null;
|
|
4429
|
+
}
|
|
4430
|
+
const content = readFileSync3(filePath, "utf8");
|
|
4431
|
+
if (!VERSION_PATTERN.test(content)) {
|
|
4432
|
+
return null;
|
|
4433
|
+
}
|
|
4434
|
+
const match = PROJECT_ID_PATTERN.exec(content);
|
|
4435
|
+
if (match === null) {
|
|
4436
|
+
return null;
|
|
4437
|
+
}
|
|
4438
|
+
return match[1] ?? null;
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
// src/tools/orchestrator-attention.util.ts
|
|
4442
|
+
var import_agent_run_status2 = __toESM(require_agent_run_status_enum(), 1);
|
|
4443
|
+
var ATTENTION_MESSAGE_CATEGORY = {
|
|
4444
|
+
SHELL: "shell",
|
|
4445
|
+
GIT: "git",
|
|
4446
|
+
NETWORK: "network",
|
|
4447
|
+
SMART_MODE: "smart-mode",
|
|
4448
|
+
MCP: "mcp",
|
|
3727
4449
|
FILE: "file",
|
|
3728
4450
|
OTHER: "other"
|
|
3729
4451
|
};
|
|
@@ -3780,10 +4502,10 @@ function deriveOrchestratorRunPhase(run) {
|
|
|
3780
4502
|
if (run.requiresAttention) {
|
|
3781
4503
|
return "awaiting_ide_approval";
|
|
3782
4504
|
}
|
|
3783
|
-
if (run.status ===
|
|
4505
|
+
if (run.status === import_agent_run_status2.AGENT_RUN_STATUS.DISPATCHED) {
|
|
3784
4506
|
return "dispatched";
|
|
3785
4507
|
}
|
|
3786
|
-
if (run.status ===
|
|
4508
|
+
if (run.status === import_agent_run_status2.AGENT_RUN_STATUS.ACKNOWLEDGED) {
|
|
3787
4509
|
return "acknowledged";
|
|
3788
4510
|
}
|
|
3789
4511
|
if (run.acknowledgedAt !== null) {
|
|
@@ -3802,1114 +4524,1067 @@ function parseAttentionMessageCategory(message) {
|
|
|
3802
4524
|
}
|
|
3803
4525
|
return ATTENTION_MESSAGE_CATEGORY.OTHER;
|
|
3804
4526
|
}
|
|
4527
|
+
function formatAttentionMessage(category, detail) {
|
|
4528
|
+
return `[${category}] ${detail.trim()}`;
|
|
4529
|
+
}
|
|
3805
4530
|
function buildAttentionGuidance(run) {
|
|
3806
4531
|
const phase = deriveOrchestratorRunPhase(run);
|
|
3807
4532
|
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) {
|
|
4533
|
+
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`;
|
|
4534
|
+
const clearStep = run.requiresAttention ? `clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}` : null;
|
|
4034
4535
|
return {
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4536
|
+
phase,
|
|
4537
|
+
reportStep,
|
|
4538
|
+
clearStep,
|
|
4539
|
+
detectedCategory,
|
|
4540
|
+
templateExamples: ALL_TEMPLATE_CATEGORIES
|
|
4039
4541
|
};
|
|
4040
4542
|
}
|
|
4041
4543
|
|
|
4042
|
-
// src/
|
|
4043
|
-
var
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
this.name = "WorkspaceError";
|
|
4544
|
+
// src/attention/shell-category.util.ts
|
|
4545
|
+
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;
|
|
4546
|
+
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;
|
|
4547
|
+
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;
|
|
4548
|
+
function truncateCommand(command, maxLength) {
|
|
4549
|
+
const trimmed = command.trim();
|
|
4550
|
+
if (trimmed.length <= maxLength) {
|
|
4551
|
+
return trimmed;
|
|
4051
4552
|
}
|
|
4052
|
-
}
|
|
4553
|
+
return `${trimmed.slice(0, maxLength - 1)}\u2026`;
|
|
4554
|
+
}
|
|
4555
|
+
function classifyShellCommand(command) {
|
|
4556
|
+
const trimmed = command.trim();
|
|
4557
|
+
if (trimmed === "") {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
if (READONLY_SHELL_PATTERN.test(trimmed)) {
|
|
4561
|
+
return null;
|
|
4562
|
+
}
|
|
4563
|
+
if (GIT_DESTRUCTIVE_PATTERN.test(trimmed)) {
|
|
4564
|
+
return "git";
|
|
4565
|
+
}
|
|
4566
|
+
if (NETWORK_PATTERN.test(trimmed)) {
|
|
4567
|
+
return "network";
|
|
4568
|
+
}
|
|
4569
|
+
return "shell";
|
|
4570
|
+
}
|
|
4053
4571
|
|
|
4054
|
-
// src/
|
|
4055
|
-
function
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4572
|
+
// src/attention/attention-message.util.ts
|
|
4573
|
+
function buildShellAttentionMessage(category, command) {
|
|
4574
|
+
const truncated = truncateCommand(command, 200);
|
|
4575
|
+
switch (category) {
|
|
4576
|
+
case "git":
|
|
4577
|
+
return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.GIT, `Approve git operation: ${truncated}`);
|
|
4578
|
+
case "network":
|
|
4579
|
+
return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.NETWORK, `Approve network access: ${truncated}`);
|
|
4580
|
+
case "shell":
|
|
4581
|
+
return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.SHELL, `Approve shell command: ${truncated}`);
|
|
4582
|
+
}
|
|
4059
4583
|
}
|
|
4060
|
-
function
|
|
4061
|
-
const
|
|
4062
|
-
return {
|
|
4063
|
-
isError: true,
|
|
4064
|
-
content: [{ type: "text", text: JSON.stringify({ code, message: sanitizedMessage }) }]
|
|
4065
|
-
};
|
|
4584
|
+
function buildMcpAttentionMessage(server, toolName) {
|
|
4585
|
+
const detail = `${server}/${toolName}`.trim();
|
|
4586
|
+
return formatAttentionMessage(ATTENTION_MESSAGE_CATEGORY.MCP, `Approve MCP / API action: ${detail}`);
|
|
4066
4587
|
}
|
|
4067
|
-
function
|
|
4068
|
-
|
|
4069
|
-
|
|
4588
|
+
function buildSmartModeAttentionMessage(toolName, reason) {
|
|
4589
|
+
const detailReason = reason !== void 0 && reason !== null && reason.trim() !== "" ? reason.trim() : "elevated permissions required";
|
|
4590
|
+
return formatAttentionMessage(
|
|
4591
|
+
ATTENTION_MESSAGE_CATEGORY.SMART_MODE,
|
|
4592
|
+
`Approve Smart Mode: ${toolName} \u2014 ${detailReason}`
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
// src/attention/hook-payload.util.ts
|
|
4597
|
+
function isRecord2(value) {
|
|
4598
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4599
|
+
}
|
|
4600
|
+
function readString(record, key) {
|
|
4601
|
+
const value = record[key];
|
|
4602
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
4603
|
+
return null;
|
|
4070
4604
|
}
|
|
4071
|
-
|
|
4072
|
-
return toolError("INTERNAL_ERROR", message);
|
|
4605
|
+
return value;
|
|
4073
4606
|
}
|
|
4074
|
-
|
|
4607
|
+
function parseHookPayloadJson(raw) {
|
|
4608
|
+
const trimmed = raw.trim();
|
|
4609
|
+
if (trimmed === "") {
|
|
4610
|
+
return null;
|
|
4611
|
+
}
|
|
4075
4612
|
try {
|
|
4076
|
-
|
|
4077
|
-
if (
|
|
4078
|
-
|
|
4613
|
+
const parsed = JSON.parse(trimmed);
|
|
4614
|
+
if (!isRecord2(parsed)) {
|
|
4615
|
+
return null;
|
|
4079
4616
|
}
|
|
4080
|
-
return
|
|
4081
|
-
} catch
|
|
4082
|
-
return
|
|
4617
|
+
return parsed;
|
|
4618
|
+
} catch {
|
|
4619
|
+
return null;
|
|
4083
4620
|
}
|
|
4084
4621
|
}
|
|
4085
|
-
|
|
4086
|
-
|
|
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
|
-
});
|
|
4622
|
+
function extractShellCommand(payload) {
|
|
4623
|
+
return readString(payload, "command");
|
|
4096
4624
|
}
|
|
4097
|
-
function
|
|
4098
|
-
|
|
4099
|
-
|
|
4625
|
+
function extractMcpExecution(payload) {
|
|
4626
|
+
const server = readString(payload, "server") ?? readString(payload, "serverName") ?? readString(payload, "mcpServer");
|
|
4627
|
+
const toolName = readString(payload, "toolName") ?? readString(payload, "tool") ?? readString(payload, "mcpToolName");
|
|
4628
|
+
if (server === null || toolName === null) {
|
|
4629
|
+
return null;
|
|
4100
4630
|
}
|
|
4101
|
-
|
|
4102
|
-
|
|
4631
|
+
return { server, toolName };
|
|
4632
|
+
}
|
|
4633
|
+
function extractPreToolUse(payload) {
|
|
4634
|
+
const toolName = readString(payload, "tool_name") ?? readString(payload, "toolName") ?? readString(payload, "tool") ?? readString(payload, "name");
|
|
4635
|
+
if (toolName === null) {
|
|
4636
|
+
return null;
|
|
4103
4637
|
}
|
|
4104
|
-
|
|
4638
|
+
const smartModeReason = readString(payload, "block_reason") ?? readString(payload, "smartModeBlockReason") ?? readString(payload, "smart_mode_block_reason") ?? readString(payload, "reason");
|
|
4639
|
+
const permission = readString(payload, "permission");
|
|
4640
|
+
const smartMode = payload.smart_mode ?? payload.smartMode;
|
|
4641
|
+
const hasSmartModeSignal = smartModeReason !== null || permission === "ask" || smartMode === true || smartMode === "blocked";
|
|
4642
|
+
if (!hasSmartModeSignal) {
|
|
4643
|
+
return null;
|
|
4644
|
+
}
|
|
4645
|
+
return { toolName, smartModeReason };
|
|
4105
4646
|
}
|
|
4106
|
-
function
|
|
4107
|
-
|
|
4108
|
-
return Math.min(exponential, maxMs);
|
|
4647
|
+
function isAfterHookEvent(event) {
|
|
4648
|
+
return event === "afterShellExecution" || event === "afterMCPExecution" || event === "postToolUse";
|
|
4109
4649
|
}
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4650
|
+
|
|
4651
|
+
// src/attention/attention-cli.ts
|
|
4652
|
+
function defaultReadStdin() {
|
|
4653
|
+
return new Promise((resolve4, reject) => {
|
|
4654
|
+
const chunks = [];
|
|
4655
|
+
process.stdin.setEncoding("utf8");
|
|
4656
|
+
process.stdin.on("data", (chunk) => {
|
|
4657
|
+
chunks.push(Buffer.from(chunk));
|
|
4658
|
+
});
|
|
4659
|
+
process.stdin.on("end", () => {
|
|
4660
|
+
resolve4(Buffer.concat(chunks).toString("utf8"));
|
|
4661
|
+
});
|
|
4662
|
+
process.stdin.on("error", reject);
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4665
|
+
function resolveBridgeWorkspaceRoot(config) {
|
|
4666
|
+
try {
|
|
4667
|
+
return resolveWorkspaceRoot(void 0, config.workspaceRoot);
|
|
4668
|
+
} catch {
|
|
4669
|
+
return null;
|
|
4113
4670
|
}
|
|
4114
|
-
const aDispatched = a.dispatchedAt ?? "";
|
|
4115
|
-
const bDispatched = b.dispatchedAt ?? "";
|
|
4116
|
-
return aDispatched.localeCompare(bDispatched);
|
|
4117
4671
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
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);
|
|
4672
|
+
function resolveProjectId(workspaceRoot) {
|
|
4673
|
+
return parseTaskBoardsYaml(workspaceRoot);
|
|
4130
4674
|
}
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
throw new WorkspaceError("VALIDATION_ERROR", "pollInterval must not exceed timeout");
|
|
4675
|
+
function writeActiveRunFromAck(config, run) {
|
|
4676
|
+
if (config.workspaceRoot === void 0) {
|
|
4677
|
+
return;
|
|
4135
4678
|
}
|
|
4136
|
-
const
|
|
4137
|
-
|
|
4138
|
-
|
|
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);
|
|
4679
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(config);
|
|
4680
|
+
if (workspaceRoot === null) {
|
|
4681
|
+
return;
|
|
4176
4682
|
}
|
|
4683
|
+
const projectId = resolveProjectId(workspaceRoot);
|
|
4684
|
+
if (projectId === null) {
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4687
|
+
const acknowledgedAt = run.acknowledgedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4688
|
+
writeActiveRunState(workspaceRoot, {
|
|
4689
|
+
agentRunId: run.id,
|
|
4690
|
+
workItemId: run.workItemId,
|
|
4691
|
+
projectId,
|
|
4692
|
+
acknowledgedAt
|
|
4693
|
+
});
|
|
4177
4694
|
}
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
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, "");
|
|
4695
|
+
function clearActiveRunAfterComplete(config) {
|
|
4696
|
+
if (config.workspaceRoot === void 0) {
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4699
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(config);
|
|
4700
|
+
if (workspaceRoot === null) {
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
clearActiveRunState(workspaceRoot);
|
|
4704
|
+
clearAttentionPendingState(workspaceRoot);
|
|
4316
4705
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
key: project.key,
|
|
4324
|
-
presetCode: project.presetCode,
|
|
4325
|
-
matchedBy
|
|
4326
|
-
};
|
|
4706
|
+
function logCliError(deps, message) {
|
|
4707
|
+
if (deps.logError !== void 0) {
|
|
4708
|
+
deps.logError(message);
|
|
4709
|
+
return;
|
|
4710
|
+
}
|
|
4711
|
+
console.error(message);
|
|
4327
4712
|
}
|
|
4328
|
-
function
|
|
4329
|
-
const
|
|
4330
|
-
if (
|
|
4331
|
-
return
|
|
4713
|
+
function readFlagValue(args, flag) {
|
|
4714
|
+
const index = args.indexOf(flag);
|
|
4715
|
+
if (index === -1 || index + 1 >= args.length) {
|
|
4716
|
+
return null;
|
|
4332
4717
|
}
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4718
|
+
return args[index + 1] ?? null;
|
|
4719
|
+
}
|
|
4720
|
+
function removeConsumedFlags(args) {
|
|
4721
|
+
const flagsWithValues = /* @__PURE__ */ new Set([
|
|
4722
|
+
"--category",
|
|
4723
|
+
"--detail",
|
|
4724
|
+
"--event",
|
|
4725
|
+
"--agent-run-id",
|
|
4726
|
+
"--work-item-id",
|
|
4727
|
+
"--project-id",
|
|
4728
|
+
"--acknowledged-at"
|
|
4729
|
+
]);
|
|
4730
|
+
const result = [];
|
|
4731
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
4732
|
+
const arg = args[index];
|
|
4733
|
+
if (flagsWithValues.has(arg)) {
|
|
4734
|
+
index += 1;
|
|
4735
|
+
continue;
|
|
4347
4736
|
}
|
|
4737
|
+
result.push(arg);
|
|
4348
4738
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4739
|
+
return result;
|
|
4740
|
+
}
|
|
4741
|
+
async function reportAttention(deps, agentRunId, message, workspaceRoot) {
|
|
4742
|
+
const transport = createOrchestratorTransport(deps.client);
|
|
4743
|
+
await transport.setAttention(agentRunId, true, message);
|
|
4744
|
+
writeAttentionPendingState(workspaceRoot, agentRunId);
|
|
4745
|
+
}
|
|
4746
|
+
async function clearAttention(deps, workspaceRoot) {
|
|
4747
|
+
const pending = readAttentionPendingState(workspaceRoot);
|
|
4748
|
+
if (pending === null) {
|
|
4749
|
+
return;
|
|
4352
4750
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
}
|
|
4751
|
+
const active = readActiveRunState(workspaceRoot);
|
|
4752
|
+
if (!isActiveRunStateUsable(active) || active.agentRunId !== pending.agentRunId) {
|
|
4753
|
+
return;
|
|
4357
4754
|
}
|
|
4358
|
-
const
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4755
|
+
const transport = createOrchestratorTransport(deps.client);
|
|
4756
|
+
try {
|
|
4757
|
+
await transport.setAttention(active.agentRunId, false);
|
|
4758
|
+
} catch (error) {
|
|
4759
|
+
const message = error instanceof Error ? error.message : "Failed to clear agent attention";
|
|
4760
|
+
logCliError(deps, message);
|
|
4761
|
+
} finally {
|
|
4762
|
+
clearAttentionPendingState(workspaceRoot);
|
|
4365
4763
|
}
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4764
|
+
}
|
|
4765
|
+
async function handleAttentionReport(deps, args) {
|
|
4766
|
+
const category = readFlagValue(args, "--category");
|
|
4767
|
+
const detail = readFlagValue(args, "--detail");
|
|
4768
|
+
if (category === null || detail === null) {
|
|
4769
|
+
logCliError(deps, "attention report requires --category and --detail");
|
|
4770
|
+
return 0;
|
|
4771
|
+
}
|
|
4772
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
|
|
4773
|
+
if (workspaceRoot === null) {
|
|
4774
|
+
return 0;
|
|
4775
|
+
}
|
|
4776
|
+
const active = readActiveRunState(workspaceRoot);
|
|
4777
|
+
if (!isActiveRunStateUsable(active)) {
|
|
4778
|
+
return 0;
|
|
4779
|
+
}
|
|
4780
|
+
const message = `[${category}] ${detail.trim()}`;
|
|
4781
|
+
try {
|
|
4782
|
+
await reportAttention(deps, active.agentRunId, message, workspaceRoot);
|
|
4783
|
+
} catch (error) {
|
|
4784
|
+
const messageText = error instanceof Error ? error.message : "Failed to report agent attention";
|
|
4785
|
+
logCliError(deps, messageText);
|
|
4371
4786
|
}
|
|
4372
|
-
return
|
|
4787
|
+
return 0;
|
|
4373
4788
|
}
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4789
|
+
async function handleAttentionReportFromHook(deps, args) {
|
|
4790
|
+
const eventRaw = readFlagValue(args, "--event");
|
|
4791
|
+
if (eventRaw === null) {
|
|
4792
|
+
logCliError(deps, "attention report-from-hook requires --event");
|
|
4793
|
+
return 0;
|
|
4794
|
+
}
|
|
4795
|
+
const event = eventRaw;
|
|
4796
|
+
if (isAfterHookEvent(event)) {
|
|
4797
|
+
return 0;
|
|
4798
|
+
}
|
|
4799
|
+
const readStdin = deps.readStdin ?? defaultReadStdin;
|
|
4800
|
+
const stdin = await readStdin();
|
|
4801
|
+
const payload = parseHookPayloadJson(stdin);
|
|
4802
|
+
if (payload === null) {
|
|
4803
|
+
return 0;
|
|
4804
|
+
}
|
|
4805
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
|
|
4806
|
+
if (workspaceRoot === null) {
|
|
4807
|
+
return 0;
|
|
4808
|
+
}
|
|
4809
|
+
const active = readActiveRunState(workspaceRoot);
|
|
4810
|
+
if (!isActiveRunStateUsable(active)) {
|
|
4811
|
+
return 0;
|
|
4812
|
+
}
|
|
4813
|
+
let message = null;
|
|
4814
|
+
if (event === "beforeShellExecution") {
|
|
4815
|
+
const command = extractShellCommand(payload);
|
|
4816
|
+
if (command === null) {
|
|
4817
|
+
return 0;
|
|
4391
4818
|
}
|
|
4819
|
+
const category = classifyShellCommand(command);
|
|
4820
|
+
if (category === null) {
|
|
4821
|
+
return 0;
|
|
4822
|
+
}
|
|
4823
|
+
message = buildShellAttentionMessage(category, command);
|
|
4824
|
+
} else if (event === "beforeMCPExecution") {
|
|
4825
|
+
const mcp = extractMcpExecution(payload);
|
|
4826
|
+
if (mcp === null) {
|
|
4827
|
+
return 0;
|
|
4828
|
+
}
|
|
4829
|
+
message = buildMcpAttentionMessage(mcp.server, mcp.toolName);
|
|
4830
|
+
} else if (event === "preToolUse") {
|
|
4831
|
+
const preTool = extractPreToolUse(payload);
|
|
4832
|
+
if (preTool === null) {
|
|
4833
|
+
return 0;
|
|
4834
|
+
}
|
|
4835
|
+
message = buildSmartModeAttentionMessage(preTool.toolName, preTool.smartModeReason);
|
|
4392
4836
|
}
|
|
4393
|
-
|
|
4837
|
+
if (message === null) {
|
|
4838
|
+
return 0;
|
|
4839
|
+
}
|
|
4840
|
+
try {
|
|
4841
|
+
await reportAttention(deps, active.agentRunId, message, workspaceRoot);
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
const messageText = error instanceof Error ? error.message : "Failed to report agent attention from hook";
|
|
4844
|
+
logCliError(deps, messageText);
|
|
4845
|
+
}
|
|
4846
|
+
return 0;
|
|
4394
4847
|
}
|
|
4395
|
-
function
|
|
4396
|
-
const
|
|
4397
|
-
if (
|
|
4398
|
-
return
|
|
4848
|
+
async function handleAttentionClear(deps) {
|
|
4849
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
|
|
4850
|
+
if (workspaceRoot === null) {
|
|
4851
|
+
return 0;
|
|
4399
4852
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4853
|
+
await clearAttention(deps, workspaceRoot);
|
|
4854
|
+
return 0;
|
|
4855
|
+
}
|
|
4856
|
+
function handleOrchestratorWriteActiveRun(deps, args) {
|
|
4857
|
+
const agentRunId = readFlagValue(args, "--agent-run-id");
|
|
4858
|
+
const workItemId = readFlagValue(args, "--work-item-id");
|
|
4859
|
+
const projectId = readFlagValue(args, "--project-id");
|
|
4860
|
+
const acknowledgedAt = readFlagValue(args, "--acknowledged-at") ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4861
|
+
if (agentRunId === null || workItemId === null || projectId === null) {
|
|
4862
|
+
logCliError(deps, "orchestrator write-active-run requires --agent-run-id, --work-item-id, --project-id");
|
|
4863
|
+
return 0;
|
|
4864
|
+
}
|
|
4865
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
|
|
4866
|
+
if (workspaceRoot === null) {
|
|
4867
|
+
return 0;
|
|
4868
|
+
}
|
|
4869
|
+
writeActiveRunState(workspaceRoot, {
|
|
4870
|
+
agentRunId,
|
|
4871
|
+
workItemId,
|
|
4872
|
+
projectId,
|
|
4873
|
+
acknowledgedAt
|
|
4874
|
+
});
|
|
4875
|
+
return 0;
|
|
4876
|
+
}
|
|
4877
|
+
function handleOrchestratorClearActiveRun(deps) {
|
|
4878
|
+
const workspaceRoot = resolveBridgeWorkspaceRoot(deps.config);
|
|
4879
|
+
if (workspaceRoot === null) {
|
|
4880
|
+
return 0;
|
|
4881
|
+
}
|
|
4882
|
+
clearOrchestratorBridgeState(workspaceRoot);
|
|
4883
|
+
return 0;
|
|
4884
|
+
}
|
|
4885
|
+
async function runAttentionCli(argv, deps) {
|
|
4886
|
+
const args = removeConsumedFlags(argv);
|
|
4887
|
+
const topLevel = args[0];
|
|
4888
|
+
if (topLevel === "attention") {
|
|
4889
|
+
const subcommand = args[1];
|
|
4890
|
+
if (subcommand === "report") {
|
|
4891
|
+
return handleAttentionReport(deps, argv);
|
|
4405
4892
|
}
|
|
4406
|
-
if (
|
|
4407
|
-
|
|
4893
|
+
if (subcommand === "report-from-hook") {
|
|
4894
|
+
return handleAttentionReportFromHook(deps, argv);
|
|
4408
4895
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
allIds.add(id);
|
|
4896
|
+
if (subcommand === "clear") {
|
|
4897
|
+
return handleAttentionClear(deps);
|
|
4412
4898
|
}
|
|
4899
|
+
logCliError(deps, `Unknown attention subcommand: ${subcommand ?? "(missing)"}`);
|
|
4900
|
+
return 0;
|
|
4413
4901
|
}
|
|
4414
|
-
if (
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
const projectId = [...allIds][0];
|
|
4419
|
-
if (projectId === void 0) {
|
|
4420
|
-
return { status: "missing" };
|
|
4902
|
+
if (topLevel === "orchestrator") {
|
|
4903
|
+
const subcommand = args[1];
|
|
4904
|
+
if (subcommand === "write-active-run") {
|
|
4905
|
+
return handleOrchestratorWriteActiveRun(deps, argv);
|
|
4421
4906
|
}
|
|
4422
|
-
|
|
4907
|
+
if (subcommand === "clear-active-run") {
|
|
4908
|
+
return handleOrchestratorClearActiveRun(deps);
|
|
4909
|
+
}
|
|
4910
|
+
logCliError(deps, `Unknown orchestrator subcommand: ${subcommand ?? "(missing)"}`);
|
|
4911
|
+
return 0;
|
|
4423
4912
|
}
|
|
4424
|
-
return
|
|
4913
|
+
return 1;
|
|
4914
|
+
}
|
|
4915
|
+
function isAttentionCliInvocation(argv) {
|
|
4916
|
+
const topLevel = argv[0];
|
|
4917
|
+
return topLevel === "attention" || topLevel === "orchestrator";
|
|
4425
4918
|
}
|
|
4426
4919
|
|
|
4427
|
-
// src/
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
function parseTaskBoardsYaml(workspaceRoot) {
|
|
4434
|
-
const filePath = join2(workspaceRoot, TASK_BOARDS_FILE);
|
|
4435
|
-
if (!existsSync2(filePath)) {
|
|
4436
|
-
return null;
|
|
4920
|
+
// src/config.ts
|
|
4921
|
+
var import_api = __toESM(require_build(), 1);
|
|
4922
|
+
function resolveSanitizeResponses() {
|
|
4923
|
+
const raw = process.env.TASK_BOARDS_MCP_SANITIZE;
|
|
4924
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
4925
|
+
return true;
|
|
4437
4926
|
}
|
|
4438
|
-
const
|
|
4439
|
-
|
|
4440
|
-
|
|
4927
|
+
const normalized = raw.trim().toLowerCase();
|
|
4928
|
+
return normalized !== "false" && normalized !== "0" && normalized !== "no";
|
|
4929
|
+
}
|
|
4930
|
+
function resolveBlockSensitiveAttachments() {
|
|
4931
|
+
const raw = process.env.TASK_BOARDS_MCP_BLOCK_SENSITIVE_ATTACHMENTS;
|
|
4932
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
4933
|
+
return true;
|
|
4441
4934
|
}
|
|
4442
|
-
const
|
|
4443
|
-
|
|
4444
|
-
|
|
4935
|
+
const normalized = raw.trim().toLowerCase();
|
|
4936
|
+
return normalized !== "false" && normalized !== "0" && normalized !== "no";
|
|
4937
|
+
}
|
|
4938
|
+
function loadConfig() {
|
|
4939
|
+
const apiUrl = process.env.TASK_BOARDS_API_URL ?? "http://localhost:3000";
|
|
4940
|
+
const apiToken = process.env.TASK_BOARDS_API_TOKEN;
|
|
4941
|
+
const workspaceRoot = process.env.WORKSPACE_ROOT;
|
|
4942
|
+
if (process.env.NODE_ENV === "production" && (apiToken === void 0 || apiToken.trim() === "")) {
|
|
4943
|
+
console.error("WARNING: TASK_BOARDS_API_TOKEN is not set. MCP server cannot authenticate to the Task Boards API.");
|
|
4445
4944
|
}
|
|
4446
|
-
return
|
|
4945
|
+
return {
|
|
4946
|
+
apiUrl: `${apiUrl.replace(/\/$/, "")}${import_api.API_V1_PREFIX}`,
|
|
4947
|
+
apiToken,
|
|
4948
|
+
workspaceRoot: workspaceRoot !== void 0 && workspaceRoot.trim() !== "" ? workspaceRoot : void 0,
|
|
4949
|
+
sanitizeResponses: resolveSanitizeResponses(),
|
|
4950
|
+
blockSensitiveAttachments: resolveBlockSensitiveAttachments()
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4954
|
+
// src/index.ts
|
|
4955
|
+
import { createRequire } from "node:module";
|
|
4956
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4957
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4958
|
+
|
|
4959
|
+
// src/tools/agent-runs.ts
|
|
4960
|
+
var import_agent_runs3 = __toESM(require_agent_runs_api(), 1);
|
|
4961
|
+
var import_agent_run_outcome = __toESM(require_agent_run_outcome_enum(), 1);
|
|
4962
|
+
var import_agent_run_status4 = __toESM(require_agent_run_status_enum(), 1);
|
|
4963
|
+
import { z as z3 } from "zod";
|
|
4964
|
+
|
|
4965
|
+
// src/tools/orchestrator-hints.ts
|
|
4966
|
+
var import_agent_run_status3 = __toESM(require_agent_run_status_enum(), 1);
|
|
4967
|
+
var import_subagent_role = __toESM(require_subagent_role_enum(), 1);
|
|
4968
|
+
var SLEEPER_POLICY = {
|
|
4969
|
+
singleBlockingPoll: true,
|
|
4970
|
+
noParallelPoll: true,
|
|
4971
|
+
noDuplicateWake: true,
|
|
4972
|
+
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."
|
|
4973
|
+
};
|
|
4974
|
+
var IDLE_INFLIGHT_CHECK_STEP = "Checked inflight runs (DISPATCHED/ACKNOWLEDGED); none found \u2014 true idle.";
|
|
4975
|
+
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.";
|
|
4976
|
+
var PROCESSING_NO_PARALLEL_POLL_STEP = "Do not start another poll until this batch finishes.";
|
|
4977
|
+
var INFLIGHT_DISPATCHED_RECOVERY_STEP = "Orphan DISPATCHED \u2014 resume Task+ack; avoid duplicate Task if subagent already running in this session";
|
|
4978
|
+
var IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION = "Do not clear_agent_attention on timeout; badge remains until human resolves in IDE";
|
|
4979
|
+
var IDLE_TIMEOUT_NEXT_STEPS = [
|
|
4980
|
+
"Call sync_git_releases(projectId) to sync work-item commits",
|
|
4981
|
+
IDLE_TIMEOUT_DO_NOT_CLEAR_ATTENTION,
|
|
4982
|
+
"Call wait_for_agent_runs again (or run_orchestrator_once) to continue monitoring"
|
|
4983
|
+
];
|
|
4984
|
+
var IDE_ATTENTION_NOT_IN_AWAITING = "IDE attention is not in-awaiting; do not move column; replace attention message on re-report";
|
|
4985
|
+
var PROCESSING_NEXT_STEPS = [
|
|
4986
|
+
"Call sync_project_subagents(projectId) before Task dispatch when the project uses custom subagent slugs",
|
|
4987
|
+
"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",
|
|
4988
|
+
"When a subagent is blocked, call report_agent_attention(agentRunId, message); clear with clear_agent_attention after resolution",
|
|
4989
|
+
"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)",
|
|
4990
|
+
"On feature branch (not master/main): git push origin HEAD then sync_git_releases(projectId)",
|
|
4991
|
+
"Parallel Task runs OK for same workItemId when agentRole differs (e.g. FRONTEND_DEVELOPER + DEVELOPER)",
|
|
4992
|
+
"Claim priority: in-development > in-qa > in-analysis; in-analysis blocked while SERVICE-assigned stories exist in dev/qa",
|
|
4993
|
+
"Call run_orchestrator_once or wait_for_agent_runs to continue monitoring"
|
|
4994
|
+
];
|
|
4995
|
+
var ATTENTION_PROCESSING_NEXT_STEPS = [
|
|
4996
|
+
"Resolve requiresAttention runs before dispatching new Task subagents",
|
|
4997
|
+
IDE_ATTENTION_NOT_IN_AWAITING,
|
|
4998
|
+
"Call clear_agent_attention(agentRunId) after each blocker is resolved"
|
|
4999
|
+
];
|
|
5000
|
+
var RUN_LIFECYCLE_DOC = {
|
|
5001
|
+
phases: ["dispatched", "acknowledged", "awaiting_ide_approval", "ready_to_complete"],
|
|
5002
|
+
terminalPhase: "complete",
|
|
5003
|
+
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."
|
|
5004
|
+
};
|
|
5005
|
+
function buildTaskStep(run) {
|
|
5006
|
+
const subagent = run.payload.suggestedSubagentType ?? "subagent";
|
|
5007
|
+
return `Task(subagent_type=${subagent}, prompt=payload.contextSummary)`;
|
|
5008
|
+
}
|
|
5009
|
+
function buildCompleteStep(run) {
|
|
5010
|
+
if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ARCHITECT) {
|
|
5011
|
+
return "complete_agent_run after work; architect may set workItemPatch.designerRequired (boolean, default false)";
|
|
5012
|
+
}
|
|
5013
|
+
if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ANALYST) {
|
|
5014
|
+
return "complete_agent_run after work; use outcome SKIP_DEV when codeChangesRequired=false (released without git commit)";
|
|
5015
|
+
}
|
|
5016
|
+
return "complete_agent_run after work";
|
|
5017
|
+
}
|
|
5018
|
+
function buildGitCommitStep(run) {
|
|
5019
|
+
if (run.agentRole === import_subagent_role.SUBAGENT_ROLE.ANALYST) {
|
|
5020
|
+
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)";
|
|
5021
|
+
}
|
|
5022
|
+
return "git commit with work-item tag (local; push only on feature branch)";
|
|
5023
|
+
}
|
|
5024
|
+
function buildReportAttentionStep(run) {
|
|
5025
|
+
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`;
|
|
5026
|
+
}
|
|
5027
|
+
function buildProcessRunAttentionHint(run) {
|
|
5028
|
+
const guidance = buildAttentionGuidance(run);
|
|
5029
|
+
return {
|
|
5030
|
+
phase: guidance.phase,
|
|
5031
|
+
requiresAttention: run.requiresAttention,
|
|
5032
|
+
message: run.attentionMessage,
|
|
5033
|
+
detectedCategory: guidance.detectedCategory,
|
|
5034
|
+
reportGuidance: `report_agent_attention(${run.id}, message) using formatAttentionMessage(category, detail); replaces prior message on same ACK run`,
|
|
5035
|
+
clearGuidance: guidance.clearStep,
|
|
5036
|
+
templateCategories: guidance.templateExamples
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
function buildDispatchedProcessRunSteps(run) {
|
|
5040
|
+
return [
|
|
5041
|
+
"sync_project_subagents(projectId) when payload.suggestedSubagentType is a project custom slug",
|
|
5042
|
+
`list_work_item_attachments(${run.workItemId})`,
|
|
5043
|
+
"download_work_item_attachments if needed",
|
|
5044
|
+
buildTaskStep(run),
|
|
5045
|
+
`ack_agent_run(${run.id})`,
|
|
5046
|
+
buildReportAttentionStep(run),
|
|
5047
|
+
`clear_agent_attention(${run.id}) after human resolves in IDE`,
|
|
5048
|
+
buildCompleteStep(run),
|
|
5049
|
+
buildGitCommitStep(run)
|
|
5050
|
+
];
|
|
4447
5051
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
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;
|
|
5052
|
+
function buildAwaitingIdeApprovalProcessRunSteps(run) {
|
|
5053
|
+
return [
|
|
5054
|
+
`clear_agent_attention(${run.id}) after resolving: ${run.attentionMessage ?? "attention flag"}`,
|
|
5055
|
+
buildCompleteStep(run),
|
|
5056
|
+
buildGitCommitStep(run)
|
|
5057
|
+
];
|
|
4464
5058
|
}
|
|
4465
|
-
function
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
}
|
|
5059
|
+
function buildAcknowledgedProcessRunSteps(run) {
|
|
5060
|
+
return [
|
|
5061
|
+
"Resume subagent work (Task may already be running in this session)",
|
|
5062
|
+
buildReportAttentionStep(run),
|
|
5063
|
+
`clear_agent_attention(${run.id}) after human resolves in IDE`,
|
|
5064
|
+
buildCompleteStep(run),
|
|
5065
|
+
buildGitCommitStep(run)
|
|
5066
|
+
];
|
|
4474
5067
|
}
|
|
4475
|
-
function
|
|
4476
|
-
|
|
4477
|
-
|
|
5068
|
+
function buildProcessRunHint(run, source) {
|
|
5069
|
+
const phase = deriveOrchestratorRunPhase(run);
|
|
5070
|
+
if (phase === "awaiting_ide_approval") {
|
|
5071
|
+
return {
|
|
5072
|
+
agentRunId: run.id,
|
|
5073
|
+
workItemId: run.workItemId,
|
|
5074
|
+
phase,
|
|
5075
|
+
source,
|
|
5076
|
+
steps: buildAwaitingIdeApprovalProcessRunSteps(run),
|
|
5077
|
+
attention: buildProcessRunAttentionHint(run)
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
if (phase === "acknowledged") {
|
|
5081
|
+
return {
|
|
5082
|
+
agentRunId: run.id,
|
|
5083
|
+
workItemId: run.workItemId,
|
|
5084
|
+
phase,
|
|
5085
|
+
source,
|
|
5086
|
+
steps: buildAcknowledgedProcessRunSteps(run)
|
|
5087
|
+
};
|
|
5088
|
+
}
|
|
5089
|
+
const steps = buildDispatchedProcessRunSteps(run);
|
|
5090
|
+
if (source === "inflight") {
|
|
5091
|
+
return {
|
|
5092
|
+
agentRunId: run.id,
|
|
5093
|
+
workItemId: run.workItemId,
|
|
5094
|
+
phase,
|
|
5095
|
+
source,
|
|
5096
|
+
steps: [INFLIGHT_DISPATCHED_RECOVERY_STEP, ...steps]
|
|
5097
|
+
};
|
|
4478
5098
|
}
|
|
5099
|
+
return {
|
|
5100
|
+
agentRunId: run.id,
|
|
5101
|
+
workItemId: run.workItemId,
|
|
5102
|
+
phase,
|
|
5103
|
+
source,
|
|
5104
|
+
steps
|
|
5105
|
+
};
|
|
4479
5106
|
}
|
|
4480
|
-
function
|
|
4481
|
-
|
|
4482
|
-
for (
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
return current;
|
|
4486
|
-
}
|
|
4487
|
-
const parent = dirname(current);
|
|
4488
|
-
if (parent === current) {
|
|
4489
|
-
break;
|
|
5107
|
+
function buildProcessRuns(items, inflightRuns) {
|
|
5108
|
+
const hints = [];
|
|
5109
|
+
for (const run of items) {
|
|
5110
|
+
if (run.requiresAttention) {
|
|
5111
|
+
hints.push(buildProcessRunHint(run, "claimed"));
|
|
4490
5112
|
}
|
|
4491
|
-
current = parent;
|
|
4492
5113
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
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);
|
|
5114
|
+
for (const run of inflightRuns) {
|
|
5115
|
+
if (run.requiresAttention) {
|
|
5116
|
+
hints.push(buildProcessRunHint(run, "inflight"));
|
|
4503
5117
|
}
|
|
4504
|
-
return resolved;
|
|
4505
5118
|
}
|
|
4506
|
-
|
|
4507
|
-
|
|
5119
|
+
for (const run of inflightRuns) {
|
|
5120
|
+
if (!run.requiresAttention) {
|
|
5121
|
+
hints.push(buildProcessRunHint(run, "inflight"));
|
|
5122
|
+
}
|
|
4508
5123
|
}
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
|
|
5124
|
+
for (const run of items) {
|
|
5125
|
+
if (!run.requiresAttention) {
|
|
5126
|
+
hints.push(buildProcessRunHint(run, "claimed"));
|
|
5127
|
+
}
|
|
4512
5128
|
}
|
|
4513
|
-
return
|
|
5129
|
+
return hints;
|
|
4514
5130
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
function emptyResponse(workspaceRoot) {
|
|
5131
|
+
function buildAttentionSummary(runs) {
|
|
5132
|
+
const blockedRuns = runs.filter((run) => run.requiresAttention);
|
|
4518
5133
|
return {
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
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."
|
|
5134
|
+
blockedRunCount: blockedRuns.length,
|
|
5135
|
+
blockedAgentRunIds: blockedRuns.map((run) => run.id),
|
|
5136
|
+
resolveBeforeNewDispatch: blockedRuns.length > 0
|
|
4526
5137
|
};
|
|
4527
5138
|
}
|
|
4528
|
-
function
|
|
5139
|
+
function buildInflightSummary(inflightRuns) {
|
|
5140
|
+
if (inflightRuns.length === 0) {
|
|
5141
|
+
return void 0;
|
|
5142
|
+
}
|
|
4529
5143
|
return {
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
key: project.key,
|
|
4535
|
-
presetCode: project.presetCode
|
|
5144
|
+
dispatchedCount: inflightRuns.filter((run) => run.status === import_agent_run_status3.AGENT_RUN_STATUS.DISPATCHED).length,
|
|
5145
|
+
acknowledgedCount: inflightRuns.filter((run) => run.status === import_agent_run_status3.AGENT_RUN_STATUS.ACKNOWLEDGED).length,
|
|
5146
|
+
requiresAttentionCount: inflightRuns.filter((run) => run.requiresAttention).length,
|
|
5147
|
+
agentRunIds: inflightRuns.map((run) => run.id)
|
|
4536
5148
|
};
|
|
4537
5149
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
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);
|
|
5150
|
+
function derivePollResultSource(items, inflightRuns) {
|
|
5151
|
+
if (items.length > 0) {
|
|
5152
|
+
return "claimed";
|
|
4547
5153
|
}
|
|
4548
|
-
|
|
4549
|
-
|
|
5154
|
+
if (inflightRuns.length > 0) {
|
|
5155
|
+
return "inflight";
|
|
5156
|
+
}
|
|
5157
|
+
return "idle";
|
|
5158
|
+
}
|
|
5159
|
+
function buildOrchestratorHints(items, inflightRuns, timedOut) {
|
|
5160
|
+
const pollResultSource = derivePollResultSource(items, inflightRuns);
|
|
5161
|
+
const allRuns = [...items, ...inflightRuns];
|
|
5162
|
+
if (timedOut) {
|
|
4550
5163
|
return {
|
|
4551
|
-
|
|
4552
|
-
|
|
5164
|
+
phase: "idle",
|
|
5165
|
+
pollResultSource,
|
|
5166
|
+
sleeperPolicy: SLEEPER_POLICY,
|
|
5167
|
+
nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
|
|
4553
5168
|
};
|
|
4554
5169
|
}
|
|
4555
|
-
if (
|
|
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") {
|
|
5170
|
+
if (allRuns.length === 0) {
|
|
4565
5171
|
return {
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
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.`
|
|
5172
|
+
phase: "idle",
|
|
5173
|
+
pollResultSource,
|
|
5174
|
+
sleeperPolicy: SLEEPER_POLICY,
|
|
5175
|
+
nextSteps: [IDLE_INFLIGHT_CHECK_STEP, ...IDLE_TIMEOUT_NEXT_STEPS, IDLE_NO_DUPLICATE_WAKE_STEP]
|
|
4574
5176
|
};
|
|
4575
5177
|
}
|
|
4576
|
-
|
|
5178
|
+
const hasAttentionRuns = allRuns.some((run) => run.requiresAttention);
|
|
5179
|
+
return {
|
|
5180
|
+
phase: "processing",
|
|
5181
|
+
pollResultSource,
|
|
5182
|
+
sleeperPolicy: SLEEPER_POLICY,
|
|
5183
|
+
inflightSummary: buildInflightSummary(inflightRuns),
|
|
5184
|
+
processRuns: buildProcessRuns(items, inflightRuns),
|
|
5185
|
+
attentionSummary: buildAttentionSummary(allRuns),
|
|
5186
|
+
runLifecycle: RUN_LIFECYCLE_DOC,
|
|
5187
|
+
nextSteps: hasAttentionRuns ? [PROCESSING_NO_PARALLEL_POLL_STEP, ...ATTENTION_PROCESSING_NEXT_STEPS, ...PROCESSING_NEXT_STEPS] : [PROCESSING_NO_PARALLEL_POLL_STEP, ...PROCESSING_NEXT_STEPS]
|
|
5188
|
+
};
|
|
4577
5189
|
}
|
|
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);
|
|
5190
|
+
function enrichPollResult(items, inflightRuns, timedOut) {
|
|
4602
5191
|
return {
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
timedOut: enriched.timedOut,
|
|
4608
|
-
orchestrator: enriched.orchestrator
|
|
5192
|
+
items,
|
|
5193
|
+
inflightRuns,
|
|
5194
|
+
timedOut,
|
|
5195
|
+
orchestrator: buildOrchestratorHints(items, inflightRuns, timedOut)
|
|
4609
5196
|
};
|
|
4610
5197
|
}
|
|
4611
|
-
|
|
5198
|
+
|
|
5199
|
+
// src/tools/agent-runs.ts
|
|
5200
|
+
var agentRunStatusValues = Object.values(import_agent_run_status4.AGENT_RUN_STATUS);
|
|
5201
|
+
var agentRunOutcomeValues = Object.values(import_agent_run_outcome.AGENT_RUN_OUTCOME);
|
|
5202
|
+
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.";
|
|
5203
|
+
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).";
|
|
5204
|
+
function registerAgentRunTools(server, client, config) {
|
|
5205
|
+
server.registerTool(
|
|
5206
|
+
"list_agent_runs",
|
|
5207
|
+
{
|
|
5208
|
+
description: "List agent runs for a project (default status PENDING). Prefer wait_for_agent_runs for orchestrator long-polling.",
|
|
5209
|
+
inputSchema: {
|
|
5210
|
+
projectId: z3.string().uuid().describe("Project UUID (required)"),
|
|
5211
|
+
status: z3.enum(agentRunStatusValues).optional().describe("Filter by agent run status")
|
|
5212
|
+
}
|
|
5213
|
+
},
|
|
5214
|
+
async ({ status, projectId }) => runTool(async () => {
|
|
5215
|
+
const effectiveStatus = status ?? import_agent_run_status4.AGENT_RUN_STATUS.PENDING;
|
|
5216
|
+
return client.get(import_agent_runs3.AGENT_RUNS_ROUTES.listAgentRuns(), {
|
|
5217
|
+
status: effectiveStatus,
|
|
5218
|
+
projectId
|
|
5219
|
+
});
|
|
5220
|
+
})
|
|
5221
|
+
);
|
|
4612
5222
|
server.registerTool(
|
|
4613
|
-
"
|
|
5223
|
+
"wait_for_agent_runs",
|
|
4614
5224
|
{
|
|
4615
|
-
description: "
|
|
5225
|
+
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
5226
|
inputSchema: {
|
|
4617
|
-
projectId:
|
|
4618
|
-
timeout:
|
|
4619
|
-
pollInterval:
|
|
4620
|
-
limit:
|
|
4621
|
-
workspaceRoot: z2.string().optional().describe("Optional absolute workspace root; defaults to WORKSPACE_ROOT or upward search from cwd")
|
|
5227
|
+
projectId: z3.string().uuid().describe("Project UUID"),
|
|
5228
|
+
timeout: z3.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
|
|
5229
|
+
pollInterval: z3.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
|
|
5230
|
+
limit: z3.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)")
|
|
4622
5231
|
}
|
|
4623
5232
|
},
|
|
4624
|
-
async ({ projectId, timeout, pollInterval, limit
|
|
4625
|
-
|
|
5233
|
+
async ({ projectId, timeout, pollInterval, limit }) => runTool(async () => {
|
|
5234
|
+
const pollResult = await pollAgentRuns(client, {
|
|
4626
5235
|
projectId,
|
|
4627
5236
|
timeoutMs: timeout * 1e3,
|
|
4628
5237
|
pollIntervalMs: pollInterval * 1e3,
|
|
4629
|
-
limit
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
5238
|
+
limit
|
|
5239
|
+
});
|
|
5240
|
+
return enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
|
|
5241
|
+
})
|
|
5242
|
+
);
|
|
5243
|
+
server.registerTool(
|
|
5244
|
+
"ack_agent_run",
|
|
5245
|
+
{
|
|
5246
|
+
description: "Acknowledge agent run delivery after Task subagent has been dispatched. Requires status DISPATCHED (DISPATCHED \u2192 ACKNOWLEDGED).",
|
|
5247
|
+
inputSchema: {
|
|
5248
|
+
agentRunId: z3.string().uuid().describe("Agent run UUID"),
|
|
5249
|
+
note: z3.string().nullable().optional().describe("Optional acknowledgment note")
|
|
5250
|
+
}
|
|
5251
|
+
},
|
|
5252
|
+
async ({ agentRunId, note }) => runTool(async () => {
|
|
5253
|
+
const body = note !== void 0 ? { note } : void 0;
|
|
5254
|
+
const response = await client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.acknowledgeAgentRun(agentRunId), body);
|
|
5255
|
+
writeActiveRunFromAck(config, response);
|
|
5256
|
+
return response;
|
|
5257
|
+
})
|
|
5258
|
+
);
|
|
5259
|
+
server.registerTool(
|
|
5260
|
+
"report_agent_attention",
|
|
5261
|
+
{
|
|
5262
|
+
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.",
|
|
5263
|
+
inputSchema: {
|
|
5264
|
+
agentRunId: z3.string().uuid().describe("Agent run UUID"),
|
|
5265
|
+
message: z3.string().min(1).describe("Why human attention is needed")
|
|
5266
|
+
}
|
|
5267
|
+
},
|
|
5268
|
+
async ({ agentRunId, message }) => runTool(async () => {
|
|
5269
|
+
const body = {
|
|
5270
|
+
requiresAttention: true,
|
|
5271
|
+
attentionMessage: message
|
|
5272
|
+
};
|
|
5273
|
+
return client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
|
|
5274
|
+
})
|
|
5275
|
+
);
|
|
5276
|
+
server.registerTool(
|
|
5277
|
+
"clear_agent_attention",
|
|
5278
|
+
{
|
|
5279
|
+
description: "Clear the human-attention flag after the blocker is resolved. Requires the run to still be ACKNOWLEDGED.",
|
|
5280
|
+
inputSchema: {
|
|
5281
|
+
agentRunId: z3.string().uuid().describe("Agent run UUID")
|
|
5282
|
+
}
|
|
5283
|
+
},
|
|
5284
|
+
async ({ agentRunId }) => runTool(async () => {
|
|
5285
|
+
const body = {
|
|
5286
|
+
requiresAttention: false
|
|
5287
|
+
};
|
|
5288
|
+
return client.patch(import_agent_runs3.AGENT_RUNS_ROUTES.setAgentRunAttention(agentRunId), body);
|
|
5289
|
+
})
|
|
5290
|
+
);
|
|
5291
|
+
server.registerTool(
|
|
5292
|
+
"complete_agent_run",
|
|
5293
|
+
{
|
|
5294
|
+
description: `Complete agent run: apply AGENTIC_SDLC workflow transition and optional work item patch. ${AGENTIC_SDLC_COLUMNS_SUMMARY} Outcomes: ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`,
|
|
5295
|
+
inputSchema: {
|
|
5296
|
+
agentRunId: z3.string().uuid().describe("Agent run UUID"),
|
|
5297
|
+
outcome: z3.enum(agentRunOutcomeValues).optional().describe(
|
|
5298
|
+
`Completion outcome (DEFAULT, SKIP_DESIGN, SKIP_DEV, HAS_BUGS, NEEDS_CLARIFICATION, FAILED). ${COMPLETE_AGENT_RUN_OUTCOME_SUMMARY}`
|
|
5299
|
+
),
|
|
5300
|
+
note: z3.string().nullable().optional().describe("Optional completion note"),
|
|
5301
|
+
workItemPatch: z3.object({
|
|
5302
|
+
description: z3.string().nullable().optional().describe("Updated work item description"),
|
|
5303
|
+
acceptanceCriteria: z3.string().nullable().optional().describe("Updated acceptance criteria"),
|
|
5304
|
+
designerRequired: z3.boolean().optional().describe("Architect only: whether DESIGNER phase runs before development")
|
|
5305
|
+
}).nullable().optional().describe("Optional work item field updates")
|
|
5306
|
+
}
|
|
5307
|
+
},
|
|
5308
|
+
async ({ agentRunId, outcome, note, workItemPatch }) => runTool(async () => {
|
|
5309
|
+
const body = {};
|
|
5310
|
+
if (outcome !== void 0) {
|
|
5311
|
+
body.outcome = outcome;
|
|
5312
|
+
}
|
|
5313
|
+
if (note !== void 0) {
|
|
5314
|
+
body.note = note;
|
|
5315
|
+
}
|
|
5316
|
+
if (workItemPatch !== void 0 && workItemPatch !== null) {
|
|
5317
|
+
body.workItemPatch = workItemPatch;
|
|
5318
|
+
}
|
|
5319
|
+
const hasBody = outcome !== void 0 || note !== void 0 || workItemPatch !== void 0 && workItemPatch !== null;
|
|
5320
|
+
const response = await client.post(
|
|
5321
|
+
import_agent_runs3.AGENT_RUNS_ROUTES.completeAgentRun(agentRunId),
|
|
5322
|
+
hasBody ? body : void 0
|
|
5323
|
+
);
|
|
5324
|
+
clearActiveRunAfterComplete(config);
|
|
5325
|
+
return response;
|
|
5326
|
+
})
|
|
4634
5327
|
);
|
|
4635
5328
|
}
|
|
4636
5329
|
|
|
4637
|
-
// src/tools/
|
|
4638
|
-
|
|
4639
|
-
var import_shared3 = __toESM(require_build2(), 1);
|
|
4640
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
4641
|
-
import { z as z3 } from "zod";
|
|
5330
|
+
// src/tools/orchestrator.ts
|
|
5331
|
+
import { z as z4 } from "zod";
|
|
4642
5332
|
|
|
4643
|
-
// src/
|
|
4644
|
-
|
|
4645
|
-
import {
|
|
4646
|
-
function detectMimeType(filePath) {
|
|
4647
|
-
const mimeType = lookupMimeType(basename2(filePath));
|
|
4648
|
-
return typeof mimeType === "string" ? mimeType : "application/octet-stream";
|
|
4649
|
-
}
|
|
5333
|
+
// src/workspace/resolve-project.ts
|
|
5334
|
+
var import_projects = __toESM(require_projects_api(), 1);
|
|
5335
|
+
import { basename as basename3 } from "node:path";
|
|
4650
5336
|
|
|
4651
|
-
// src/
|
|
4652
|
-
|
|
4653
|
-
|
|
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);
|
|
5337
|
+
// src/workspace/normalize-token.ts
|
|
5338
|
+
function normalizeToken(value) {
|
|
5339
|
+
return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "");
|
|
4671
5340
|
}
|
|
4672
5341
|
|
|
4673
|
-
// src/workspace/
|
|
4674
|
-
|
|
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);
|
|
5342
|
+
// src/workspace/auto-match-project.ts
|
|
5343
|
+
function toCandidate(project, matchedBy) {
|
|
4699
5344
|
return {
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
5345
|
+
projectId: project.id,
|
|
5346
|
+
name: project.name,
|
|
5347
|
+
key: project.key,
|
|
5348
|
+
presetCode: project.presetCode,
|
|
5349
|
+
matchedBy
|
|
4704
5350
|
};
|
|
4705
5351
|
}
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
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;
|
|
5352
|
+
function autoMatchProject(folderToken, projects) {
|
|
5353
|
+
const normalizedFolder = normalizeToken(folderToken);
|
|
5354
|
+
if (normalizedFolder.length === 0) {
|
|
5355
|
+
return { kind: "none" };
|
|
4716
5356
|
}
|
|
4717
|
-
const
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
5357
|
+
const keyMatches = [];
|
|
5358
|
+
const nameMatches = [];
|
|
5359
|
+
for (const project of projects) {
|
|
5360
|
+
const input = {
|
|
5361
|
+
id: project.id,
|
|
5362
|
+
name: project.name,
|
|
5363
|
+
key: project.key,
|
|
5364
|
+
presetCode: project.presetCode
|
|
5365
|
+
};
|
|
5366
|
+
if (normalizeToken(project.key) === normalizedFolder) {
|
|
5367
|
+
keyMatches.push(input);
|
|
5368
|
+
}
|
|
5369
|
+
if (normalizeToken(project.name) === normalizedFolder) {
|
|
5370
|
+
nameMatches.push(input);
|
|
5371
|
+
}
|
|
4726
5372
|
}
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
)
|
|
5373
|
+
const uniqueById = /* @__PURE__ */ new Map();
|
|
5374
|
+
for (const project of keyMatches) {
|
|
5375
|
+
uniqueById.set(project.id, { project, matchedBy: "key" });
|
|
5376
|
+
}
|
|
5377
|
+
for (const project of nameMatches) {
|
|
5378
|
+
if (!uniqueById.has(project.id)) {
|
|
5379
|
+
uniqueById.set(project.id, { project, matchedBy: "name" });
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
const matches = [...uniqueById.values()];
|
|
5383
|
+
if (matches.length === 1) {
|
|
5384
|
+
const match = matches[0];
|
|
5385
|
+
if (match === void 0) {
|
|
5386
|
+
return { kind: "none" };
|
|
5387
|
+
}
|
|
5388
|
+
return { kind: "single", project: match.project, matchedBy: match.matchedBy };
|
|
5389
|
+
}
|
|
5390
|
+
if (matches.length > 1) {
|
|
5391
|
+
return {
|
|
5392
|
+
kind: "candidates",
|
|
5393
|
+
candidates: matches.map((match) => toCandidate(match.project, match.matchedBy))
|
|
5394
|
+
};
|
|
4733
5395
|
}
|
|
5396
|
+
return { kind: "none" };
|
|
4734
5397
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
const
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
5398
|
+
|
|
5399
|
+
// src/workspace/parse-ide-rules.ts
|
|
5400
|
+
import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
5401
|
+
import { join as join5 } from "node:path";
|
|
5402
|
+
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;
|
|
5403
|
+
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;
|
|
5404
|
+
function extractUuids(content) {
|
|
5405
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5406
|
+
for (const pattern of [PROJECT_ID_LINE_PATTERN, TASK_BOARDS_UUID_PATTERN]) {
|
|
5407
|
+
pattern.lastIndex = 0;
|
|
5408
|
+
let match = pattern.exec(content);
|
|
5409
|
+
while (match !== null) {
|
|
5410
|
+
const id = match[1];
|
|
5411
|
+
if (id !== void 0) {
|
|
5412
|
+
ids.add(id.toLowerCase());
|
|
4748
5413
|
}
|
|
5414
|
+
match = pattern.exec(content);
|
|
4749
5415
|
}
|
|
4750
5416
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
5417
|
+
return ids;
|
|
5418
|
+
}
|
|
5419
|
+
function parseIdeRules(workspaceRoot) {
|
|
5420
|
+
const rulesDir = join5(workspaceRoot, ".cursor", "rules");
|
|
5421
|
+
if (!existsSync6(rulesDir)) {
|
|
5422
|
+
return { status: "missing" };
|
|
5423
|
+
}
|
|
5424
|
+
const entries = readdirSync(rulesDir, { withFileTypes: true });
|
|
5425
|
+
const allIds = /* @__PURE__ */ new Set();
|
|
5426
|
+
for (const entry of entries) {
|
|
5427
|
+
if (!entry.isFile()) {
|
|
4754
5428
|
continue;
|
|
4755
5429
|
}
|
|
4756
|
-
|
|
4757
|
-
if (!overwrite && existsSync5(stagingPath)) {
|
|
4758
|
-
skipped.push({ attachmentId: attachment.id, reason: "file already exists (set overwrite=true to replace)" });
|
|
5430
|
+
if (!entry.name.endsWith(".mdc") && !entry.name.endsWith(".md")) {
|
|
4759
5431
|
continue;
|
|
4760
5432
|
}
|
|
4761
|
-
const
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
5433
|
+
const content = readFileSync4(join5(rulesDir, entry.name), "utf8");
|
|
5434
|
+
for (const id of extractUuids(content)) {
|
|
5435
|
+
allIds.add(id);
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
if (allIds.size === 0) {
|
|
5439
|
+
return { status: "missing" };
|
|
5440
|
+
}
|
|
5441
|
+
if (allIds.size === 1) {
|
|
5442
|
+
const projectId = [...allIds][0];
|
|
5443
|
+
if (projectId === void 0) {
|
|
5444
|
+
return { status: "missing" };
|
|
5445
|
+
}
|
|
5446
|
+
return { status: "found", projectId };
|
|
4769
5447
|
}
|
|
5448
|
+
return { status: "ambiguous" };
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
// src/workspace/resolve-project.ts
|
|
5452
|
+
function emptyResponse(workspaceRoot) {
|
|
4770
5453
|
return {
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
5454
|
+
resolutionSource: "ambiguous",
|
|
5455
|
+
workspaceRoot,
|
|
5456
|
+
projectId: null,
|
|
5457
|
+
name: null,
|
|
5458
|
+
key: null,
|
|
5459
|
+
presetCode: null,
|
|
5460
|
+
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
5461
|
};
|
|
4776
5462
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
5463
|
+
function fromProject(workspaceRoot, resolutionSource, project) {
|
|
5464
|
+
return {
|
|
5465
|
+
resolutionSource,
|
|
5466
|
+
workspaceRoot,
|
|
5467
|
+
projectId: project.id,
|
|
5468
|
+
name: project.name,
|
|
5469
|
+
key: project.key,
|
|
5470
|
+
presetCode: project.presetCode
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
async function enrichProject(client, workspaceRoot, resolutionSource, projectId) {
|
|
5474
|
+
const project = await client.get(import_projects.PROJECTS_ROUTES.getProject(projectId));
|
|
5475
|
+
return fromProject(workspaceRoot, resolutionSource, project);
|
|
5476
|
+
}
|
|
5477
|
+
async function resolveProject(client, options = {}) {
|
|
5478
|
+
const workspaceRoot = resolveWorkspaceRoot(options.workspaceRootOverride, options.envWorkspaceRoot);
|
|
5479
|
+
const yamlProjectId = parseTaskBoardsYaml(workspaceRoot);
|
|
5480
|
+
if (yamlProjectId !== null) {
|
|
5481
|
+
return enrichProject(client, workspaceRoot, "yaml", yamlProjectId);
|
|
4785
5482
|
}
|
|
4786
|
-
const
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
5483
|
+
const rulesResult = parseIdeRules(workspaceRoot);
|
|
5484
|
+
if (rulesResult.status === "ambiguous") {
|
|
5485
|
+
return {
|
|
5486
|
+
...emptyResponse(workspaceRoot),
|
|
5487
|
+
hint: "Multiple distinct projectId values found in .cursor/rules. Use .task-boards.yaml or set WORKSPACE_ROOT to a single-project workspace."
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
if (rulesResult.status === "found") {
|
|
5491
|
+
return enrichProject(client, workspaceRoot, "rule", rulesResult.projectId);
|
|
5492
|
+
}
|
|
5493
|
+
const folderToken = basename3(workspaceRoot);
|
|
5494
|
+
const listResponse = await client.get(import_projects.PROJECTS_ROUTES.listProjects());
|
|
5495
|
+
const autoMatch = autoMatchProject(folderToken, listResponse.items);
|
|
5496
|
+
if (autoMatch.kind === "single") {
|
|
5497
|
+
return enrichProject(client, workspaceRoot, "auto_match", autoMatch.project.id);
|
|
5498
|
+
}
|
|
5499
|
+
if (autoMatch.kind === "candidates") {
|
|
5500
|
+
return {
|
|
5501
|
+
resolutionSource: "ambiguous",
|
|
5502
|
+
workspaceRoot,
|
|
5503
|
+
projectId: null,
|
|
5504
|
+
name: null,
|
|
5505
|
+
key: null,
|
|
5506
|
+
presetCode: null,
|
|
5507
|
+
candidates: autoMatch.candidates,
|
|
5508
|
+
hint: `Multiple projects match folder name "${folderToken}". Pick one candidate or add .task-boards.yaml.`
|
|
5509
|
+
};
|
|
5510
|
+
}
|
|
5511
|
+
return emptyResponse(workspaceRoot);
|
|
5512
|
+
}
|
|
5513
|
+
|
|
5514
|
+
// src/tools/orchestrator.ts
|
|
5515
|
+
async function runOrchestratorOnce(client, options) {
|
|
5516
|
+
let projectId = options.projectId;
|
|
5517
|
+
let resolutionSource = "explicit";
|
|
5518
|
+
if (projectId === void 0) {
|
|
5519
|
+
const resolved = await resolveProject(client, {
|
|
5520
|
+
workspaceRootOverride: options.workspaceRootOverride,
|
|
5521
|
+
envWorkspaceRoot: options.envWorkspaceRoot
|
|
5522
|
+
});
|
|
5523
|
+
if (resolved.projectId === null) {
|
|
5524
|
+
const hint = resolved.hint ?? "Could not resolve project for workspace.";
|
|
5525
|
+
throw new WorkspaceError("PROJECT_NOT_FOUND", hint);
|
|
4794
5526
|
}
|
|
4795
|
-
|
|
5527
|
+
projectId = resolved.projectId;
|
|
5528
|
+
resolutionSource = resolved.resolutionSource;
|
|
5529
|
+
}
|
|
5530
|
+
const pollResult = await pollAgentRuns(client, {
|
|
5531
|
+
projectId,
|
|
5532
|
+
timeoutMs: options.timeoutMs,
|
|
5533
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
5534
|
+
limit: options.limit
|
|
5535
|
+
});
|
|
5536
|
+
const enriched = enrichPollResult(pollResult.items, pollResult.inflightRuns, pollResult.timedOut);
|
|
4796
5537
|
return {
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
5538
|
+
projectId,
|
|
5539
|
+
resolutionSource,
|
|
5540
|
+
items: enriched.items,
|
|
5541
|
+
inflightRuns: enriched.inflightRuns,
|
|
5542
|
+
timedOut: enriched.timedOut,
|
|
5543
|
+
orchestrator: enriched.orchestrator
|
|
4800
5544
|
};
|
|
4801
5545
|
}
|
|
4802
|
-
function
|
|
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
|
-
);
|
|
5546
|
+
function registerOrchestratorTools(server, client, config) {
|
|
4838
5547
|
server.registerTool(
|
|
4839
|
-
"
|
|
5548
|
+
"run_orchestrator_once",
|
|
4840
5549
|
{
|
|
4841
|
-
description: "
|
|
5550
|
+
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
5551
|
inputSchema: {
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
5552
|
+
projectId: z4.string().uuid().optional().describe("Project UUID; when omitted, resolves via .task-boards.yaml / .cursor/rules / folder name"),
|
|
5553
|
+
timeout: z4.number().int().min(1).max(300).default(120).describe("Maximum wait in seconds (1\u2013300, default 120)"),
|
|
5554
|
+
pollInterval: z4.number().int().min(1).max(60).default(2).describe("Seconds between polls (1\u201360, default 2); must not exceed timeout"),
|
|
5555
|
+
limit: z4.number().int().min(1).max(10).default(1).describe("Max runs to claim per poll (1\u201310, default 1)"),
|
|
5556
|
+
workspaceRoot: z4.string().optional().describe("Optional absolute workspace root; defaults to WORKSPACE_ROOT or upward search from cwd")
|
|
4846
5557
|
}
|
|
4847
5558
|
},
|
|
4848
|
-
async ({
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
);
|
|
4859
|
-
}
|
|
5559
|
+
async ({ projectId, timeout, pollInterval, limit, workspaceRoot }) => runTool(
|
|
5560
|
+
async () => runOrchestratorOnce(client, {
|
|
5561
|
+
projectId,
|
|
5562
|
+
timeoutMs: timeout * 1e3,
|
|
5563
|
+
pollIntervalMs: pollInterval * 1e3,
|
|
5564
|
+
limit,
|
|
5565
|
+
workspaceRootOverride: workspaceRoot,
|
|
5566
|
+
envWorkspaceRoot: config.workspaceRoot
|
|
5567
|
+
})
|
|
5568
|
+
)
|
|
4860
5569
|
);
|
|
4861
5570
|
}
|
|
4862
5571
|
|
|
4863
5572
|
// src/tools/board.ts
|
|
4864
5573
|
var import_boards = __toESM(require_boards_api(), 1);
|
|
4865
|
-
import { z as
|
|
5574
|
+
import { z as z5 } from "zod";
|
|
4866
5575
|
function registerBoardTools(server, client) {
|
|
4867
5576
|
server.registerTool(
|
|
4868
5577
|
"get_board",
|
|
4869
5578
|
{
|
|
4870
5579
|
description: "Get board projection for a project (columns and on-board work items).",
|
|
4871
5580
|
inputSchema: {
|
|
4872
|
-
projectId:
|
|
5581
|
+
projectId: z5.string().uuid().describe("Project UUID")
|
|
4873
5582
|
}
|
|
4874
5583
|
},
|
|
4875
5584
|
async ({ projectId }) => runTool(async () => client.get(import_boards.BOARDS_ROUTES.getBoard(projectId)))
|
|
4876
5585
|
);
|
|
4877
5586
|
}
|
|
4878
5587
|
|
|
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
5588
|
// src/tools/labels.ts
|
|
4914
5589
|
var import_label_color = __toESM(require_label_color_enum(), 1);
|
|
4915
5590
|
var import_labels = __toESM(require_labels_api(), 1);
|
|
@@ -5306,44 +5981,6 @@ function registerSyncGitReleasesTools(server, client, config) {
|
|
|
5306
5981
|
|
|
5307
5982
|
// src/tools/sync-project-subagents.ts
|
|
5308
5983
|
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
5984
|
function registerSyncProjectSubagentsTools(server, client, config) {
|
|
5348
5985
|
server.registerTool(
|
|
5349
5986
|
"sync_project_subagents",
|
|
@@ -5499,6 +6136,7 @@ function registerWorkItemTools(server, client) {
|
|
|
5499
6136
|
assigneeUserId: z11.string().uuid().nullable().optional().describe(
|
|
5500
6137
|
"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
6138
|
),
|
|
6139
|
+
codeChangesRequired: z11.boolean().optional().describe("STORY only: when false, ANALYST may complete with SKIP_DEV to release without code/git"),
|
|
5502
6140
|
labelIds: z11.array(z11.string().uuid()).max(10).optional().describe("Replace work item labels (up to 10)")
|
|
5503
6141
|
}
|
|
5504
6142
|
},
|
|
@@ -5513,6 +6151,7 @@ function registerWorkItemTools(server, client) {
|
|
|
5513
6151
|
storyPoints,
|
|
5514
6152
|
estimate,
|
|
5515
6153
|
assigneeUserId,
|
|
6154
|
+
codeChangesRequired,
|
|
5516
6155
|
labelIds
|
|
5517
6156
|
}) => runTool(async () => {
|
|
5518
6157
|
const current = await client.get(import_work_items.WORK_ITEMS_ROUTES.getWorkItem(workItemId));
|
|
@@ -5526,6 +6165,7 @@ function registerWorkItemTools(server, client) {
|
|
|
5526
6165
|
storyPoints,
|
|
5527
6166
|
estimate,
|
|
5528
6167
|
assigneeUserId,
|
|
6168
|
+
codeChangesRequired,
|
|
5529
6169
|
labelIds,
|
|
5530
6170
|
version: current.version
|
|
5531
6171
|
};
|
|
@@ -5584,7 +6224,7 @@ function registerTools(server, client, config) {
|
|
|
5584
6224
|
registerLabelTools(server, client);
|
|
5585
6225
|
registerAttachmentTools(server, client, config);
|
|
5586
6226
|
registerBoardTools(server, client);
|
|
5587
|
-
registerAgentRunTools(server, client);
|
|
6227
|
+
registerAgentRunTools(server, client, config);
|
|
5588
6228
|
registerOrchestratorTools(server, client, config);
|
|
5589
6229
|
registerSyncGitReleasesTools(server, client, config);
|
|
5590
6230
|
registerSyncProjectSubagentsTools(server, client, config);
|
|
@@ -5607,7 +6247,17 @@ async function main() {
|
|
|
5607
6247
|
}
|
|
5608
6248
|
|
|
5609
6249
|
// src/cli.ts
|
|
5610
|
-
|
|
6250
|
+
async function main2() {
|
|
6251
|
+
const argv = process.argv.slice(2);
|
|
6252
|
+
if (isAttentionCliInvocation(argv)) {
|
|
6253
|
+
const config = loadConfig();
|
|
6254
|
+
const client = new RestClient(config.apiUrl, config.apiToken);
|
|
6255
|
+
const exitCode = await runAttentionCli(argv, { config, client });
|
|
6256
|
+
process.exit(exitCode);
|
|
6257
|
+
}
|
|
6258
|
+
await main();
|
|
6259
|
+
}
|
|
6260
|
+
main2().catch((error) => {
|
|
5611
6261
|
const message = error instanceof Error ? error.message : "Failed to start MCP server";
|
|
5612
6262
|
console.error(message);
|
|
5613
6263
|
process.exit(1);
|