@martinloop/mcp 0.1.4 → 0.2.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 +138 -135
- package/dist/discovery-metadata.d.ts +16 -0
- package/dist/discovery-metadata.js +62 -0
- package/dist/discovery-support.d.ts +62 -0
- package/dist/discovery-support.js +224 -0
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +3 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +455 -0
- package/dist/resources.d.ts +29 -0
- package/dist/resources.js +575 -0
- package/dist/server-validation.d.ts +2 -3
- package/dist/server-validation.js +295 -87
- package/dist/server.d.ts +76 -7
- package/dist/server.js +1135 -247
- package/dist/tools/doctor.js +14 -6
- package/dist/tools/get-attempt.d.ts +15 -0
- package/dist/tools/get-attempt.js +15 -0
- package/dist/tools/get-run.d.ts +24 -0
- package/dist/tools/get-run.js +23 -0
- package/dist/tools/get-status.d.ts +11 -0
- package/dist/tools/get-status.js +12 -2
- package/dist/tools/get-verification-results.d.ts +14 -0
- package/dist/tools/get-verification-results.js +14 -0
- package/dist/tools/inspect-loop.d.ts +9 -0
- package/dist/tools/inspect-loop.js +11 -2
- package/dist/tools/list-runs.d.ts +29 -0
- package/dist/tools/list-runs.js +24 -0
- package/dist/tools/preflight.js +7 -2
- package/dist/tools/run-dossier.d.ts +41 -0
- package/dist/tools/run-dossier.js +41 -0
- package/dist/tools/run-loop.d.ts +19 -0
- package/dist/tools/run-loop.js +41 -3
- package/dist/tools/run-store.d.ts +57 -3
- package/dist/tools/run-store.js +404 -53
- package/dist/tools/tool-errors.d.ts +37 -0
- package/dist/tools/tool-errors.js +170 -0
- package/dist/tools/tool-response.d.ts +16 -0
- package/dist/tools/tool-response.js +34 -0
- package/dist/tools/tool-support.d.ts +92 -2
- package/dist/tools/tool-support.js +358 -63
- package/dist/tools/triage-runs.d.ts +33 -0
- package/dist/tools/triage-runs.js +138 -0
- package/dist/vendor/adapters/claude-cli.js +0 -1
- package/dist/vendor/adapters/cli-bridge.js +0 -1
- package/dist/vendor/adapters/direct-provider.js +0 -1
- package/dist/vendor/adapters/index.js +0 -1
- package/dist/vendor/adapters/runtime-support.js +0 -1
- package/dist/vendor/adapters/stub-agent-cli.js +0 -1
- package/dist/vendor/adapters/stub-direct-provider.js +0 -1
- package/dist/vendor/adapters/verifier-only.js +0 -1
- package/dist/vendor/contracts/governance.js +0 -1
- package/dist/vendor/contracts/index.d.ts +2 -0
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/operator.d.ts +19 -0
- package/dist/vendor/contracts/operator.js +11 -0
- package/dist/vendor/core/compiler.js +0 -1
- package/dist/vendor/core/context-integrity.js +0 -1
- package/dist/vendor/core/grounding.js +0 -1
- package/dist/vendor/core/index.js +1 -2
- package/dist/vendor/core/leash.js +19 -12
- package/dist/vendor/core/persistence/compiler.js +0 -1
- package/dist/vendor/core/persistence/index.js +0 -1
- package/dist/vendor/core/persistence/ledger.js +0 -1
- package/dist/vendor/core/persistence/runs-reader.js +0 -1
- package/dist/vendor/core/persistence/store.js +0 -1
- package/dist/vendor/core/policy.js +0 -1
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +135 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
- package/dist/vendor/core/rollback.js +2 -3
- package/package.json +10 -3
- package/server.json +2 -2
package/dist/tools/run-store.js
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
import { stat } from "node:fs/promises";
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { resolveSafeLoopRecordPath, resolveSafeRunsPath, resolveSafeRunsRootPath } from "../server-validation.js";
|
|
3
|
+
import { readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "../vendor/core/index.js";
|
|
4
|
+
import { resolveSafeLoopRecordPath, resolveSafeRunsJsonPath, resolveSafeRunsPath, resolveSafeRunsRootPath } from "../server-validation.js";
|
|
5
|
+
import { attemptNotFoundError, invalidSelectorError, noLoopRecordsError, storeUnreadableError } from "./tool-errors.js";
|
|
5
6
|
export async function loadLoopRecordsForInspect(input) {
|
|
6
7
|
const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
|
|
7
8
|
if (!input.file) {
|
|
9
|
+
const inspected = await readAllLoopRecordsSafely(runsRoot);
|
|
8
10
|
return {
|
|
9
11
|
source: runsRoot,
|
|
10
|
-
loops:
|
|
12
|
+
loops: inspected.loops,
|
|
13
|
+
warnings: inspected.warnings
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
const targetPath = resolveSafeRunsPath(input.file, runsRoot);
|
|
14
|
-
const targetStats = await
|
|
15
|
-
if (targetStats
|
|
16
|
-
const canonicalLoopRecordPath =
|
|
17
|
-
|
|
18
|
-
const canonicalLoopRecordStats = await
|
|
19
|
-
if (canonicalLoopRecordStats
|
|
17
|
+
const targetStats = await safeStat(targetPath);
|
|
18
|
+
if (targetStats?.isDirectory()) {
|
|
19
|
+
const canonicalLoopRecordPath = resolveInspectableLoopRecordPath(targetPath, runsRoot);
|
|
20
|
+
if (canonicalLoopRecordPath) {
|
|
21
|
+
const canonicalLoopRecordStats = await safeStat(canonicalLoopRecordPath);
|
|
22
|
+
if (canonicalLoopRecordStats?.isFile()) {
|
|
20
23
|
return {
|
|
21
24
|
source: canonicalLoopRecordPath,
|
|
22
|
-
loops: await
|
|
25
|
+
loops: await readRecordsFromFile(canonicalLoopRecordPath),
|
|
26
|
+
warnings: []
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
|
-
|
|
27
|
-
// fall through to treating the directory as a full runs root
|
|
28
|
-
}
|
|
30
|
+
const inspected = await readAllLoopRecordsSafely(targetPath);
|
|
29
31
|
return {
|
|
30
32
|
source: targetPath,
|
|
31
|
-
loops:
|
|
33
|
+
loops: inspected.loops,
|
|
34
|
+
warnings: inspected.warnings
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
return {
|
|
35
38
|
source: targetPath,
|
|
36
|
-
loops: await
|
|
39
|
+
loops: await readRecordsFromFile(targetPath),
|
|
40
|
+
warnings: []
|
|
37
41
|
};
|
|
38
42
|
}
|
|
39
43
|
export async function loadLoopRecordForStatus(input) {
|
|
@@ -43,67 +47,414 @@ export async function loadLoopRecordForStatus(input) {
|
|
|
43
47
|
loop: JSON.parse(input.loopJson)
|
|
44
48
|
};
|
|
45
49
|
}
|
|
50
|
+
const detail = await loadDetailedLoopRecord(input);
|
|
51
|
+
return {
|
|
52
|
+
source: detail.source,
|
|
53
|
+
loop: detail.loop
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export async function listLoopRecords(input) {
|
|
57
|
+
const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
|
|
58
|
+
const inspected = await readAllLoopRecordsSafely(runsRoot);
|
|
59
|
+
const warnings = [...inspected.warnings];
|
|
60
|
+
const updatedAfterTimestamp = input.updatedAfter !== undefined ? new Date(input.updatedAfter).getTime() : undefined;
|
|
61
|
+
if (input.updatedAfter !== undefined &&
|
|
62
|
+
(!Number.isFinite(updatedAfterTimestamp) || Number.isNaN(updatedAfterTimestamp))) {
|
|
63
|
+
throw invalidSelectorError("Invalid updatedAfter.", "Provide updatedAfter as an ISO-8601 timestamp.");
|
|
64
|
+
}
|
|
65
|
+
const loops = inspected.loops
|
|
66
|
+
.filter((loop) => {
|
|
67
|
+
if (input.status && loop.status !== input.status) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (input.lifecycleState && loop.lifecycleState !== input.lifecycleState) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (input.adapterId && !loop.attempts.some((attempt) => attempt.adapterId === input.adapterId)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (input.model && !loop.attempts.some((attempt) => attempt.model === input.model)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (updatedAfterTimestamp !== undefined) {
|
|
80
|
+
const loopTimestamp = new Date(loop.updatedAt ?? loop.createdAt ?? 0).getTime();
|
|
81
|
+
if (!Number.isFinite(loopTimestamp) || loopTimestamp <= updatedAfterTimestamp) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
})
|
|
87
|
+
.sort((left, right) => timestampForLoop(right) - timestampForLoop(left))
|
|
88
|
+
.slice(0, input.limit ?? 20);
|
|
89
|
+
if (loops.length === 0) {
|
|
90
|
+
warnings.push("No loop records matched the current filters.");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
source: runsRoot,
|
|
94
|
+
runsRoot,
|
|
95
|
+
loops,
|
|
96
|
+
warnings
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export async function loadDetailedLoopRecord(input) {
|
|
46
100
|
const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
|
|
101
|
+
const selectors = [
|
|
102
|
+
input.file !== undefined ? "file" : null,
|
|
103
|
+
input.loopId !== undefined ? "loopId" : null,
|
|
104
|
+
input.latest ? "latest" : null
|
|
105
|
+
].filter((value) => value !== null);
|
|
106
|
+
if (selectors.length !== 1) {
|
|
107
|
+
throw invalidSelectorError("Provide exactly one of file, loopId, or latest.", "Choose exactly one run selector per call.");
|
|
108
|
+
}
|
|
47
109
|
if (input.file) {
|
|
48
110
|
const targetPath = resolveSafeRunsPath(input.file, runsRoot);
|
|
49
|
-
const targetStats = await
|
|
111
|
+
const targetStats = await safeStat(targetPath);
|
|
112
|
+
if (!targetStats) {
|
|
113
|
+
throw noLoopRecordsError();
|
|
114
|
+
}
|
|
50
115
|
if (targetStats.isDirectory()) {
|
|
51
|
-
const canonicalLoopRecordPath =
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
const loop = await
|
|
56
|
-
|
|
57
|
-
throw new Error("No loop records found.");
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
116
|
+
const canonicalLoopRecordPath = resolveInspectableLoopRecordPath(targetPath, runsRoot);
|
|
117
|
+
if (canonicalLoopRecordPath) {
|
|
118
|
+
const canonicalStats = await safeStat(canonicalLoopRecordPath);
|
|
119
|
+
if (canonicalStats?.isFile()) {
|
|
120
|
+
const loop = await readCanonicalLoopRecord(canonicalLoopRecordPath);
|
|
121
|
+
return buildDetailedLoopSource({
|
|
60
122
|
source: canonicalLoopRecordPath,
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
sourceKind: "file",
|
|
124
|
+
runsRoot,
|
|
125
|
+
loop,
|
|
126
|
+
canonicalLoopRecordPath,
|
|
127
|
+
canonicalRunDirectory: path.dirname(canonicalLoopRecordPath)
|
|
128
|
+
});
|
|
63
129
|
}
|
|
64
130
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const loop = await readLatestLoopRecord(targetPath);
|
|
131
|
+
const inspected = await readAllLoopRecordsSafely(targetPath);
|
|
132
|
+
const loop = inspected.loops[0];
|
|
69
133
|
if (!loop) {
|
|
70
|
-
throw
|
|
134
|
+
throw noLoopRecordsError();
|
|
71
135
|
}
|
|
72
|
-
|
|
136
|
+
const detail = await buildDetailedLoopSourceFromDiscoveredLoop({
|
|
73
137
|
source: targetPath,
|
|
138
|
+
sourceKind: "runs_root",
|
|
139
|
+
runsRoot,
|
|
74
140
|
loop
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
...detail,
|
|
144
|
+
warnings: [...detail.warnings, ...inspected.warnings]
|
|
75
145
|
};
|
|
76
146
|
}
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
79
|
-
throw
|
|
147
|
+
const latest = await readLatestLoopRecordFromFile(targetPath);
|
|
148
|
+
if (!latest) {
|
|
149
|
+
throw noLoopRecordsError();
|
|
80
150
|
}
|
|
81
|
-
|
|
151
|
+
if (path.basename(targetPath) === "loop-record.json") {
|
|
152
|
+
const loop = await readCanonicalLoopRecord(targetPath);
|
|
153
|
+
return buildDetailedLoopSource({
|
|
154
|
+
source: targetPath,
|
|
155
|
+
sourceKind: "file",
|
|
156
|
+
runsRoot,
|
|
157
|
+
loop,
|
|
158
|
+
canonicalLoopRecordPath: targetPath,
|
|
159
|
+
canonicalRunDirectory: path.dirname(targetPath)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return await buildDetailedLoopSourceFromDiscoveredLoop({
|
|
82
163
|
source: targetPath,
|
|
83
|
-
|
|
84
|
-
|
|
164
|
+
sourceKind: "file",
|
|
165
|
+
runsRoot,
|
|
166
|
+
loop: latest
|
|
167
|
+
});
|
|
85
168
|
}
|
|
86
169
|
if (input.loopId) {
|
|
87
|
-
const
|
|
88
|
-
const
|
|
170
|
+
const canonicalLoopRecordPath = resolvePotentialLoopRecordPath(input.loopId, runsRoot);
|
|
171
|
+
const canonicalStats = await safeStat(canonicalLoopRecordPath);
|
|
172
|
+
if (canonicalStats?.isFile()) {
|
|
173
|
+
const loop = await readCanonicalLoopRecord(canonicalLoopRecordPath);
|
|
174
|
+
return buildDetailedLoopSource({
|
|
175
|
+
source: canonicalLoopRecordPath,
|
|
176
|
+
sourceKind: "loop_id",
|
|
177
|
+
runsRoot,
|
|
178
|
+
loop,
|
|
179
|
+
canonicalLoopRecordPath,
|
|
180
|
+
canonicalRunDirectory: path.dirname(canonicalLoopRecordPath)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const inspected = await readAllLoopRecordsSafely(runsRoot);
|
|
184
|
+
const loop = inspected.loops.find((candidate) => candidate.loopId === input.loopId);
|
|
89
185
|
if (!loop) {
|
|
90
|
-
throw
|
|
186
|
+
throw noLoopRecordsError();
|
|
91
187
|
}
|
|
92
|
-
|
|
93
|
-
source:
|
|
188
|
+
const detail = await buildDetailedLoopSourceFromDiscoveredLoop({
|
|
189
|
+
source: runsRoot,
|
|
190
|
+
sourceKind: "loop_id",
|
|
191
|
+
runsRoot,
|
|
94
192
|
loop
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
...detail,
|
|
196
|
+
warnings: [...detail.warnings, ...inspected.warnings]
|
|
95
197
|
};
|
|
96
198
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
199
|
+
const inspected = await readAllLoopRecordsSafely(runsRoot);
|
|
200
|
+
const loop = inspected.loops[0];
|
|
201
|
+
if (!loop) {
|
|
202
|
+
throw noLoopRecordsError();
|
|
203
|
+
}
|
|
204
|
+
const detail = await buildDetailedLoopSourceFromDiscoveredLoop({
|
|
205
|
+
source: runsRoot,
|
|
206
|
+
sourceKind: "latest",
|
|
207
|
+
runsRoot,
|
|
208
|
+
loop
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
...detail,
|
|
212
|
+
warnings: [...detail.warnings, ...inspected.warnings]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export async function loadAttemptFromLoop(input) {
|
|
216
|
+
const detail = await loadDetailedLoopRecord(input);
|
|
217
|
+
const attempt = input.attemptIndex !== undefined
|
|
218
|
+
? detail.loop.attempts.find((candidate) => candidate.index === input.attemptIndex)
|
|
219
|
+
: detail.loop.attempts.at(-1);
|
|
220
|
+
if (!attempt) {
|
|
221
|
+
throw attemptNotFoundError(input.attemptIndex ?? detail.loop.attempts.length);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
detail,
|
|
225
|
+
attempt
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export async function readLedgerEvents(detail) {
|
|
229
|
+
const requestedLedgerPath = detail.ledgerPath ??
|
|
230
|
+
(detail.canonicalRunDirectory ? path.join(detail.canonicalRunDirectory, "ledger.jsonl") : undefined);
|
|
231
|
+
if (!requestedLedgerPath) {
|
|
232
|
+
return withLedgerDiagnostics([]);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const ledgerPath = resolveInspectableLedgerPath(requestedLedgerPath, detail.runsRoot);
|
|
236
|
+
const ledgerStats = await safeStat(ledgerPath);
|
|
237
|
+
if (!ledgerStats?.isFile()) {
|
|
238
|
+
return withLedgerDiagnostics([], { ledgerPath });
|
|
101
239
|
}
|
|
240
|
+
const text = await readFile(ledgerPath, "utf8");
|
|
241
|
+
const events = text
|
|
242
|
+
.split(/\r?\n/u)
|
|
243
|
+
.map((line) => line.trim())
|
|
244
|
+
.filter(Boolean)
|
|
245
|
+
.map((line) => JSON.parse(line));
|
|
246
|
+
return withLedgerDiagnostics(events, { ledgerPath });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return withLedgerDiagnostics([], {
|
|
250
|
+
unreadable: true,
|
|
251
|
+
warnings: [
|
|
252
|
+
`Verification ledger for '${detail.loop.loopId}' is unreadable; ledger verification evidence is unavailable.`
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export async function readAttemptArtifactFiles(detail, attemptIndex) {
|
|
258
|
+
if (!detail.canonicalRunDirectory) {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
const directory = path.join(detail.canonicalRunDirectory, "artifacts", `attempt-${String(attemptIndex).padStart(3, "0")}`);
|
|
262
|
+
try {
|
|
263
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
264
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export async function readAllLoopRecordsSafely(runsRoot) {
|
|
271
|
+
let entries;
|
|
272
|
+
try {
|
|
273
|
+
entries = await readdir(runsRoot, { withFileTypes: true });
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
102
276
|
return {
|
|
103
|
-
|
|
104
|
-
|
|
277
|
+
loops: [],
|
|
278
|
+
warnings: [
|
|
279
|
+
"Configured Martin runs root is missing or unreadable; no loop records could be inspected."
|
|
280
|
+
]
|
|
105
281
|
};
|
|
106
282
|
}
|
|
107
|
-
|
|
283
|
+
const loops = [];
|
|
284
|
+
const warnings = [];
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
if (entry.isDirectory()) {
|
|
287
|
+
const canonicalLoopRecordPath = resolveInspectableLoopRecordPath(path.join(runsRoot, entry.name), runsRoot);
|
|
288
|
+
if (!canonicalLoopRecordPath) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const canonicalStats = await safeStat(canonicalLoopRecordPath);
|
|
292
|
+
if (!canonicalStats?.isFile()) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
loops.push(await readCanonicalLoopRecord(canonicalLoopRecordPath));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
warnings.push(`Skipped unreadable loop record for '${entry.name}'.`);
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (!entry.isFile()) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const extension = path.extname(entry.name).toLowerCase();
|
|
307
|
+
if (extension !== ".json" && extension !== ".jsonl") {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const filePath = path.join(runsRoot, entry.name);
|
|
311
|
+
try {
|
|
312
|
+
loops.push(...(await readRecordsFromFile(resolveSafeRunsJsonPath(entry.name, runsRoot))));
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
warnings.push(`Skipped unreadable run file '${entry.name}'.`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
loops.sort((left, right) => timestampForLoop(right) - timestampForLoop(left));
|
|
319
|
+
return {
|
|
320
|
+
loops,
|
|
321
|
+
warnings
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
async function buildDetailedLoopSourceFromDiscoveredLoop(input) {
|
|
325
|
+
let canonicalLoopRecordPath;
|
|
326
|
+
try {
|
|
327
|
+
canonicalLoopRecordPath = resolveSafeLoopRecordPath(input.loop.loopId, input.runsRoot);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
canonicalLoopRecordPath = undefined;
|
|
331
|
+
}
|
|
332
|
+
const canonicalStats = canonicalLoopRecordPath ? await safeStat(canonicalLoopRecordPath) : undefined;
|
|
333
|
+
if (canonicalLoopRecordPath && canonicalStats?.isFile()) {
|
|
334
|
+
const canonicalLoop = await readCanonicalLoopRecord(canonicalLoopRecordPath);
|
|
335
|
+
return buildDetailedLoopSource({
|
|
336
|
+
source: input.source,
|
|
337
|
+
sourceKind: input.sourceKind,
|
|
338
|
+
runsRoot: input.runsRoot,
|
|
339
|
+
loop: canonicalLoop,
|
|
340
|
+
canonicalLoopRecordPath,
|
|
341
|
+
canonicalRunDirectory: path.dirname(canonicalLoopRecordPath)
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return buildDetailedLoopSource({
|
|
345
|
+
source: input.source,
|
|
346
|
+
sourceKind: input.sourceKind,
|
|
347
|
+
runsRoot: input.runsRoot,
|
|
348
|
+
loop: input.loop,
|
|
349
|
+
warnings: [
|
|
350
|
+
`Canonical run directory for ${input.loop.loopId} is not available; dossier data may be partial.`
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function buildDetailedLoopSource(input) {
|
|
355
|
+
return {
|
|
356
|
+
source: input.source,
|
|
357
|
+
sourceKind: input.sourceKind,
|
|
358
|
+
runsRoot: input.runsRoot,
|
|
359
|
+
loop: input.loop,
|
|
360
|
+
warnings: input.warnings ?? [],
|
|
361
|
+
...(input.canonicalRunDirectory ? { canonicalRunDirectory: input.canonicalRunDirectory } : {}),
|
|
362
|
+
...(input.canonicalLoopRecordPath ? { canonicalLoopRecordPath: input.canonicalLoopRecordPath } : {}),
|
|
363
|
+
...(input.canonicalRunDirectory
|
|
364
|
+
? { ledgerPath: path.join(input.canonicalRunDirectory, "ledger.jsonl") }
|
|
365
|
+
: {})
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function readCanonicalLoopRecord(file) {
|
|
369
|
+
try {
|
|
370
|
+
const text = await readFile(file, "utf8");
|
|
371
|
+
return validateInspectableLoopRecord(JSON.parse(text));
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
throw storeUnreadableError();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function readRecordsFromFile(file) {
|
|
378
|
+
try {
|
|
379
|
+
return (await readLoopRecordsFromFile(file)).map((loop) => validateInspectableLoopRecord(loop));
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
throw storeUnreadableError();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function safeStat(targetPath) {
|
|
386
|
+
try {
|
|
387
|
+
return await stat(targetPath);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function resolveInspectableLoopRecordPath(runDirectory, runsRoot) {
|
|
394
|
+
try {
|
|
395
|
+
const relativeLoopRecordPath = path.relative(runsRoot, path.join(runDirectory, "loop-record.json"));
|
|
396
|
+
return resolveSafeRunsJsonPath(relativeLoopRecordPath, runsRoot);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function resolvePotentialLoopRecordPath(loopId, runsRoot) {
|
|
403
|
+
try {
|
|
404
|
+
return resolveSafeLoopRecordPath(loopId, runsRoot);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
if (!/^[A-Za-z0-9._-]+$/u.test(loopId)) {
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
return path.join(runsRoot, loopId, "loop-record.json");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function resolveInspectableLedgerPath(ledgerPath, runsRoot) {
|
|
414
|
+
const relativeLedgerPath = path.relative(runsRoot, ledgerPath);
|
|
415
|
+
return resolveSafeRunsJsonPath(relativeLedgerPath, runsRoot);
|
|
416
|
+
}
|
|
417
|
+
function timestampForLoop(loop) {
|
|
418
|
+
const timestamp = new Date(loop.updatedAt ?? loop.createdAt ?? 0).getTime();
|
|
419
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
420
|
+
}
|
|
421
|
+
function withLedgerDiagnostics(events, diagnostics = {}) {
|
|
422
|
+
return Object.assign(events, diagnostics);
|
|
423
|
+
}
|
|
424
|
+
function validateInspectableLoopRecord(value) {
|
|
425
|
+
if (!isRecord(value)) {
|
|
426
|
+
throw storeUnreadableError();
|
|
427
|
+
}
|
|
428
|
+
const budget = value["budget"];
|
|
429
|
+
const cost = value["cost"];
|
|
430
|
+
const attempts = value["attempts"];
|
|
431
|
+
if (typeof value["loopId"] !== "string" ||
|
|
432
|
+
!/^[A-Za-z0-9._-]+$/u.test(value["loopId"]) ||
|
|
433
|
+
typeof value["status"] !== "string" ||
|
|
434
|
+
typeof value["lifecycleState"] !== "string" ||
|
|
435
|
+
!isRecord(budget) ||
|
|
436
|
+
!isFiniteNumber(budget["maxUsd"]) ||
|
|
437
|
+
!isFiniteNumber(budget["softLimitUsd"]) ||
|
|
438
|
+
!Number.isInteger(budget["maxIterations"]) ||
|
|
439
|
+
!Number.isInteger(budget["maxTokens"]) ||
|
|
440
|
+
!isRecord(cost) ||
|
|
441
|
+
!isFiniteNumber(cost["actualUsd"]) ||
|
|
442
|
+
!isFiniteNumber(cost["tokensIn"]) ||
|
|
443
|
+
!isFiniteNumber(cost["tokensOut"]) ||
|
|
444
|
+
(cost["avoidedUsd"] !== undefined && !isFiniteNumber(cost["avoidedUsd"])) ||
|
|
445
|
+
!Array.isArray(attempts) ||
|
|
446
|
+
!attempts.every(isRecord) ||
|
|
447
|
+
(value["events"] !== undefined &&
|
|
448
|
+
(!Array.isArray(value["events"]) || !value["events"].every(isRecord))) ||
|
|
449
|
+
(value["artifacts"] !== undefined &&
|
|
450
|
+
(!Array.isArray(value["artifacts"]) || !value["artifacts"].every(isRecord)))) {
|
|
451
|
+
throw storeUnreadableError();
|
|
452
|
+
}
|
|
453
|
+
return value;
|
|
454
|
+
}
|
|
455
|
+
function isRecord(value) {
|
|
456
|
+
return typeof value === "object" && value !== null;
|
|
457
|
+
}
|
|
458
|
+
function isFiniteNumber(value) {
|
|
459
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
108
460
|
}
|
|
109
|
-
//# sourceMappingURL=run-store.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MartinErrorCategory } from "../vendor/contracts/index.js";
|
|
2
|
+
export type ToolFailureCode = "attempt_not_found" | "engine_unavailable" | "invalid_arguments" | "invalid_json" | "invalid_path" | "invalid_selector" | "no_loop_records" | "store_unreadable" | "tool_execution_failed" | "unknown_tool" | "unsupported_operation";
|
|
3
|
+
export type ToolFailureCategory = MartinErrorCategory;
|
|
4
|
+
export interface ToolFailure {
|
|
5
|
+
code: ToolFailureCode;
|
|
6
|
+
category: ToolFailureCategory;
|
|
7
|
+
message: string;
|
|
8
|
+
suggestion?: string;
|
|
9
|
+
retryable: boolean;
|
|
10
|
+
details?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface MartinToolErrorOptions {
|
|
13
|
+
category?: ToolFailureCategory;
|
|
14
|
+
suggestion?: string;
|
|
15
|
+
retryable?: boolean;
|
|
16
|
+
exposeMessage?: boolean;
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export declare class MartinToolError extends Error {
|
|
20
|
+
readonly code: ToolFailureCode;
|
|
21
|
+
readonly category: ToolFailureCategory;
|
|
22
|
+
readonly suggestion?: string;
|
|
23
|
+
readonly retryable: boolean;
|
|
24
|
+
readonly exposeMessage: boolean;
|
|
25
|
+
readonly details?: Record<string, unknown>;
|
|
26
|
+
constructor(code: ToolFailureCode, message: string, options?: MartinToolErrorOptions);
|
|
27
|
+
}
|
|
28
|
+
export declare function sanitizePotentiallySensitiveMessage(message: string): string;
|
|
29
|
+
export declare function sanitizeToolErrorMessage(error: unknown): string;
|
|
30
|
+
export declare function toToolFailure(error: unknown): ToolFailure;
|
|
31
|
+
export declare function invalidArgumentsError(message: string, suggestion?: string): MartinToolError;
|
|
32
|
+
export declare function invalidPathError(message: string, suggestion?: string): MartinToolError;
|
|
33
|
+
export declare function invalidSelectorError(message: string, suggestion?: string): MartinToolError;
|
|
34
|
+
export declare function noLoopRecordsError(message?: string): MartinToolError;
|
|
35
|
+
export declare function attemptNotFoundError(attemptIndex: number): MartinToolError;
|
|
36
|
+
export declare function storeUnreadableError(message?: string): MartinToolError;
|
|
37
|
+
export declare function unsupportedOperationError(message: string, suggestion?: string): MartinToolError;
|