@martinloop/mcp 0.3.3 → 0.3.5
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/README.md +112 -107
- package/dist/discovery-metadata.d.ts +6 -6
- package/dist/discovery-metadata.js +6 -0
- package/dist/discovery-support.js +2 -2
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +210 -148
- package/dist/server-validation.js +2 -0
- package/dist/server.js +87 -1
- package/dist/tools/eval.js +29 -5
- package/dist/tools/execution-policy.d.ts +11 -0
- package/dist/tools/execution-policy.js +47 -0
- package/dist/tools/run-dossier.js +2 -2
- package/dist/tools/run-loop.d.ts +1 -0
- package/dist/tools/run-loop.js +15 -2
- package/dist/tools/tool-support.d.ts +3 -1
- package/dist/tools/tool-support.js +127 -19
- package/dist/tools/triage-runs.js +31 -6
- package/dist/vendor/adapters/claude-cli.js +100 -21
- package/dist/vendor/adapters/cli-bridge.d.ts +16 -0
- package/dist/vendor/adapters/cli-bridge.js +92 -2
- package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
- package/dist/vendor/adapters/codex-launcher.js +124 -10
- package/dist/vendor/adapters/openai-compatible.js +6 -6
- package/dist/vendor/contracts/adapter-capabilities.d.ts +17 -0
- package/dist/vendor/contracts/adapter-capabilities.js +35 -0
- package/dist/vendor/contracts/context-graph.d.ts +48 -0
- package/dist/vendor/contracts/context-graph.js +26 -0
- package/dist/vendor/contracts/execution-policy.d.ts +78 -0
- package/dist/vendor/contracts/execution-policy.js +43 -0
- package/dist/vendor/contracts/governance.d.ts +6 -3
- package/dist/vendor/contracts/identity.d.ts +29 -0
- package/dist/vendor/contracts/identity.js +30 -0
- package/dist/vendor/contracts/index.d.ts +40 -2
- package/dist/vendor/contracts/index.js +5 -0
- package/dist/vendor/contracts/observation.d.ts +21 -0
- package/dist/vendor/contracts/observation.js +27 -0
- package/dist/vendor/contracts/trajectory.d.ts +26 -0
- package/dist/vendor/contracts/trajectory.js +27 -0
- package/dist/vendor/contracts/verification.d.ts +28 -0
- package/dist/vendor/contracts/verification.js +7 -0
- package/dist/vendor/core/context-graph.d.ts +3 -0
- package/dist/vendor/core/context-graph.js +371 -0
- package/dist/vendor/core/identity.d.ts +20 -0
- package/dist/vendor/core/identity.js +86 -0
- package/dist/vendor/core/index.d.ts +10 -1
- package/dist/vendor/core/index.js +131 -10
- package/dist/vendor/core/persistence/integrity.js +2 -2
- package/dist/vendor/core/policy-compiler.d.ts +2 -0
- package/dist/vendor/core/policy-compiler.js +195 -0
- package/dist/vendor/core/policy.d.ts +15 -0
- package/dist/vendor/core/policy.js +84 -4
- package/dist/vendor/core/rollback.js +32 -12
- package/dist/vendor/core/routing.d.ts +40 -0
- package/dist/vendor/core/routing.js +185 -0
- package/dist/vendor/core/trajectory.d.ts +18 -0
- package/dist/vendor/core/trajectory.js +295 -0
- package/dist/workflow-state.d.ts +16 -0
- package/dist/workflow-state.js +1 -1
- package/package.json +76 -75
- package/server.json +21 -21
|
@@ -78,7 +78,8 @@ export function classifyFailure(input) {
|
|
|
78
78
|
failureClass: "environment_mismatch",
|
|
79
79
|
rationale: "The adapter could not access the required local runtime or CLI tooling.",
|
|
80
80
|
retryable: false,
|
|
81
|
-
recommendedIntervention: "switch_adapter"
|
|
81
|
+
recommendedIntervention: "switch_adapter",
|
|
82
|
+
diagnosticHint: diagnoseEnvironmentIssue(message)
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
if (containsPositive(message, ["syntax error", "parse error", "typescript error"])) {
|
|
@@ -86,7 +87,8 @@ export function classifyFailure(input) {
|
|
|
86
87
|
failureClass: "syntax_error",
|
|
87
88
|
rationale: "The attempt failed on a parser or compiler-style issue.",
|
|
88
89
|
retryable: true,
|
|
89
|
-
recommendedIntervention: "compress_context"
|
|
90
|
+
recommendedIntervention: "compress_context",
|
|
91
|
+
diagnosticHint: "Check for malformed edits — the verifier found a syntax or parse error in the changed files."
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
if (containsPositive(message, [
|
|
@@ -104,7 +106,8 @@ export function classifyFailure(input) {
|
|
|
104
106
|
failureClass: "repo_grounding_failure",
|
|
105
107
|
rationale: "The attempt referenced modules, symbols, or files that are not grounded in the repo or approved docs.",
|
|
106
108
|
retryable: true,
|
|
107
|
-
recommendedIntervention: repeatedFailure === "repo_grounding_failure" ? "tighten_task" : "run_verifier"
|
|
109
|
+
recommendedIntervention: repeatedFailure === "repo_grounding_failure" ? "tighten_task" : "run_verifier",
|
|
110
|
+
diagnosticHint: diagnoseGroundingIssue(message)
|
|
108
111
|
};
|
|
109
112
|
}
|
|
110
113
|
if (containsPositive(message, [
|
|
@@ -118,7 +121,8 @@ export function classifyFailure(input) {
|
|
|
118
121
|
failureClass: "verification_failure",
|
|
119
122
|
rationale: "The proposed fix did not satisfy the verification gate.",
|
|
120
123
|
retryable: true,
|
|
121
|
-
recommendedIntervention: repeatedFailure === "verification_failure" ? "tighten_task" : "run_verifier"
|
|
124
|
+
recommendedIntervention: repeatedFailure === "verification_failure" ? "tighten_task" : "run_verifier",
|
|
125
|
+
diagnosticHint: diagnoseVerificationIssue(message)
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
if (containsPositive(message, [
|
|
@@ -192,6 +196,19 @@ export function evaluateCostGovernor(input) {
|
|
|
192
196
|
remainingTokens
|
|
193
197
|
};
|
|
194
198
|
}
|
|
199
|
+
export function calculateCostPerOutcome(input) {
|
|
200
|
+
const costPerAttempt = input.attemptCount > 0
|
|
201
|
+
? roundUsd(input.totalCostUsd / input.attemptCount)
|
|
202
|
+
: 0;
|
|
203
|
+
return {
|
|
204
|
+
costPerAcceptedChange: input.accepted ? input.totalCostUsd : undefined,
|
|
205
|
+
costPerAttempt,
|
|
206
|
+
acceptanceRate: input.accepted ? 1 : 0,
|
|
207
|
+
wastedCoordinationUsd: input.accepted
|
|
208
|
+
? roundUsd(input.preworkCostUsd)
|
|
209
|
+
: roundUsd(input.totalCostUsd)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
195
212
|
export function inferExit(input) {
|
|
196
213
|
if (input.lastResult.status === "completed" && input.lastResult.verification.passed) {
|
|
197
214
|
return {
|
|
@@ -622,3 +639,66 @@ function computeDiffRiskScore(input) {
|
|
|
622
639
|
function roundScore(value) {
|
|
623
640
|
return Math.round(value * 100) / 100;
|
|
624
641
|
}
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
// Diagnostic hint generators
|
|
644
|
+
//
|
|
645
|
+
// These extract actionable context from failure messages so the next attempt
|
|
646
|
+
// gets a concrete diagnosis, not just "verification failed."
|
|
647
|
+
// ---------------------------------------------------------------------------
|
|
648
|
+
const KNOWN_TOOLS = {
|
|
649
|
+
bun: "npm install -g bun",
|
|
650
|
+
pnpm: "npm install -g pnpm",
|
|
651
|
+
yarn: "npm install -g yarn",
|
|
652
|
+
eslint: "npm install eslint",
|
|
653
|
+
vitest: "npm install vitest",
|
|
654
|
+
jest: "npm install jest",
|
|
655
|
+
tsc: "npm install typescript",
|
|
656
|
+
prettier: "npm install prettier",
|
|
657
|
+
biome: "npm install @biomejs/biome"
|
|
658
|
+
};
|
|
659
|
+
function diagnoseEnvironmentIssue(message) {
|
|
660
|
+
// Try to extract the missing command name
|
|
661
|
+
const cmdMatch = message.match(/(?:command not found|enoent)[:\s]*['"]?(\w[\w.-]*)/i)
|
|
662
|
+
?? message.match(/['"](\w[\w.-]*)['"].*(?:not found|enoent)/i);
|
|
663
|
+
const missingCmd = cmdMatch?.[1];
|
|
664
|
+
if (missingCmd && KNOWN_TOOLS[missingCmd.toLowerCase()]) {
|
|
665
|
+
return `The verifier needs "${missingCmd}" which is not installed. Fix: ${KNOWN_TOOLS[missingCmd.toLowerCase()]}`;
|
|
666
|
+
}
|
|
667
|
+
if (missingCmd) {
|
|
668
|
+
return `The verifier could not find "${missingCmd}". Ensure it is installed in the workspace before retrying.`;
|
|
669
|
+
}
|
|
670
|
+
return "A required tool or command was not found in the environment. Check that all dependencies referenced by the verify command are installed.";
|
|
671
|
+
}
|
|
672
|
+
function diagnoseGroundingIssue(message) {
|
|
673
|
+
// Extract the missing module/symbol name
|
|
674
|
+
const moduleMatch = message.match(/cannot find module ['"]([^'"]+)['"]/i)
|
|
675
|
+
?? message.match(/module not found[:\s]*['"]?([^\s'"]+)/i);
|
|
676
|
+
const nameMatch = message.match(/cannot find name ['"]?(\w+)/i);
|
|
677
|
+
if (moduleMatch?.[1]) {
|
|
678
|
+
const mod = moduleMatch[1];
|
|
679
|
+
const isRelative = mod.startsWith(".") || mod.startsWith("/");
|
|
680
|
+
if (isRelative) {
|
|
681
|
+
return `Import "${mod}" points to a file that does not exist. Check the path and filename.`;
|
|
682
|
+
}
|
|
683
|
+
return `Module "${mod}" is not installed. Run npm install or check package.json dependencies.`;
|
|
684
|
+
}
|
|
685
|
+
if (nameMatch?.[1]) {
|
|
686
|
+
return `Symbol "${nameMatch[1]}" is not defined. Check that the import exists and the name is spelled correctly.`;
|
|
687
|
+
}
|
|
688
|
+
return "The code references modules or symbols that do not exist in the repository. Verify imports and file paths.";
|
|
689
|
+
}
|
|
690
|
+
function diagnoseVerificationIssue(message) {
|
|
691
|
+
// Count assertion failures for severity signal
|
|
692
|
+
const assertionCount = (message.match(/assertionerror/gi) ?? []).length;
|
|
693
|
+
if (assertionCount > 3) {
|
|
694
|
+
return `${String(assertionCount)} assertion failures detected — the change may have introduced a regression. Focus on the specific test expectations that broke.`;
|
|
695
|
+
}
|
|
696
|
+
// Check for common patterns
|
|
697
|
+
if (/timeout/i.test(message) && !/budget/i.test(message)) {
|
|
698
|
+
return "Tests timed out. The change may have introduced an infinite loop or unresolved async operation.";
|
|
699
|
+
}
|
|
700
|
+
if (/exit code (?!0)\d+/i.test(message) || /exited with code/i.test(message)) {
|
|
701
|
+
return "The verifier command exited non-zero. Read the output above to identify which specific check failed and why.";
|
|
702
|
+
}
|
|
703
|
+
return "Verification did not pass. Review the verifier output for the specific failing assertion or check.";
|
|
704
|
+
}
|
|
@@ -134,17 +134,24 @@ function readRepoState(repoRoot) {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
function readGitLines(repoRoot, args) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
138
|
+
const result = spawnSync("git", args, {
|
|
139
|
+
cwd: repoRoot,
|
|
140
|
+
encoding: "utf8"
|
|
141
|
+
});
|
|
142
|
+
if (result.status === 0 && typeof result.stdout === "string") {
|
|
143
|
+
return uniqueSorted(result.stdout
|
|
144
|
+
.split(/\r?\n/u)
|
|
145
|
+
.map((line) => normalizeRepoPath(line))
|
|
146
|
+
.filter(Boolean));
|
|
147
|
+
}
|
|
148
|
+
// Retry once after a short delay — handles git lock-file contention
|
|
149
|
+
// that occurs when another process holds .git/index.lock.
|
|
150
|
+
if (attempt === 0 && result.status !== 0) {
|
|
151
|
+
spawnSync("sleep", ["0.5"], { encoding: "utf8" });
|
|
152
|
+
}
|
|
143
153
|
}
|
|
144
|
-
return
|
|
145
|
-
.split(/\r?\n/u)
|
|
146
|
-
.map((line) => normalizeRepoPath(line))
|
|
147
|
-
.filter(Boolean));
|
|
154
|
+
return [];
|
|
148
155
|
}
|
|
149
156
|
function readGitScalar(repoRoot, args) {
|
|
150
157
|
const result = spawnSync("git", args, {
|
|
@@ -190,9 +197,22 @@ function restoreTrackedFileFromHead(repoRoot, filePath) {
|
|
|
190
197
|
cwd: repoRoot,
|
|
191
198
|
encoding: "utf8"
|
|
192
199
|
});
|
|
193
|
-
if (result.status
|
|
194
|
-
|
|
200
|
+
if (result.status === 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Retry once after a short delay (git lock-file contention)
|
|
204
|
+
spawnSync("sleep", ["0.5"], { encoding: "utf8" });
|
|
205
|
+
const retry = spawnSync("git", ["restore", "--staged", "--worktree", "--source=HEAD", "--", filePath], { cwd: repoRoot, encoding: "utf8" });
|
|
206
|
+
if (retry.status === 0) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Fallback: try git checkout which works on older git versions
|
|
210
|
+
const fallback = spawnSync("git", ["checkout", "HEAD", "--", filePath], { cwd: repoRoot, encoding: "utf8" });
|
|
211
|
+
if (fallback.status === 0) {
|
|
212
|
+
return;
|
|
195
213
|
}
|
|
214
|
+
throw new Error(result.stderr?.trim() ||
|
|
215
|
+
`git restore failed for ${filePath} (retry and checkout fallback also failed)`);
|
|
196
216
|
}
|
|
197
217
|
async function removeRepoPath(repoRoot, filePath) {
|
|
198
218
|
await rm(resolveRepoPath(repoRoot, filePath), { recursive: true, force: true });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route classification and compression for governed agent runs.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task needs manager/planner orchestration or can go
|
|
5
|
+
* directly to a worker, based on task complexity signals. Prevents
|
|
6
|
+
* wasted coordination cost (Pre Work Burn) on simple tasks.
|
|
7
|
+
*/
|
|
8
|
+
import type { RoutingMode, RoutingPolicy } from "../contracts/index.js";
|
|
9
|
+
export interface RouteDecision {
|
|
10
|
+
selectedMode: RoutingMode;
|
|
11
|
+
confidence: number;
|
|
12
|
+
expectedCostUsd: number;
|
|
13
|
+
expectedPreworkBurnPct: number;
|
|
14
|
+
reason: string[];
|
|
15
|
+
blockedSteps: string[];
|
|
16
|
+
compressed: boolean;
|
|
17
|
+
compressionSummary?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RouteClassificationInput {
|
|
20
|
+
objective: string;
|
|
21
|
+
verificationPlan: string[];
|
|
22
|
+
repoRoot?: string;
|
|
23
|
+
allowedPaths?: string[];
|
|
24
|
+
deniedPaths?: string[];
|
|
25
|
+
budgetUsd: number;
|
|
26
|
+
policy?: Partial<RoutingPolicy>;
|
|
27
|
+
/** Number of files the task references or is scoped to. */
|
|
28
|
+
scopedFileCount?: number;
|
|
29
|
+
/** Historical success rate for similar tasks with direct execution (0-1). */
|
|
30
|
+
historicalDirectSuccessRate?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function classifyRoute(input: RouteClassificationInput): RouteDecision;
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether a run's Pre Work Burn exceeds the routing policy limits.
|
|
35
|
+
* Returns a block reason if exceeded, undefined if within bounds.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluatePreworkBurnPolicy(preworkCostUsd: number, totalCostUsd: number, policy?: Partial<RoutingPolicy>): {
|
|
38
|
+
exceeded: boolean;
|
|
39
|
+
reason?: string;
|
|
40
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route classification and compression for governed agent runs.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task needs manager/planner orchestration or can go
|
|
5
|
+
* directly to a worker, based on task complexity signals. Prevents
|
|
6
|
+
* wasted coordination cost (Pre Work Burn) on simple tasks.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_ROUTING_POLICY } from "../contracts/index.js";
|
|
9
|
+
const SECURITY_KEYWORDS = [
|
|
10
|
+
"auth", "authentication", "authorization", "credential", "secret",
|
|
11
|
+
"token", "password", "oauth", "jwt", "session", "csrf", "xss",
|
|
12
|
+
"injection", "encryption", "certificate", "tls", "ssl", "permission"
|
|
13
|
+
];
|
|
14
|
+
const ARCHITECTURE_KEYWORDS = [
|
|
15
|
+
"refactor", "architecture", "redesign", "restructure", "migration",
|
|
16
|
+
"database schema", "api design", "service boundary", "microservice",
|
|
17
|
+
"event sourcing", "cqrs", "distributed"
|
|
18
|
+
];
|
|
19
|
+
const MIGRATION_KEYWORDS = [
|
|
20
|
+
"migration", "migrate", "upgrade", "downgrade", "schema change",
|
|
21
|
+
"data migration", "database migration", "breaking change"
|
|
22
|
+
];
|
|
23
|
+
const MULTI_FILE_KEYWORDS = [
|
|
24
|
+
"across", "all files", "every file", "codebase-wide", "global",
|
|
25
|
+
"rename everywhere", "update all", "system-wide"
|
|
26
|
+
];
|
|
27
|
+
function extractComplexitySignals(input) {
|
|
28
|
+
const lower = input.objective.toLowerCase();
|
|
29
|
+
return {
|
|
30
|
+
objectiveLength: input.objective.length,
|
|
31
|
+
fileCount: input.scopedFileCount ?? estimateFileCount(input),
|
|
32
|
+
hasSecurityKeywords: SECURITY_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
33
|
+
hasArchitectureKeywords: ARCHITECTURE_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
34
|
+
hasMigrationKeywords: MIGRATION_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
35
|
+
hasMultiFileKeywords: MULTI_FILE_KEYWORDS.some((kw) => lower.includes(kw)),
|
|
36
|
+
verifierCount: input.verificationPlan.length,
|
|
37
|
+
budgetUsd: input.budgetUsd,
|
|
38
|
+
scopeRestricted: (input.allowedPaths?.length ?? 0) > 0 || (input.deniedPaths?.length ?? 0) > 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function estimateFileCount(input) {
|
|
42
|
+
const pathCount = (input.allowedPaths?.length ?? 0) + (input.deniedPaths?.length ?? 0);
|
|
43
|
+
if (pathCount > 0)
|
|
44
|
+
return pathCount;
|
|
45
|
+
// Heuristic: short objectives with specific file refs → 1-2 files
|
|
46
|
+
const fileRefs = input.objective.match(/[\w/.-]+\.\w{1,5}/g);
|
|
47
|
+
return fileRefs?.length ?? 1;
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Confidence scoring
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
function calculateDirectConfidence(signals, input) {
|
|
53
|
+
let confidence = 0.7; // Base: most tasks can go direct
|
|
54
|
+
// Boost for simple tasks
|
|
55
|
+
if (signals.objectiveLength < 200)
|
|
56
|
+
confidence += 0.1;
|
|
57
|
+
if (signals.fileCount <= 2)
|
|
58
|
+
confidence += 0.1;
|
|
59
|
+
if (signals.scopeRestricted)
|
|
60
|
+
confidence += 0.05;
|
|
61
|
+
if (signals.verifierCount === 1)
|
|
62
|
+
confidence += 0.05;
|
|
63
|
+
// Penalize for complex tasks
|
|
64
|
+
if (signals.hasSecurityKeywords)
|
|
65
|
+
confidence -= 0.25;
|
|
66
|
+
if (signals.hasArchitectureKeywords)
|
|
67
|
+
confidence -= 0.2;
|
|
68
|
+
if (signals.hasMigrationKeywords)
|
|
69
|
+
confidence -= 0.3;
|
|
70
|
+
if (signals.hasMultiFileKeywords)
|
|
71
|
+
confidence -= 0.15;
|
|
72
|
+
if (signals.objectiveLength > 500)
|
|
73
|
+
confidence -= 0.1;
|
|
74
|
+
if (signals.fileCount > 5)
|
|
75
|
+
confidence -= 0.15;
|
|
76
|
+
if (signals.budgetUsd > 10)
|
|
77
|
+
confidence -= 0.1;
|
|
78
|
+
// Historical evidence
|
|
79
|
+
if (input.historicalDirectSuccessRate !== undefined) {
|
|
80
|
+
confidence = confidence * 0.6 + input.historicalDirectSuccessRate * 0.4;
|
|
81
|
+
}
|
|
82
|
+
return Math.max(0, Math.min(1, confidence));
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Public API
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
export function classifyRoute(input) {
|
|
88
|
+
const policy = { ...DEFAULT_ROUTING_POLICY, ...input.policy };
|
|
89
|
+
const signals = extractComplexitySignals(input);
|
|
90
|
+
const directConfidence = calculateDirectConfidence(signals, input);
|
|
91
|
+
const reason = [];
|
|
92
|
+
const blockedSteps = [];
|
|
93
|
+
// Policy override: forced mode
|
|
94
|
+
if (policy.mode === "direct") {
|
|
95
|
+
return {
|
|
96
|
+
selectedMode: "direct",
|
|
97
|
+
confidence: 1,
|
|
98
|
+
expectedCostUsd: estimateDirectCost(input.budgetUsd),
|
|
99
|
+
expectedPreworkBurnPct: 5,
|
|
100
|
+
reason: ["Policy forces direct execution mode."],
|
|
101
|
+
blockedSteps: ["manager", "planner", "router", "consensus"],
|
|
102
|
+
compressed: true,
|
|
103
|
+
compressionSummary: "Policy: direct mode — skipping all orchestration."
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (policy.mode === "consensus") {
|
|
107
|
+
return {
|
|
108
|
+
selectedMode: "consensus",
|
|
109
|
+
confidence: 0.5,
|
|
110
|
+
expectedCostUsd: input.budgetUsd * 0.8,
|
|
111
|
+
expectedPreworkBurnPct: 35,
|
|
112
|
+
reason: ["Policy forces consensus mode."],
|
|
113
|
+
blockedSteps: [],
|
|
114
|
+
compressed: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Adaptive routing
|
|
118
|
+
if (directConfidence >= policy.skipOrchestrationIfConfidenceAbove) {
|
|
119
|
+
reason.push(`Direct confidence ${(directConfidence * 100).toFixed(0)}% exceeds threshold ${(policy.skipOrchestrationIfConfidenceAbove * 100).toFixed(0)}%.`);
|
|
120
|
+
if (signals.objectiveLength < 200)
|
|
121
|
+
reason.push("Short, focused objective.");
|
|
122
|
+
if (signals.fileCount <= 2)
|
|
123
|
+
reason.push(`Scoped to ${String(signals.fileCount)} file(s).`);
|
|
124
|
+
if (signals.scopeRestricted)
|
|
125
|
+
reason.push("Path scope is restricted.");
|
|
126
|
+
blockedSteps.push("manager", "consensus");
|
|
127
|
+
return {
|
|
128
|
+
selectedMode: "direct",
|
|
129
|
+
confidence: directConfidence,
|
|
130
|
+
expectedCostUsd: estimateDirectCost(input.budgetUsd),
|
|
131
|
+
expectedPreworkBurnPct: 8,
|
|
132
|
+
reason,
|
|
133
|
+
blockedSteps,
|
|
134
|
+
compressed: true,
|
|
135
|
+
compressionSummary: `Compressed from manager→planner→worker to direct worker. Estimated savings: ${String(Math.round(input.budgetUsd * 0.3 * 100) / 100)} USD.`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Complex task — needs orchestration
|
|
139
|
+
if (signals.hasSecurityKeywords)
|
|
140
|
+
reason.push("Security-sensitive task detected.");
|
|
141
|
+
if (signals.hasMigrationKeywords)
|
|
142
|
+
reason.push("Migration task detected.");
|
|
143
|
+
if (signals.hasArchitectureKeywords)
|
|
144
|
+
reason.push("Architectural scope detected.");
|
|
145
|
+
if (signals.fileCount > 5)
|
|
146
|
+
reason.push(`Touches ${String(signals.fileCount)}+ files.`);
|
|
147
|
+
const needsConsensus = signals.hasSecurityKeywords && signals.hasMigrationKeywords;
|
|
148
|
+
return {
|
|
149
|
+
selectedMode: needsConsensus ? "consensus" : "manager",
|
|
150
|
+
confidence: directConfidence,
|
|
151
|
+
expectedCostUsd: input.budgetUsd * (needsConsensus ? 0.8 : 0.6),
|
|
152
|
+
expectedPreworkBurnPct: needsConsensus ? 40 : 25,
|
|
153
|
+
reason,
|
|
154
|
+
blockedSteps: needsConsensus ? [] : ["consensus"],
|
|
155
|
+
compressed: false
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function estimateDirectCost(budgetUsd) {
|
|
159
|
+
// Direct execution typically uses 30-50% of budget
|
|
160
|
+
return Math.round(budgetUsd * 0.35 * 100) / 100;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Checks whether a run's Pre Work Burn exceeds the routing policy limits.
|
|
164
|
+
* Returns a block reason if exceeded, undefined if within bounds.
|
|
165
|
+
*/
|
|
166
|
+
export function evaluatePreworkBurnPolicy(preworkCostUsd, totalCostUsd, policy = {}) {
|
|
167
|
+
const resolved = { ...DEFAULT_ROUTING_POLICY, ...policy };
|
|
168
|
+
if (!resolved.enabled) {
|
|
169
|
+
return { exceeded: false };
|
|
170
|
+
}
|
|
171
|
+
if (preworkCostUsd > resolved.maxPreworkCostUsd) {
|
|
172
|
+
return {
|
|
173
|
+
exceeded: true,
|
|
174
|
+
reason: `Pre-work cost $${preworkCostUsd.toFixed(2)} exceeds routing policy cap of $${resolved.maxPreworkCostUsd.toFixed(2)}.`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const burnPct = totalCostUsd > 0 ? (preworkCostUsd / totalCostUsd) * 100 : 0;
|
|
178
|
+
if (burnPct > resolved.maxPreworkBudgetPct) {
|
|
179
|
+
return {
|
|
180
|
+
exceeded: true,
|
|
181
|
+
reason: `Pre Work Burn at ${burnPct.toFixed(0)}% exceeds routing policy cap of ${String(resolved.maxPreworkBudgetPct)}%.`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { exceeded: false };
|
|
185
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CircuitBreakDecision, type TrajectoryAssessment } from "../contracts/index.js";
|
|
2
|
+
export declare function assessTrajectory(input: {
|
|
3
|
+
objective: string;
|
|
4
|
+
verificationPlan?: string[];
|
|
5
|
+
attempts: TrajectoryAttemptLike[];
|
|
6
|
+
}): TrajectoryAssessment;
|
|
7
|
+
export declare function decideCircuitBreak(input: {
|
|
8
|
+
objective: string;
|
|
9
|
+
verificationPlan?: string[];
|
|
10
|
+
attempts: TrajectoryAttemptLike[];
|
|
11
|
+
remainingIterations?: number;
|
|
12
|
+
}): CircuitBreakDecision;
|
|
13
|
+
interface TrajectoryAttemptLike {
|
|
14
|
+
index: number;
|
|
15
|
+
summary?: string;
|
|
16
|
+
failureClass?: string;
|
|
17
|
+
}
|
|
18
|
+
export {};
|