@martinloop/mcp 0.2.7 → 0.3.1
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 +49 -104
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +2 -2
- package/dist/server-validation.d.ts +1 -0
- package/dist/server-validation.js +8 -0
- package/dist/server.js +87 -9
- package/dist/tools/doctor.d.ts +39 -1
- package/dist/tools/doctor.js +68 -9
- package/dist/tools/eval.js +3 -2
- package/dist/tools/get-run.d.ts +3 -0
- package/dist/tools/get-run.js +3 -1
- package/dist/tools/get-verification-results.d.ts +3 -0
- package/dist/tools/get-verification-results.js +3 -1
- package/dist/tools/plan.js +4 -2
- package/dist/tools/pr-tools.js +2 -1
- package/dist/tools/preflight.d.ts +41 -1
- package/dist/tools/preflight.js +74 -19
- package/dist/tools/run-dossier.d.ts +3 -0
- package/dist/tools/run-dossier.js +5 -2
- package/dist/tools/run-loop.d.ts +7 -2
- package/dist/tools/run-loop.js +67 -35
- package/dist/tools/run-store.js +67 -15
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-support.d.ts +8 -3
- package/dist/tools/tool-support.js +61 -18
- package/dist/tools/workflow-governance.d.ts +19 -3
- package/dist/tools/workflow-governance.js +107 -55
- package/dist/vendor/adapters/claude-cli.d.ts +45 -3
- package/dist/vendor/adapters/claude-cli.js +465 -45
- package/dist/vendor/adapters/cli-bridge.d.ts +46 -0
- package/dist/vendor/adapters/cli-bridge.js +147 -38
- package/dist/vendor/adapters/codex-launcher.d.ts +76 -0
- package/dist/vendor/adapters/codex-launcher.js +538 -0
- package/dist/vendor/adapters/index.d.ts +3 -2
- package/dist/vendor/adapters/index.js +3 -2
- package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
- package/dist/vendor/adapters/openai-compatible.js +50 -19
- package/dist/vendor/adapters/runtime-support.d.ts +3 -0
- package/dist/vendor/adapters/runtime-support.js +9 -1
- package/dist/vendor/adapters/stub-direct-provider.js +3 -0
- package/dist/vendor/adapters/verifier-only.d.ts +2 -0
- package/dist/vendor/adapters/verifier-only.js +11 -4
- package/dist/vendor/contracts/index.d.ts +39 -0
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/core/context-integrity.js +28 -3
- package/dist/vendor/core/grounding.d.ts +1 -0
- package/dist/vendor/core/grounding.js +6 -2
- package/dist/vendor/core/index.d.ts +24 -3
- package/dist/vendor/core/index.js +113 -21
- package/dist/vendor/core/leash.js +85 -8
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/integrity.d.ts +38 -0
- package/dist/vendor/core/persistence/integrity.js +248 -0
- package/dist/vendor/core/persistence/store.d.ts +7 -0
- package/dist/vendor/core/persistence/store.js +25 -1
- package/dist/vendor/core/policy.d.ts +9 -0
- package/dist/workflow-state.d.ts +9 -0
- package/dist/workflow-state.js +46 -3
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -12,17 +12,23 @@
|
|
|
12
12
|
* Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
|
|
13
13
|
*
|
|
14
14
|
* Usage:
|
|
15
|
+
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
|
+
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
+
* MARTIN_OPENAI_MODEL=gpt-4.1-mini
|
|
18
|
+
* martin-loop run "fix the bug" --engine openai
|
|
19
|
+
*
|
|
20
|
+
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
15
21
|
* MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
|
|
16
22
|
* MARTIN_OPENAI_API_KEY=sk-or-...
|
|
17
23
|
* MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
|
|
18
|
-
* martin run "fix the bug" --engine openai
|
|
24
|
+
* martin-loop run "fix the bug" --engine openai
|
|
19
25
|
*
|
|
20
26
|
* Or for Ollama:
|
|
21
27
|
* MARTIN_OPENAI_BASE_URL=http://localhost:11434
|
|
22
28
|
* MARTIN_OPENAI_MODEL=llama3.3
|
|
23
|
-
* martin run "fix the bug" --engine openai
|
|
29
|
+
* martin-loop run "fix the bug" --engine openai
|
|
24
30
|
*/
|
|
25
|
-
import {
|
|
31
|
+
import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
|
|
26
32
|
import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
|
|
27
33
|
// ---------------------------------------------------------------------------
|
|
28
34
|
// OpenRouter/OpenAI-compatible model pricing ($/1K tokens)
|
|
@@ -73,6 +79,18 @@ Follow these rules exactly:
|
|
|
73
79
|
- If the task asks you to write or modify code, output the complete file content with changes applied.
|
|
74
80
|
- Be precise, minimal, and test-backed in all changes.
|
|
75
81
|
- State what you changed and why at the end of your response.`;
|
|
82
|
+
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
83
|
+
export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
84
|
+
export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
|
|
85
|
+
const apiKey = env["MARTIN_OPENAI_API_KEY"] ?? "";
|
|
86
|
+
return {
|
|
87
|
+
baseUrl: env["MARTIN_OPENAI_BASE_URL"] ?? DEFAULT_OPENAI_BASE_URL,
|
|
88
|
+
model: env["MARTIN_OPENAI_MODEL"] ?? DEFAULT_OPENAI_MODEL,
|
|
89
|
+
apiKey,
|
|
90
|
+
apiKeyConfigured: apiKey.length > 0,
|
|
91
|
+
authPosture: apiKey.length > 0 ? "api_key" : "anonymous_or_local"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
76
94
|
function buildPrompt(request) {
|
|
77
95
|
const lines = [
|
|
78
96
|
`TASK: ${request.context.taskTitle}`,
|
|
@@ -105,13 +123,17 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
105
123
|
const verifyTimeoutMs = options.verifyTimeoutMs ?? 60_000;
|
|
106
124
|
const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
107
125
|
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
126
|
+
const runtimeConfig = resolveOpenAiCompatibleRuntimeConfig();
|
|
127
|
+
const baseUrl = (options.baseUrl ?? runtimeConfig.baseUrl).replace(/\/$/, "");
|
|
128
|
+
const model = options.model ?? runtimeConfig.model;
|
|
129
|
+
const apiKey = options.apiKey ?? runtimeConfig.apiKey;
|
|
108
130
|
return {
|
|
109
|
-
adapterId: `openai-compatible:${
|
|
131
|
+
adapterId: `openai-compatible:${model}`,
|
|
110
132
|
kind: "direct-provider",
|
|
111
|
-
label: `OpenAI-compatible: ${
|
|
133
|
+
label: `OpenAI-compatible: ${model}`,
|
|
112
134
|
metadata: {
|
|
113
135
|
providerId: "openai-compatible",
|
|
114
|
-
model
|
|
136
|
+
model,
|
|
115
137
|
transport: "http",
|
|
116
138
|
capabilities: createAdapterCapabilities({
|
|
117
139
|
preflight: true,
|
|
@@ -121,7 +143,12 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
121
143
|
},
|
|
122
144
|
async execute(request) {
|
|
123
145
|
const prompt = buildPrompt(request);
|
|
124
|
-
const estimated = estimateCost(
|
|
146
|
+
const estimated = estimateCost(model, prompt.length, 2000);
|
|
147
|
+
const hasVerificationSteps = request.context.verificationPlan.length > 0 ||
|
|
148
|
+
(request.context.verificationStack?.length ?? 0) > 0;
|
|
149
|
+
const baselineChangedFiles = hasVerificationSteps
|
|
150
|
+
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
151
|
+
: new Set();
|
|
125
152
|
// Preflight: bail if projected cost exceeds remaining budget
|
|
126
153
|
if (request.context.remainingBudgetUsd > 0 &&
|
|
127
154
|
estimated.actualUsd > request.context.remainingBudgetUsd * 0.95) {
|
|
@@ -140,7 +167,7 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
140
167
|
};
|
|
141
168
|
}
|
|
142
169
|
// Call the OpenAI-compatible endpoint
|
|
143
|
-
const endpoint = `${
|
|
170
|
+
const endpoint = `${baseUrl}/v1/chat/completions`;
|
|
144
171
|
let responseText = "";
|
|
145
172
|
let tokensIn = estimated.tokensIn;
|
|
146
173
|
let tokensOut = 0;
|
|
@@ -148,10 +175,10 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
148
175
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
149
176
|
try {
|
|
150
177
|
const headers = { "Content-Type": "application/json" };
|
|
151
|
-
if (
|
|
152
|
-
headers["Authorization"] = `Bearer ${
|
|
178
|
+
if (apiKey)
|
|
179
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
153
180
|
// OpenRouter requires a site URL header for attribution
|
|
154
|
-
if (
|
|
181
|
+
if (baseUrl.includes("openrouter")) {
|
|
155
182
|
headers["HTTP-Referer"] = "https://martinloop.com";
|
|
156
183
|
headers["X-Title"] = "MartinLoop";
|
|
157
184
|
}
|
|
@@ -159,7 +186,7 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
159
186
|
method: "POST",
|
|
160
187
|
headers,
|
|
161
188
|
body: JSON.stringify({
|
|
162
|
-
model
|
|
189
|
+
model,
|
|
163
190
|
messages: [
|
|
164
191
|
{ role: "system", content: systemPrompt },
|
|
165
192
|
{ role: "user", content: prompt }
|
|
@@ -174,7 +201,7 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
174
201
|
const errMsg = body.error?.message ?? `HTTP ${res.status}`;
|
|
175
202
|
return {
|
|
176
203
|
status: "failed",
|
|
177
|
-
summary: `${
|
|
204
|
+
summary: `${model} API error: ${errMsg}`,
|
|
178
205
|
usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
|
|
179
206
|
verification: { passed: false, summary: "API call failed before verifier." },
|
|
180
207
|
failure: { message: errMsg, classHint: "infrastructure_error" }
|
|
@@ -191,7 +218,7 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
191
218
|
}
|
|
192
219
|
catch (error) {
|
|
193
220
|
const isAbort = error instanceof Error && error.name === "AbortError";
|
|
194
|
-
const message = isAbort ? `${
|
|
221
|
+
const message = isAbort ? `${model} request timed out after ${timeoutMs}ms` : String(error);
|
|
195
222
|
return {
|
|
196
223
|
status: "failed",
|
|
197
224
|
summary: message,
|
|
@@ -206,7 +233,7 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
206
233
|
if (!responseText.trim()) {
|
|
207
234
|
return {
|
|
208
235
|
status: "failed",
|
|
209
|
-
summary: `${
|
|
236
|
+
summary: `${model} returned an empty response.`,
|
|
210
237
|
usage: normalizeUsage({ actualUsd: 0, tokensIn, tokensOut: 0, provenance: "actual" }),
|
|
211
238
|
verification: { passed: false, summary: "Empty response — nothing to verify." },
|
|
212
239
|
failure: { message: "empty_response" }
|
|
@@ -214,8 +241,12 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
214
241
|
}
|
|
215
242
|
// Run verification
|
|
216
243
|
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack);
|
|
217
|
-
const execution =
|
|
218
|
-
|
|
244
|
+
const execution = {
|
|
245
|
+
changedFiles: hasVerificationSteps
|
|
246
|
+
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
247
|
+
: []
|
|
248
|
+
};
|
|
249
|
+
const pricing = KNOWN_MODEL_PRICING[model] ?? {
|
|
219
250
|
inputPer1K: FALLBACK_INPUT_PER_1K,
|
|
220
251
|
outputPer1K: FALLBACK_OUTPUT_PER_1K
|
|
221
252
|
};
|
|
@@ -223,8 +254,8 @@ export function createOpenAiCompatibleAdapter(options) {
|
|
|
223
254
|
return {
|
|
224
255
|
status: verification.passed ? "completed" : "failed",
|
|
225
256
|
summary: verification.passed
|
|
226
|
-
? `${
|
|
227
|
-
: `${
|
|
257
|
+
? `${model} completed the task. Verifier passed.`
|
|
258
|
+
: `${model} completed but verifier failed: ${verification.summary}`,
|
|
228
259
|
usage: normalizeUsage({
|
|
229
260
|
actualUsd,
|
|
230
261
|
tokensIn,
|
|
@@ -7,7 +7,10 @@ export declare function normalizeUsage(input: {
|
|
|
7
7
|
estimatedUsd?: number;
|
|
8
8
|
tokensIn?: number;
|
|
9
9
|
tokensOut?: number;
|
|
10
|
+
cachedInputTokens?: number;
|
|
11
|
+
reasoningTokensOut?: number;
|
|
10
12
|
provenance?: MartinAdapterResult["usage"]["provenance"];
|
|
13
|
+
providerSettlement?: MartinAdapterResult["usage"]["providerSettlement"];
|
|
11
14
|
}): MartinAdapterResult["usage"];
|
|
12
15
|
export declare function diffStatsFromNumstat(stdout: string): DiffStats | undefined;
|
|
13
16
|
export declare function normalizeStructuredErrors(errors: StructuredError[] | undefined): StructuredError[];
|
|
@@ -5,6 +5,7 @@ export function createAdapterCapabilities(overrides = {}) {
|
|
|
5
5
|
diffArtifacts: false,
|
|
6
6
|
structuredErrors: false,
|
|
7
7
|
cachingSignals: false,
|
|
8
|
+
workspaceMutations: true,
|
|
8
9
|
...overrides
|
|
9
10
|
};
|
|
10
11
|
}
|
|
@@ -21,7 +22,14 @@ export function normalizeUsage(input) {
|
|
|
21
22
|
: {}),
|
|
22
23
|
tokensIn: input.tokensIn ?? 0,
|
|
23
24
|
tokensOut: input.tokensOut ?? 0,
|
|
24
|
-
|
|
25
|
+
...(input.cachedInputTokens !== undefined
|
|
26
|
+
? { cachedInputTokens: input.cachedInputTokens }
|
|
27
|
+
: {}),
|
|
28
|
+
...(input.reasoningTokensOut !== undefined
|
|
29
|
+
? { reasoningTokensOut: input.reasoningTokensOut }
|
|
30
|
+
: {}),
|
|
31
|
+
provenance,
|
|
32
|
+
...(input.providerSettlement ? { providerSettlement: input.providerSettlement } : {})
|
|
25
33
|
};
|
|
26
34
|
}
|
|
27
35
|
export function diffStatsFromNumstat(stdout) {
|
|
@@ -4,6 +4,9 @@ export function createStubDirectProviderAdapter(options) {
|
|
|
4
4
|
providerId: options.providerId,
|
|
5
5
|
model: options.model,
|
|
6
6
|
label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
|
|
7
|
+
capabilities: {
|
|
8
|
+
workspaceMutations: false
|
|
9
|
+
},
|
|
7
10
|
responder: options.responder
|
|
8
11
|
});
|
|
9
12
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { MartinAdapter } from "../core/index.js";
|
|
2
|
+
import { type SpawnLike } from "./cli-bridge.js";
|
|
2
3
|
export interface VerifierOnlyAdapterOptions {
|
|
3
4
|
workingDirectory?: string;
|
|
4
5
|
verifyTimeoutMs?: number;
|
|
5
6
|
label?: string;
|
|
7
|
+
spawnImpl?: SpawnLike;
|
|
6
8
|
}
|
|
7
9
|
export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
|
|
2
2
|
import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
|
|
3
3
|
export function createVerifierOnlyAdapter(options = {}) {
|
|
4
4
|
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
@@ -17,9 +17,16 @@ export function createVerifierOnlyAdapter(options = {}) {
|
|
|
17
17
|
})
|
|
18
18
|
},
|
|
19
19
|
async execute(request) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
20
|
+
const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
|
|
21
|
+
(request.context.verificationStack?.length ?? 0) > 0;
|
|
22
|
+
const baselineChangedFiles = shouldTrackVerifierWrites
|
|
23
|
+
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
24
|
+
: new Set();
|
|
25
|
+
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl);
|
|
26
|
+
const changedFiles = shouldTrackVerifierWrites
|
|
27
|
+
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
28
|
+
: [];
|
|
29
|
+
const execution = { changedFiles };
|
|
23
30
|
if (verification.passed) {
|
|
24
31
|
return {
|
|
25
32
|
status: "completed",
|
|
@@ -49,6 +49,40 @@ export interface LoopCost {
|
|
|
49
49
|
avoidedUsd: number;
|
|
50
50
|
tokensIn: number;
|
|
51
51
|
tokensOut: number;
|
|
52
|
+
estimatedUsd?: number;
|
|
53
|
+
provenance?: CostProvenance;
|
|
54
|
+
providerSettlement?: ProviderUsageSettlement;
|
|
55
|
+
}
|
|
56
|
+
export type UsageSettlementSource = "claude_json" | "codex_jsonl" | "gemini_json" | "estimated_fallback" | "unavailable";
|
|
57
|
+
export interface ProviderUsageSettlement {
|
|
58
|
+
providerId: string;
|
|
59
|
+
model: string;
|
|
60
|
+
transport?: "cli" | "http" | "routed_http";
|
|
61
|
+
source: UsageSettlementSource;
|
|
62
|
+
inputTokens: number;
|
|
63
|
+
cachedInputTokens?: number;
|
|
64
|
+
outputTokens: number;
|
|
65
|
+
reasoningOutputTokens?: number;
|
|
66
|
+
rawUsageAvailable: boolean;
|
|
67
|
+
settledAt: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ReceiptScope {
|
|
70
|
+
repoRoot?: string;
|
|
71
|
+
workingDirectory?: string;
|
|
72
|
+
invocationRoot?: string;
|
|
73
|
+
runsRoot?: string;
|
|
74
|
+
}
|
|
75
|
+
export type ReceiptIntegrityState = "verified" | "unsigned" | "tamper_detected";
|
|
76
|
+
export interface ReceiptIntegritySummary {
|
|
77
|
+
state: ReceiptIntegrityState;
|
|
78
|
+
keyId?: string;
|
|
79
|
+
signedAt?: string;
|
|
80
|
+
loopRecordSha256?: string;
|
|
81
|
+
ledgerSha256?: string;
|
|
82
|
+
ledgerHeadHash?: string;
|
|
83
|
+
entryCount?: number;
|
|
84
|
+
reason?: string;
|
|
85
|
+
warnings?: string[];
|
|
52
86
|
}
|
|
53
87
|
export interface LoopArtifact {
|
|
54
88
|
artifactId: string;
|
|
@@ -90,6 +124,8 @@ export interface LoopRecord {
|
|
|
90
124
|
metadata: Record<string, string>;
|
|
91
125
|
createdAt: string;
|
|
92
126
|
updatedAt: string;
|
|
127
|
+
receiptScope?: ReceiptScope;
|
|
128
|
+
receiptIntegrity?: ReceiptIntegritySummary;
|
|
93
129
|
}
|
|
94
130
|
export interface LoopRecordDraft {
|
|
95
131
|
loopId?: string;
|
|
@@ -107,6 +143,8 @@ export interface LoopRecordDraft {
|
|
|
107
143
|
metadata?: Record<string, string>;
|
|
108
144
|
createdAt?: string;
|
|
109
145
|
updatedAt?: string;
|
|
146
|
+
receiptScope?: ReceiptScope;
|
|
147
|
+
receiptIntegrity?: ReceiptIntegritySummary;
|
|
110
148
|
}
|
|
111
149
|
export type { MartinErrorCategory, MartinOutputMode, MartinRunListFilters, MartinRunSelector } from "./operator.js";
|
|
112
150
|
export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
|
|
@@ -272,6 +310,7 @@ export interface BudgetSettlement {
|
|
|
272
310
|
usd: number;
|
|
273
311
|
provenance: CostProvenance;
|
|
274
312
|
};
|
|
313
|
+
providerSettlement?: ProviderUsageSettlement;
|
|
275
314
|
totalActualUsd: number;
|
|
276
315
|
preflightEstimateUsd: number;
|
|
277
316
|
varianceUsd: number;
|
|
@@ -36,6 +36,8 @@ export function createLoopRecord(draft, options = {}) {
|
|
|
36
36
|
},
|
|
37
37
|
createdAt: draft.createdAt ?? now,
|
|
38
38
|
updatedAt: draft.updatedAt ?? now,
|
|
39
|
+
...(draft.receiptScope ? { receiptScope: draft.receiptScope } : {}),
|
|
40
|
+
...(draft.receiptIntegrity ? { receiptIntegrity: draft.receiptIntegrity } : {}),
|
|
39
41
|
...(draft.teamId ? { teamId: draft.teamId } : {})
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -10,6 +10,23 @@ const POISON_PATTERNS = [
|
|
|
10
10
|
/\[system_override\]/i,
|
|
11
11
|
/\[authority_inversion\]/i
|
|
12
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* Identity-redefinition / persona-override patterns.
|
|
15
|
+
*
|
|
16
|
+
* These intentionally require an *override framing* (e.g. "now", "no longer",
|
|
17
|
+
* "forget", "pretend", or an explicit authority-role claim) rather than any
|
|
18
|
+
* sentence shaped like "you are X" / "I am X" — the latter matches ordinary
|
|
19
|
+
* benign text (e.g. "You are welcome to try MartinLoop") and produced
|
|
20
|
+
* false-positive hard aborts.
|
|
21
|
+
*/
|
|
22
|
+
const IDENTITY_REDEFINITION_PATTERNS = [
|
|
23
|
+
/\byou(?:'re|\s+are)\s+now\s+(?:a|an|the)\b(?!\s+(?:martin\s+loop|ai\s+coding\s+agent))/i,
|
|
24
|
+
/\byou(?:'re|\s+are)\s+no\s+longer\s+(?!.*\b(?:martin\s+loop|an?\s+ai)\b)/i,
|
|
25
|
+
/\bforget\s+(?:that\s+)?you(?:'re|\s+are)\s+martin\s+loop\b/i,
|
|
26
|
+
/\b(?:pretend|imagine)\s+(?:that\s+)?you(?:'re|\s+are)\b/i,
|
|
27
|
+
/\bact\s+as\s+(?:if\s+you(?:'re|\s+are)\s+)?(?:a|an)\s+(?:different|new|unrestricted|jailbroken)\b/i,
|
|
28
|
+
/\bi\s+am\s+(?:the|your)\s+(?:new\s+)?(?:system|developer|admin(?:istrator)?|root\s*user|owner|creator|operator)\b/i
|
|
29
|
+
];
|
|
13
30
|
/**
|
|
14
31
|
* T05: Context Poisoning Pre-gate.
|
|
15
32
|
* Scans untrusted input channels for authority inversion or instruction re-injection.
|
|
@@ -23,7 +40,12 @@ export async function runContextIntegrityPrecheck(runId, attemptIndex, artifacts
|
|
|
23
40
|
tools: Boolean(inputs.toolOutput),
|
|
24
41
|
history: Boolean(inputs.history)
|
|
25
42
|
};
|
|
26
|
-
const untrustedBuffer = [
|
|
43
|
+
const untrustedBuffer = [
|
|
44
|
+
inputs.userPrompt,
|
|
45
|
+
inputs.toolOutput,
|
|
46
|
+
inputs.retrievedContext,
|
|
47
|
+
inputs.history
|
|
48
|
+
]
|
|
27
49
|
.filter(Boolean)
|
|
28
50
|
.join("\n---\n");
|
|
29
51
|
for (const pattern of POISON_PATTERNS) {
|
|
@@ -31,8 +53,11 @@ export async function runContextIntegrityPrecheck(runId, attemptIndex, artifacts
|
|
|
31
53
|
signals.push(`Detected poison pattern: ${pattern.toString()}`);
|
|
32
54
|
}
|
|
33
55
|
}
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
for (const pattern of IDENTITY_REDEFINITION_PATTERNS) {
|
|
57
|
+
if (pattern.test(untrustedBuffer)) {
|
|
58
|
+
signals.push("Identity redefinition attempt detected.");
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
36
61
|
}
|
|
37
62
|
const verdict = signals.length > 0 ? "context_poisoning_block" : "clean";
|
|
38
63
|
const precheck = {
|
|
@@ -16,6 +16,7 @@ export interface RepoGroundingHit {
|
|
|
16
16
|
matchedTerms: string[];
|
|
17
17
|
symbols: string[];
|
|
18
18
|
}
|
|
19
|
+
export declare function resolveGroundingRoot(env?: NodeJS.ProcessEnv): string;
|
|
19
20
|
export declare function loadOrBuildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
|
|
20
21
|
export declare function buildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
|
|
21
22
|
export declare function queryRepoGroundingIndex(index: RepoGroundingIndex, query: string, limit?: number): RepoGroundingHit[];
|
|
@@ -12,6 +12,10 @@ const IGNORED_DIRS = new Set([
|
|
|
12
12
|
]);
|
|
13
13
|
const MAX_FILE_BYTES = 64_000;
|
|
14
14
|
const MAX_FILES = 500;
|
|
15
|
+
export function resolveGroundingRoot(env = process.env) {
|
|
16
|
+
return env["MARTIN_GROUNDING_DIR"]?.trim() ??
|
|
17
|
+
join(homedir(), ".martin", "grounding");
|
|
18
|
+
}
|
|
15
19
|
export async function loadOrBuildRepoGroundingIndex(repoRoot) {
|
|
16
20
|
const cachePath = getGroundingCachePath(repoRoot);
|
|
17
21
|
try {
|
|
@@ -23,7 +27,7 @@ export async function loadOrBuildRepoGroundingIndex(repoRoot) {
|
|
|
23
27
|
catch { }
|
|
24
28
|
const index = await buildRepoGroundingIndex(repoRoot);
|
|
25
29
|
try {
|
|
26
|
-
await mkdir(
|
|
30
|
+
await mkdir(resolveGroundingRoot(), { recursive: true });
|
|
27
31
|
await writeFile(cachePath, JSON.stringify(index, null, 2), "utf8");
|
|
28
32
|
}
|
|
29
33
|
catch {
|
|
@@ -79,7 +83,7 @@ export function queryRepoGroundingIndex(index, query, limit = 6) {
|
|
|
79
83
|
.slice(0, limit);
|
|
80
84
|
}
|
|
81
85
|
function getGroundingCachePath(repoRoot) {
|
|
82
|
-
return join(
|
|
86
|
+
return join(resolveGroundingRoot(), `${Buffer.from(repoRoot).toString("base64url")}.json`);
|
|
83
87
|
}
|
|
84
88
|
async function walk(repoRoot, currentDir, files, state) {
|
|
85
89
|
if (state.count >= MAX_FILES)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type MutationMode, type LoopRecord, type LoopTask } from "../contracts/index.js";
|
|
1
|
+
import { type ApprovalPolicy, 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";
|
|
@@ -13,8 +13,8 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
|
|
|
13
13
|
export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
|
|
14
14
|
export { compilePromptPacket } from "./compiler.js";
|
|
15
15
|
export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
|
|
16
|
-
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "./persistence/index.js";
|
|
17
|
-
export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, RunContract, RunStore } from "./persistence/index.js";
|
|
16
|
+
export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
17
|
+
export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
|
|
18
18
|
export { compileAndPersistContext } from "./persistence/index.js";
|
|
19
19
|
export type { CompileResult } from "./persistence/index.js";
|
|
20
20
|
export interface MartinAdapterRequest {
|
|
@@ -44,6 +44,20 @@ export interface MartinAdapterRequest {
|
|
|
44
44
|
};
|
|
45
45
|
previousAttempts: LoopAttempt[];
|
|
46
46
|
}
|
|
47
|
+
export interface MartinVerificationStep {
|
|
48
|
+
command: string;
|
|
49
|
+
launched: boolean;
|
|
50
|
+
exitCode?: number;
|
|
51
|
+
timedOut: boolean;
|
|
52
|
+
fastFail?: boolean;
|
|
53
|
+
detail?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface MartinVerificationOutcome {
|
|
56
|
+
passed: boolean;
|
|
57
|
+
summary: string;
|
|
58
|
+
steps?: MartinVerificationStep[];
|
|
59
|
+
warnings?: string[];
|
|
60
|
+
}
|
|
47
61
|
export interface MartinAdapterResult {
|
|
48
62
|
status: "completed" | "failed";
|
|
49
63
|
summary: string;
|
|
@@ -52,11 +66,16 @@ export interface MartinAdapterResult {
|
|
|
52
66
|
estimatedUsd?: number;
|
|
53
67
|
tokensIn: number;
|
|
54
68
|
tokensOut: number;
|
|
69
|
+
cachedInputTokens?: number;
|
|
70
|
+
reasoningTokensOut?: number;
|
|
55
71
|
provenance?: CostProvenance;
|
|
72
|
+
providerSettlement?: ProviderUsageSettlement;
|
|
56
73
|
};
|
|
57
74
|
verification: {
|
|
58
75
|
passed: boolean;
|
|
59
76
|
summary: string;
|
|
77
|
+
steps?: MartinVerificationStep[];
|
|
78
|
+
warnings?: string[];
|
|
60
79
|
};
|
|
61
80
|
execution?: {
|
|
62
81
|
changedFiles?: string[];
|
|
@@ -93,6 +112,7 @@ export interface MartinAdapter {
|
|
|
93
112
|
diffArtifacts?: boolean;
|
|
94
113
|
structuredErrors?: boolean;
|
|
95
114
|
cachingSignals?: boolean;
|
|
115
|
+
workspaceMutations?: boolean;
|
|
96
116
|
};
|
|
97
117
|
[key: string]: unknown;
|
|
98
118
|
};
|
|
@@ -135,6 +155,7 @@ export interface RunMartinInput {
|
|
|
135
155
|
maxRecentAttempts?: number;
|
|
136
156
|
fallbackModels?: string[];
|
|
137
157
|
fallbackAdapters?: MartinAdapter[];
|
|
158
|
+
receiptScope?: ReceiptScope;
|
|
138
159
|
/** Optional persistence store. When provided, runMartin writes artifacts on each lifecycle event. */
|
|
139
160
|
store?: RunStore;
|
|
140
161
|
}
|