@martinloop/mcp 0.5.3 → 0.5.6
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 +175 -175
- package/dist/arcade/capabilities.d.ts +36 -0
- package/dist/arcade/capabilities.js +37 -0
- package/dist/arcade/resource.d.ts +31 -0
- package/dist/arcade/resource.js +52 -0
- package/dist/arcade/status.d.ts +23 -0
- package/dist/arcade/status.js +25 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +3 -3
- package/dist/resources.js +147 -147
- package/dist/server-validation.js +2 -0
- package/dist/server.js +79 -35
- package/dist/tools/run-loop.d.ts +2 -2
- package/dist/tools/run-loop.js +26 -6
- package/dist/tools/tool-response.d.ts +9 -1
- package/dist/tools/tool-response.js +7 -5
- package/dist/tools/tool-support.js +62 -271
- package/dist/vendor/adapters/claude-cli.d.ts +9 -0
- package/dist/vendor/adapters/claude-cli.js +21 -3
- package/dist/vendor/adapters/cli-bridge.js +7 -1
- package/dist/vendor/adapters/codex-capabilities.d.ts +12 -3
- package/dist/vendor/adapters/codex-capabilities.js +46 -18
- package/dist/vendor/adapters/codex-cli.d.ts +2 -1
- package/dist/vendor/adapters/codex-cli.js +12 -2
- package/dist/vendor/adapters/codex-host.d.ts +4 -1
- package/dist/vendor/adapters/codex-host.js +34 -16
- package/dist/vendor/adapters/codex-launcher.d.ts +1 -1
- package/dist/vendor/adapters/codex-launcher.js +1 -1
- package/dist/vendor/adapters/index.d.ts +3 -1
- package/dist/vendor/adapters/index.js +2 -0
- package/dist/vendor/adapters/openai-compatible.js +12 -9
- package/dist/vendor/adapters/stub-agent-cli.d.ts +9 -0
- package/dist/vendor/adapters/stub-agent-cli.js +43 -0
- package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
- package/dist/vendor/adapters/stub-direct-provider.js +16 -0
- package/dist/vendor/contracts/agent-execution.d.ts +9 -0
- package/dist/vendor/contracts/agent-execution.js +14 -0
- package/dist/vendor/contracts/index.d.ts +9 -0
- package/dist/vendor/contracts/index.js +1 -0
- package/dist/vendor/core/grounding.js +42 -7
- package/dist/vendor/core/index.d.ts +5 -2
- package/dist/vendor/core/index.js +20 -66
- package/dist/vendor/core/policy.d.ts +1 -0
- package/dist/vendor/core/policy.js +8 -0
- package/dist/vendor/core/rollback.d.ts +3 -0
- package/dist/vendor/core/rollback.js +33 -20
- package/dist/vendor/core/verified-handoff.js +25 -6
- package/dist/vendor/presentation/governed-plan.d.ts +33 -0
- package/dist/vendor/presentation/governed-plan.js +146 -0
- package/dist/vendor/presentation/index.d.ts +7 -0
- package/dist/vendor/presentation/index.js +7 -0
- package/dist/vendor/presentation/motion.d.ts +12 -0
- package/dist/vendor/presentation/motion.js +123 -0
- package/dist/vendor/presentation/status.d.ts +8 -0
- package/dist/vendor/presentation/status.js +11 -0
- package/dist/vendor/presentation/table.d.ts +11 -0
- package/dist/vendor/presentation/table.js +47 -0
- package/dist/vendor/presentation/text.d.ts +6 -0
- package/dist/vendor/presentation/text.js +30 -0
- package/dist/vendor/presentation/theme.d.ts +31 -0
- package/dist/vendor/presentation/theme.js +82 -0
- package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
- package/dist/vendor/presentation/verified-handoff.js +148 -0
- package/package.json +81 -80
- package/server.json +21 -21
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createAdapterCapabilities } from "./runtime-support.js";
|
|
2
|
+
export function createStubAgentCliAdapter(options) {
|
|
3
|
+
const metadata = {
|
|
4
|
+
command: options.command.join(" "),
|
|
5
|
+
providerId: "agent-cli",
|
|
6
|
+
model: options.profile ?? options.command[0] ?? "default",
|
|
7
|
+
transport: "cli",
|
|
8
|
+
capabilities: createAdapterCapabilities(),
|
|
9
|
+
...(options.profile ? { profile: options.profile } : {})
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
adapterId: `agent-cli:stub:${options.command.join(":")}`,
|
|
13
|
+
kind: "agent-cli",
|
|
14
|
+
label: options.label ?? `Stub agent CLI (${options.command.join(" ")})`,
|
|
15
|
+
metadata,
|
|
16
|
+
async execute(request) {
|
|
17
|
+
if (options.responder) {
|
|
18
|
+
return await options.responder(request);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
status: "failed",
|
|
22
|
+
summary: `Stub agent CLI ${options.command.join(" ")} did not execute a live session.`,
|
|
23
|
+
usage: {
|
|
24
|
+
actualUsd: 0,
|
|
25
|
+
tokensIn: 0,
|
|
26
|
+
tokensOut: 0,
|
|
27
|
+
provenance: "unavailable"
|
|
28
|
+
},
|
|
29
|
+
verification: {
|
|
30
|
+
passed: false,
|
|
31
|
+
summary: "No CLI execution was attempted."
|
|
32
|
+
},
|
|
33
|
+
failure: {
|
|
34
|
+
message: `Agent CLI ${options.command.join(" ")} is not configured for live execution.`,
|
|
35
|
+
classHint: "environment_mismatch"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Internal test-compatibility re-export. Public builders remove the entire
|
|
42
|
+
// stub compatibility surface before constructing distributable packages.
|
|
43
|
+
export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
|
|
2
|
+
export interface StubDirectProviderAdapterOptions {
|
|
3
|
+
providerId: string;
|
|
4
|
+
model: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
|
|
7
|
+
}
|
|
8
|
+
export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
+
export function createStubDirectProviderAdapter(options) {
|
|
3
|
+
const adapter = createDirectProviderAdapter({
|
|
4
|
+
providerId: options.providerId,
|
|
5
|
+
model: options.model,
|
|
6
|
+
label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
|
|
7
|
+
capabilities: {
|
|
8
|
+
workspaceMutations: false
|
|
9
|
+
},
|
|
10
|
+
responder: options.responder
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
...adapter,
|
|
14
|
+
adapterId: `direct:stub:${options.providerId}:${options.model}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_AGENT_EXECUTION_INTENT: "governed-autonomous";
|
|
2
|
+
export type AgentExecutionIntent = typeof DEFAULT_AGENT_EXECUTION_INTENT;
|
|
3
|
+
/** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
|
|
4
|
+
export declare const GOVERNED_AUTONOMOUS_BOUNDARY: {
|
|
5
|
+
readonly interaction: "non-interactive";
|
|
6
|
+
readonly writeScope: "workspace-only";
|
|
7
|
+
};
|
|
8
|
+
export declare const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300000;
|
|
9
|
+
export declare function normalizeProviderExecutionTimeoutMs(value: number | undefined): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_AGENT_EXECUTION_INTENT = "governed-autonomous";
|
|
2
|
+
/** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
|
|
3
|
+
export const GOVERNED_AUTONOMOUS_BOUNDARY = {
|
|
4
|
+
interaction: "non-interactive",
|
|
5
|
+
writeScope: "workspace-only"
|
|
6
|
+
};
|
|
7
|
+
export const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300_000;
|
|
8
|
+
export function normalizeProviderExecutionTimeoutMs(value) {
|
|
9
|
+
const resolved = value ?? DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS;
|
|
10
|
+
if (!Number.isFinite(resolved) || resolved <= 0) {
|
|
11
|
+
throw new Error("providerExecutionTimeoutMs must be a positive finite number.");
|
|
12
|
+
}
|
|
13
|
+
return resolved;
|
|
14
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* portfolio snapshots and routing economics.
|
|
9
9
|
*/
|
|
10
10
|
import type { TerminationEnvelopeV1 } from "./exits.js";
|
|
11
|
+
import type { AgentExecutionIntent } from "./agent-execution.js";
|
|
12
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
|
|
13
|
+
export type { AgentExecutionIntent } from "./agent-execution.js";
|
|
11
14
|
export type LoopStatus = "queued" | "running" | "verifying" | "completed" | "failed" | "exited";
|
|
12
15
|
export type LoopLifecycleState = "created" | "running" | "verifying" | "completed" | "budget_exit" | "diminishing_returns" | "stuck_exit" | "human_escalation" | "wall_clock" | "error_threshold" | "external_event";
|
|
13
16
|
export declare const FAILURE_CLASSES: readonly ["logic_error", "hallucination", "syntax_error", "type_error", "test_regression", "scope_creep", "no_progress", "repo_grounding_failure", "verification_failure", "environment_mismatch", "budget_pressure", "safety_leash_blocked", "sandbox_write_blocked"];
|
|
@@ -27,6 +30,10 @@ export interface LoopTask {
|
|
|
27
30
|
executionProfile?: ExecutionProfile;
|
|
28
31
|
allowedNetworkDomains?: string[];
|
|
29
32
|
approvalPolicy?: ApprovalPolicy;
|
|
33
|
+
/** Provider-neutral execution posture. Defaults to governed autonomous execution. */
|
|
34
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
35
|
+
/** Hard wall-clock limit for one provider coding process. */
|
|
36
|
+
providerExecutionTimeoutMs?: number;
|
|
30
37
|
/** Glob patterns for files the agent is allowed to modify. Empty = no restriction. */
|
|
31
38
|
allowedPaths?: string[];
|
|
32
39
|
/** Glob patterns for files the agent must never modify. */
|
|
@@ -103,6 +110,8 @@ export interface ReceiptScope {
|
|
|
103
110
|
writableRoot?: string;
|
|
104
111
|
/** How the effective sandbox was determined. */
|
|
105
112
|
capabilitySource?: "probe" | "configured" | "unknown";
|
|
113
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
114
|
+
providerExecutionTimeoutMs?: number;
|
|
106
115
|
/** Enforced demo changes (DEMO.md-only enforcement output). */
|
|
107
116
|
demoChangedFiles?: string[];
|
|
108
117
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* creating and managing loop records, handling events, validating batches, and tracking
|
|
8
8
|
* portfolio snapshots and routing economics.
|
|
9
9
|
*/
|
|
10
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
|
|
10
11
|
export const FAILURE_CLASSES = [
|
|
11
12
|
"logic_error",
|
|
12
13
|
"hallucination",
|
|
@@ -196,7 +196,14 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
196
196
|
const indexedPaths = new Set(index.files.map((file) => file.path));
|
|
197
197
|
const indexedSymbols = new Set(index.files.flatMap((file) => file.symbols.map((symbol) => symbol.toLowerCase())));
|
|
198
198
|
const referencedFiles = new Set();
|
|
199
|
+
const addedFiles = new Set();
|
|
199
200
|
const diffFilePattern = /^(?:---|\+\+\+)\s+[ab]\/(.+)$/gm;
|
|
201
|
+
const addedFilePattern = /^---\s+\/dev\/null\r?\n\+\+\+\s+b\/(.+)$/gm;
|
|
202
|
+
for (const match of diff.matchAll(addedFilePattern)) {
|
|
203
|
+
const filePath = match[1]?.trim();
|
|
204
|
+
if (filePath)
|
|
205
|
+
addedFiles.add(filePath);
|
|
206
|
+
}
|
|
200
207
|
for (const match of diff.matchAll(diffFilePattern)) {
|
|
201
208
|
const filePath = match[1]?.trim();
|
|
202
209
|
if (filePath && filePath !== "/dev/null") {
|
|
@@ -204,7 +211,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
for (const filePath of referencedFiles) {
|
|
207
|
-
if (indexedPaths.has(filePath)) {
|
|
214
|
+
if (indexedPaths.has(filePath) || addedFiles.has(filePath)) {
|
|
208
215
|
resolvedFiles.push(filePath);
|
|
209
216
|
}
|
|
210
217
|
else {
|
|
@@ -227,7 +234,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
227
234
|
}
|
|
228
235
|
const addedLinePattern = /^\+(?!\+\+)(.+)$/gm;
|
|
229
236
|
const importPattern = /(?:import|require)\s*(?:.*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
230
|
-
const
|
|
237
|
+
const callableIdentifierPattern = /(?<!\.)\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
231
238
|
const symbolKeywords = new Set([
|
|
232
239
|
"const",
|
|
233
240
|
"let",
|
|
@@ -244,10 +251,33 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
244
251
|
"true",
|
|
245
252
|
"false",
|
|
246
253
|
"null",
|
|
247
|
-
"undefined"
|
|
254
|
+
"undefined",
|
|
255
|
+
"if",
|
|
256
|
+
"for",
|
|
257
|
+
"while",
|
|
258
|
+
"switch",
|
|
259
|
+
"catch",
|
|
260
|
+
"typeof"
|
|
248
261
|
]);
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
const addedLines = [...diff.matchAll(addedLinePattern)].map((match) => match[1] ?? "");
|
|
263
|
+
const declaredSymbols = new Set();
|
|
264
|
+
const declarationPattern = /\b(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
265
|
+
for (const line of addedLines) {
|
|
266
|
+
for (const declarationMatch of line.matchAll(declarationPattern)) {
|
|
267
|
+
const symbol = declarationMatch[1];
|
|
268
|
+
if (symbol)
|
|
269
|
+
declaredSymbols.add(symbol.toLowerCase());
|
|
270
|
+
}
|
|
271
|
+
if (/^\s*import\b/u.test(line)) {
|
|
272
|
+
const importClause = line.match(/^\s*import\s+(.+?)\s+from\s+['"]/u)?.[1] ?? "";
|
|
273
|
+
for (const importSymbol of importClause.matchAll(/\b(?:as\s+)?([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
|
|
274
|
+
const symbol = importSymbol[1];
|
|
275
|
+
if (symbol && symbol !== "as")
|
|
276
|
+
declaredSymbols.add(symbol.toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const line of addedLines) {
|
|
251
281
|
for (const importMatch of line.matchAll(importPattern)) {
|
|
252
282
|
const reference = importMatch[1] ?? "";
|
|
253
283
|
if (!reference.startsWith(".") && !reference.startsWith("/")) {
|
|
@@ -259,6 +289,9 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
259
289
|
const hasMatch = index.files.some((file) => {
|
|
260
290
|
const withoutExt = file.path.replace(/\.(js|ts|tsx|jsx)$/, "");
|
|
261
291
|
return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
|
|
292
|
+
}) || [...addedFiles].some((filePath) => {
|
|
293
|
+
const withoutExt = filePath.replace(/\.(js|ts|tsx|jsx)$/, "");
|
|
294
|
+
return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
|
|
262
295
|
});
|
|
263
296
|
if (!hasMatch) {
|
|
264
297
|
violations.push({
|
|
@@ -268,13 +301,15 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
|
|
|
268
301
|
});
|
|
269
302
|
}
|
|
270
303
|
}
|
|
271
|
-
for (const symbolMatch of line.matchAll(
|
|
304
|
+
for (const symbolMatch of line.matchAll(callableIdentifierPattern)) {
|
|
272
305
|
const symbol = symbolMatch[1];
|
|
273
306
|
if (!symbol)
|
|
274
307
|
continue;
|
|
275
308
|
if (symbolKeywords.has(symbol))
|
|
276
309
|
continue;
|
|
277
|
-
if (
|
|
310
|
+
if (/^[A-Z]/u.test(symbol))
|
|
311
|
+
continue;
|
|
312
|
+
if (declaredSymbols.has(symbol.toLowerCase()))
|
|
278
313
|
continue;
|
|
279
314
|
if (indexedSymbols.has(symbol.toLowerCase()))
|
|
280
315
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApprovalPolicy, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
1
|
+
import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
|
|
2
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
|
|
3
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
4
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
@@ -7,7 +7,8 @@ import { type RunStore } from "./persistence/index.js";
|
|
|
7
7
|
import { type ExitPolicyOverrides } from "./exits.js";
|
|
8
8
|
import { type ExitSignalSource } from "./exit-signal.js";
|
|
9
9
|
import { type VerifierExecutionBinding } from "./verified-handoff.js";
|
|
10
|
-
export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
|
|
10
|
+
export type { AgentExecutionIntent, ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
|
|
11
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
11
12
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
12
13
|
export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
|
|
13
14
|
export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
|
|
@@ -69,6 +70,8 @@ export interface MartinAdapterRequest {
|
|
|
69
70
|
executionProfile?: ExecutionProfile;
|
|
70
71
|
allowedNetworkDomains?: string[];
|
|
71
72
|
approvalPolicy?: ApprovalPolicy;
|
|
73
|
+
agentExecutionIntent?: AgentExecutionIntent;
|
|
74
|
+
providerExecutionTimeoutMs?: number;
|
|
72
75
|
focus: string;
|
|
73
76
|
remainingBudgetUsd: number;
|
|
74
77
|
remainingIterations: number;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
1
|
+
import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
4
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
5
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
6
4
|
import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
|
|
@@ -15,6 +13,7 @@ import { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
15
13
|
import { decideCircuitBreak } from "./trajectory.js";
|
|
16
14
|
import { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
|
|
17
15
|
import { verifierActuallyPassed, } from "./verified-handoff.js";
|
|
16
|
+
export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
18
17
|
export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
|
|
19
18
|
export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
|
|
20
19
|
export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
|
|
@@ -160,12 +159,16 @@ export function distillContext(loop, options = {}) {
|
|
|
160
159
|
export async function runMartin(input) {
|
|
161
160
|
const now = input.now ?? (() => new Date().toISOString());
|
|
162
161
|
const idFactory = input.idFactory;
|
|
162
|
+
const providerExecutionTimeoutMs = normalizeProviderExecutionTimeoutMs(input.task.providerExecutionTimeoutMs);
|
|
163
|
+
const agentExecutionIntent = input.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT;
|
|
163
164
|
let loop = createLoopRecord({
|
|
164
165
|
workspaceId: input.workspaceId,
|
|
165
166
|
projectId: input.projectId,
|
|
166
|
-
task: input.task,
|
|
167
|
+
task: { ...input.task, agentExecutionIntent, providerExecutionTimeoutMs },
|
|
167
168
|
budget: input.budget,
|
|
168
|
-
...(input.receiptScope
|
|
169
|
+
...(input.receiptScope
|
|
170
|
+
? { receiptScope: { ...input.receiptScope, agentExecutionIntent, providerExecutionTimeoutMs } }
|
|
171
|
+
: {}),
|
|
169
172
|
...(input.teamId ? { teamId: input.teamId } : {}),
|
|
170
173
|
...(input.metadata ? { metadata: input.metadata } : {})
|
|
171
174
|
}, { now: now(), idFactory });
|
|
@@ -655,6 +658,8 @@ export async function runMartin(input) {
|
|
|
655
658
|
? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
|
|
656
659
|
: {}),
|
|
657
660
|
...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
|
|
661
|
+
agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
|
|
662
|
+
providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
|
|
658
663
|
focus: distilled.focus,
|
|
659
664
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
660
665
|
remainingIterations: distilled.constraints.remainingIterations,
|
|
@@ -745,6 +750,8 @@ export async function runMartin(input) {
|
|
|
745
750
|
? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
|
|
746
751
|
: {}),
|
|
747
752
|
...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
|
|
753
|
+
agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
|
|
754
|
+
providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
|
|
748
755
|
focus: distilled.focus,
|
|
749
756
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
750
757
|
remainingIterations: distilled.constraints.remainingIterations,
|
|
@@ -787,6 +794,7 @@ export async function runMartin(input) {
|
|
|
787
794
|
const attemptCompletedAt = now();
|
|
788
795
|
const compiledContext = compilePromptPacket(request);
|
|
789
796
|
const expectedVerifierBinding = resolveExpectedVerifierBinding(request);
|
|
797
|
+
const verifierConfigured = expectedVerifierBinding.commands.length > 0;
|
|
790
798
|
const verification = normalizeVerificationOutcome(result, expectedVerifierBinding);
|
|
791
799
|
// PATCH → VERIFY
|
|
792
800
|
currentPhase = "VERIFY";
|
|
@@ -1126,17 +1134,12 @@ export async function runMartin(input) {
|
|
|
1126
1134
|
// Uses the task's repoRoot to build/load the grounding index, then scans any diff
|
|
1127
1135
|
let groundingScanResult;
|
|
1128
1136
|
const patchDiff = buildPatchDiff(result, changedFiles);
|
|
1129
|
-
|
|
1130
|
-
const hasReportedOrObservedChanges = changedFiles.length > 0 || (result.execution?.changedFiles?.length ?? 0) > 0;
|
|
1131
|
-
const groundingEvidenceRequired = result.status === "completed" && workspaceMutationCapable && hasReportedOrObservedChanges;
|
|
1132
|
-
let groundingEvidenceAvailable = !groundingEvidenceRequired;
|
|
1133
|
-
if (groundingEvidenceRequired && patchDiff && input.task.repoRoot) {
|
|
1137
|
+
if (patchDiff && input.task.repoRoot) {
|
|
1134
1138
|
try {
|
|
1135
1139
|
const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
|
|
1136
|
-
groundingScanResult =
|
|
1140
|
+
groundingScanResult = scanPatchForGroundingViolations(patchDiff, groundingIndex, {
|
|
1137
1141
|
allowedPaths: input.task.allowedPaths
|
|
1138
|
-
})
|
|
1139
|
-
groundingEvidenceAvailable = true;
|
|
1142
|
+
});
|
|
1140
1143
|
if (input.store && groundingScanResult.violations.length > 0) {
|
|
1141
1144
|
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1142
1145
|
kind: "grounding.violations_found",
|
|
@@ -1152,22 +1155,10 @@ export async function runMartin(input) {
|
|
|
1152
1155
|
}
|
|
1153
1156
|
}
|
|
1154
1157
|
catch {
|
|
1155
|
-
//
|
|
1156
|
-
// grounding inputs follow the same recover-forward trust contract.
|
|
1158
|
+
// Grounding scan is best-effort — never fail the loop because of a scan error
|
|
1157
1159
|
}
|
|
1158
1160
|
}
|
|
1159
|
-
|
|
1160
|
-
// The completed work remains useful, but unavailable grounding evidence
|
|
1161
|
-
// cannot be converted into a zero-violation governance claim.
|
|
1162
|
-
loop = {
|
|
1163
|
-
...loop,
|
|
1164
|
-
metadata: {
|
|
1165
|
-
...loop.metadata,
|
|
1166
|
-
groundingEvidenceStatus: "unavailable"
|
|
1167
|
-
}
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && groundingEvidenceAvailable;
|
|
1161
|
+
const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && verifierConfigured;
|
|
1171
1162
|
let patchDecision;
|
|
1172
1163
|
if (shouldEvaluatePatchTruth) {
|
|
1173
1164
|
patchDecision = evaluatePatchDecision({
|
|
@@ -1228,7 +1219,7 @@ export async function runMartin(input) {
|
|
|
1228
1219
|
}
|
|
1229
1220
|
else {
|
|
1230
1221
|
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1231
|
-
kind: verification.passed ? "attempt.kept" : "attempt.discarded",
|
|
1222
|
+
kind: verification.passed || !verifierConfigured ? "attempt.kept" : "attempt.discarded",
|
|
1232
1223
|
runId: loop.loopId,
|
|
1233
1224
|
attemptIndex: currentAttemptIndex,
|
|
1234
1225
|
payload: { reason: verification.summary }
|
|
@@ -1305,6 +1296,7 @@ export async function runMartin(input) {
|
|
|
1305
1296
|
lastResult: effectiveResult,
|
|
1306
1297
|
lastFailure: failure,
|
|
1307
1298
|
costState,
|
|
1299
|
+
verificationRequired: verifierConfigured,
|
|
1308
1300
|
canSwitchAdapter: failure?.recommendedIntervention === "switch_adapter" &&
|
|
1309
1301
|
adapterChain[currentAdapterIndex] !== undefined &&
|
|
1310
1302
|
currentAdapter.adapterId !== executingAdapter.adapterId
|
|
@@ -1569,9 +1561,6 @@ function mergeCostProvenance(previous, current) {
|
|
|
1569
1561
|
return COST_PROVENANCE_RANK[current] < COST_PROVENANCE_RANK[previous] ? current : previous;
|
|
1570
1562
|
}
|
|
1571
1563
|
function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
1572
|
-
if (repoRoot && rollbackBoundary?.headRef) {
|
|
1573
|
-
return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
|
|
1574
|
-
}
|
|
1575
1564
|
if (result.execution?.changedFiles !== undefined) {
|
|
1576
1565
|
return result.execution.changedFiles;
|
|
1577
1566
|
}
|
|
@@ -1592,41 +1581,6 @@ function buildPatchDiff(result, changedFiles) {
|
|
|
1592
1581
|
}
|
|
1593
1582
|
return undefined;
|
|
1594
1583
|
}
|
|
1595
|
-
function reconcileExistingGroundingFiles(scan, repoRoot, boundary) {
|
|
1596
|
-
const resolvedFiles = new Set(scan.resolvedFiles);
|
|
1597
|
-
const violations = scan.violations.filter((violation) => {
|
|
1598
|
-
if (violation.kind !== "file_not_found") {
|
|
1599
|
-
return true;
|
|
1600
|
-
}
|
|
1601
|
-
const absolutePath = resolve(repoRoot, violation.reference);
|
|
1602
|
-
const relativePath = relative(repoRoot, absolutePath);
|
|
1603
|
-
const isInsideRepo = relativePath.length > 0 && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
1604
|
-
if (!isInsideRepo || !isTrackedRepoFileAtBoundary(repoRoot, violation.reference, boundary)) {
|
|
1605
|
-
return true;
|
|
1606
|
-
}
|
|
1607
|
-
resolvedFiles.add(violation.reference);
|
|
1608
|
-
return false;
|
|
1609
|
-
});
|
|
1610
|
-
return {
|
|
1611
|
-
...scan,
|
|
1612
|
-
violations,
|
|
1613
|
-
resolvedFiles: [...resolvedFiles]
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
function isTrackedRepoFileAtBoundary(repoRoot, filePath, boundary) {
|
|
1617
|
-
if (!boundary) {
|
|
1618
|
-
return false;
|
|
1619
|
-
}
|
|
1620
|
-
const normalizedPath = filePath.replace(/\\/gu, "/");
|
|
1621
|
-
if (boundary.snapshots.some((snapshot) => snapshot.existed && snapshot.path === normalizedPath)) {
|
|
1622
|
-
return true;
|
|
1623
|
-
}
|
|
1624
|
-
if (!boundary.headRef) {
|
|
1625
|
-
return false;
|
|
1626
|
-
}
|
|
1627
|
-
const result = spawnSync("git", ["-C", repoRoot, "cat-file", "-e", `${boundary.headRef}:${normalizedPath}`], { encoding: "utf8", stdio: "ignore", windowsHide: true });
|
|
1628
|
-
return result.status === 0;
|
|
1629
|
-
}
|
|
1630
1584
|
function createBudgetSettlement(input) {
|
|
1631
1585
|
const totalActualUsd = getUsageUsd(input.usage);
|
|
1632
1586
|
return {
|
|
@@ -97,6 +97,7 @@ export declare function inferExit(input: {
|
|
|
97
97
|
lastFailure?: FailureAssessment;
|
|
98
98
|
costState: CostGovernorState;
|
|
99
99
|
canSwitchAdapter?: boolean;
|
|
100
|
+
verificationRequired?: boolean;
|
|
100
101
|
}): ExitDecision;
|
|
101
102
|
export interface BudgetPreflightInput {
|
|
102
103
|
promptCharCount: number;
|
|
@@ -210,6 +210,14 @@ export function calculateCostPerOutcome(input) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
export function inferExit(input) {
|
|
213
|
+
if (input.lastResult.status === "completed" && input.verificationRequired === false) {
|
|
214
|
+
return {
|
|
215
|
+
shouldExit: true,
|
|
216
|
+
lifecycleState: "completed",
|
|
217
|
+
status: "completed",
|
|
218
|
+
reason: "Execution-only work completed without verifier evidence; outcome is not VERIFIED."
|
|
219
|
+
};
|
|
220
|
+
}
|
|
213
221
|
if (input.lastResult.status === "completed" && input.lastResult.verification.passed) {
|
|
214
222
|
return {
|
|
215
223
|
shouldExit: true,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { PatchDecision, RollbackBoundaryArtifact, RollbackOutcomeArtifact } from "../contracts/index.js";
|
|
2
|
+
export declare class RollbackStateUnavailableError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
2
5
|
export declare function listAttemptChangedFilesSinceBoundary(input: {
|
|
3
6
|
repoRoot?: string;
|
|
4
7
|
boundary?: RollbackBoundaryArtifact;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
3
|
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
export class RollbackStateUnavailableError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "RollbackStateUnavailableError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
5
10
|
export function listAttemptChangedFilesSinceBoundary(input) {
|
|
6
11
|
if (!input.repoRoot) {
|
|
7
12
|
return [];
|
|
@@ -12,24 +17,11 @@ export function listAttemptChangedFilesSinceBoundary(input) {
|
|
|
12
17
|
}
|
|
13
18
|
const baselineTracked = new Set(input.boundary.trackedDirtyFiles);
|
|
14
19
|
const baselineUntracked = new Set(input.boundary.untrackedFiles);
|
|
15
|
-
const baselineContentChanges = input.boundary.snapshots
|
|
16
|
-
.filter((snapshot) => snapshot.existed && repoFileDiffersFromSnapshot(input.repoRoot, snapshot))
|
|
17
|
-
.map((snapshot) => snapshot.path);
|
|
18
20
|
return uniqueSorted([
|
|
19
21
|
...repoState.trackedDirtyFiles.filter((filePath) => !baselineTracked.has(filePath)),
|
|
20
|
-
...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
|
|
21
|
-
...baselineContentChanges
|
|
22
|
+
...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
|
|
22
23
|
]);
|
|
23
24
|
}
|
|
24
|
-
function repoFileDiffersFromSnapshot(repoRoot, snapshot) {
|
|
25
|
-
try {
|
|
26
|
-
const current = readFileSync(resolveRepoPath(repoRoot, snapshot.path));
|
|
27
|
-
return current.toString("base64") !== snapshot.contentBase64;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
25
|
export async function captureRollbackBoundary(input) {
|
|
34
26
|
if (!input.repoRoot) {
|
|
35
27
|
return undefined;
|
|
@@ -147,6 +139,7 @@ function readRepoState(repoRoot) {
|
|
|
147
139
|
};
|
|
148
140
|
}
|
|
149
141
|
function readGitLines(repoRoot, args) {
|
|
142
|
+
let lastFailure = "unknown Git failure";
|
|
150
143
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
151
144
|
const result = spawnSync("git", args, {
|
|
152
145
|
cwd: repoRoot,
|
|
@@ -158,13 +151,14 @@ function readGitLines(repoRoot, args) {
|
|
|
158
151
|
.map((line) => normalizeRepoPath(line))
|
|
159
152
|
.filter(Boolean));
|
|
160
153
|
}
|
|
154
|
+
lastFailure = gitFailureMessage(result);
|
|
161
155
|
// Retry once after a short delay — handles git lock-file contention
|
|
162
156
|
// that occurs when another process holds .git/index.lock.
|
|
163
157
|
if (attempt === 0 && result.status !== 0) {
|
|
164
|
-
|
|
158
|
+
delaySynchronously(500);
|
|
165
159
|
}
|
|
166
160
|
}
|
|
167
|
-
|
|
161
|
+
throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${lastFailure}`);
|
|
168
162
|
}
|
|
169
163
|
function readGitScalar(repoRoot, args) {
|
|
170
164
|
const result = spawnSync("git", args, {
|
|
@@ -172,7 +166,7 @@ function readGitScalar(repoRoot, args) {
|
|
|
172
166
|
encoding: "utf8"
|
|
173
167
|
});
|
|
174
168
|
if (result.status !== 0 || typeof result.stdout !== "string") {
|
|
175
|
-
|
|
169
|
+
throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${gitFailureMessage(result)}`);
|
|
176
170
|
}
|
|
177
171
|
const value = result.stdout.trim();
|
|
178
172
|
return value.length > 0 ? value : undefined;
|
|
@@ -188,7 +182,10 @@ async function readRollbackSnapshot(repoRoot, filePath) {
|
|
|
188
182
|
contentBase64: contents.toString("base64")
|
|
189
183
|
};
|
|
190
184
|
}
|
|
191
|
-
catch {
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!isFileNotFoundError(error)) {
|
|
187
|
+
throw new RollbackStateUnavailableError(`Rollback snapshot unavailable for ${normalizeRepoPath(filePath)}: ${toErrorMessage(error)}`);
|
|
188
|
+
}
|
|
192
189
|
return {
|
|
193
190
|
path: normalizeRepoPath(filePath),
|
|
194
191
|
existed: false,
|
|
@@ -214,7 +211,7 @@ function restoreTrackedFileFromHead(repoRoot, filePath) {
|
|
|
214
211
|
return;
|
|
215
212
|
}
|
|
216
213
|
// Retry once after a short delay (git lock-file contention)
|
|
217
|
-
|
|
214
|
+
delaySynchronously(500);
|
|
218
215
|
const retry = spawnSync("git", ["restore", "--staged", "--worktree", "--source=HEAD", "--", filePath], { cwd: repoRoot, encoding: "utf8" });
|
|
219
216
|
if (retry.status === 0) {
|
|
220
217
|
return;
|
|
@@ -264,3 +261,19 @@ function emptyRepoState() {
|
|
|
264
261
|
function toErrorMessage(error) {
|
|
265
262
|
return error instanceof Error ? error.message : String(error);
|
|
266
263
|
}
|
|
264
|
+
function gitFailureMessage(result) {
|
|
265
|
+
if (result.error) {
|
|
266
|
+
return result.error.message;
|
|
267
|
+
}
|
|
268
|
+
if (typeof result.stderr === "string" && result.stderr.trim().length > 0) {
|
|
269
|
+
return result.stderr.trim();
|
|
270
|
+
}
|
|
271
|
+
return `git exited with status ${String(result.status)}`;
|
|
272
|
+
}
|
|
273
|
+
function isFileNotFoundError(error) {
|
|
274
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
275
|
+
}
|
|
276
|
+
function delaySynchronously(milliseconds) {
|
|
277
|
+
const signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
278
|
+
Atomics.wait(signal, 0, 0, milliseconds);
|
|
279
|
+
}
|