prismalens 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -13
- package/dist/bin/prismalens.js +39 -50
- package/dist/chunk-2Z7QZSIS.js +20 -0
- package/dist/chunk-5BT3XQOG.js +2990 -0
- package/dist/chunk-6N4PHBII.js +30 -0
- package/dist/chunk-CTKCK7QT.js +387 -0
- package/dist/chunk-CVSS5JPC.js +130 -0
- package/dist/chunk-HPE5CTO4.js +22 -0
- package/dist/chunk-PTBCAI34.js +34 -0
- package/dist/chunk-PVQVFRIV.js +452 -0
- package/dist/chunk-QDK5PAI3.js +155 -0
- package/dist/chunk-SSEIMBQ2.js +204 -0
- package/dist/chunk-TGP6QTFO.js +156 -0
- package/dist/chunk-V6M6ISKI.js +161 -0
- package/dist/chunk-XH6DIKTA.js +83 -0
- package/dist/src/cli/auth.js +112 -101
- package/dist/src/cli/doctor.js +184 -187
- package/dist/src/cli/flags.js +6 -29
- package/dist/src/cli/grouping.js +16 -227
- package/dist/src/cli/init.js +58 -52
- package/dist/src/cli/investigate.js +23 -465
- package/dist/src/cli/listen.js +550 -286
- package/dist/src/cli/report.js +70 -67
- package/dist/src/cli/serve.js +260 -30
- package/dist/src/cli/status.js +87 -86
- package/package.json +16 -7
- package/dist/bin/prismalens.d.ts +0 -3
- package/dist/bin/prismalens.d.ts.map +0 -1
- package/dist/bin/prismalens.js.map +0 -1
- package/dist/src/cli/auth.d.ts +0 -3
- package/dist/src/cli/auth.d.ts.map +0 -1
- package/dist/src/cli/auth.js.map +0 -1
- package/dist/src/cli/doctor.d.ts +0 -18
- package/dist/src/cli/doctor.d.ts.map +0 -1
- package/dist/src/cli/doctor.js.map +0 -1
- package/dist/src/cli/flags.d.ts +0 -2
- package/dist/src/cli/flags.d.ts.map +0 -1
- package/dist/src/cli/flags.js.map +0 -1
- package/dist/src/cli/grouping.d.ts +0 -18
- package/dist/src/cli/grouping.d.ts.map +0 -1
- package/dist/src/cli/grouping.js.map +0 -1
- package/dist/src/cli/init.d.ts +0 -3
- package/dist/src/cli/init.d.ts.map +0 -1
- package/dist/src/cli/init.js.map +0 -1
- package/dist/src/cli/investigate.d.ts +0 -115
- package/dist/src/cli/investigate.d.ts.map +0 -1
- package/dist/src/cli/investigate.js.map +0 -1
- package/dist/src/cli/listen.d.ts +0 -56
- package/dist/src/cli/listen.d.ts.map +0 -1
- package/dist/src/cli/listen.js.map +0 -1
- package/dist/src/cli/report.d.ts +0 -24
- package/dist/src/cli/report.d.ts.map +0 -1
- package/dist/src/cli/report.js.map +0 -1
- package/dist/src/cli/serve.d.ts +0 -3
- package/dist/src/cli/serve.d.ts.map +0 -1
- package/dist/src/cli/serve.js.map +0 -1
- package/dist/src/cli/status.d.ts +0 -19
- package/dist/src/cli/status.d.ts.map +0 -1
- package/dist/src/cli/status.js.map +0 -1
- package/dist/src/config/loader.d.ts +0 -22
- package/dist/src/config/loader.d.ts.map +0 -1
- package/dist/src/config/loader.js +0 -193
- package/dist/src/config/loader.js.map +0 -1
- package/dist/src/config/schema.d.ts +0 -463
- package/dist/src/config/schema.d.ts.map +0 -1
- package/dist/src/config/schema.js +0 -217
- package/dist/src/config/schema.js.map +0 -1
- package/dist/src/core/caps.d.ts +0 -36
- package/dist/src/core/caps.d.ts.map +0 -1
- package/dist/src/core/caps.js +0 -36
- package/dist/src/core/caps.js.map +0 -1
- package/dist/src/core/db.d.ts +0 -3
- package/dist/src/core/db.d.ts.map +0 -1
- package/dist/src/core/db.js +0 -105
- package/dist/src/core/db.js.map +0 -1
- package/dist/src/core/detect-repo.d.ts +0 -4
- package/dist/src/core/detect-repo.d.ts.map +0 -1
- package/dist/src/core/detect-repo.js +0 -28
- package/dist/src/core/detect-repo.js.map +0 -1
- package/dist/src/core/file-session-store.d.ts +0 -10
- package/dist/src/core/file-session-store.d.ts.map +0 -1
- package/dist/src/core/file-session-store.js +0 -34
- package/dist/src/core/file-session-store.js.map +0 -1
- package/dist/src/core/parse-stdin.d.ts +0 -2
- package/dist/src/core/parse-stdin.d.ts.map +0 -1
- package/dist/src/core/parse-stdin.js +0 -21
- package/dist/src/core/parse-stdin.js.map +0 -1
- package/dist/src/core/run-investigation.d.ts +0 -54
- package/dist/src/core/run-investigation.d.ts.map +0 -1
- package/dist/src/core/run-investigation.js +0 -200
- package/dist/src/core/run-investigation.js.map +0 -1
- package/dist/src/core/session.d.ts +0 -63
- package/dist/src/core/session.d.ts.map +0 -1
- package/dist/src/core/session.js +0 -45
- package/dist/src/core/session.js.map +0 -1
- package/dist/src/core/slack-notify.d.ts +0 -9
- package/dist/src/core/slack-notify.d.ts.map +0 -1
- package/dist/src/core/slack-notify.js +0 -57
- package/dist/src/core/slack-notify.js.map +0 -1
- package/dist/src/core/sqlite-session-store.d.ts +0 -39
- package/dist/src/core/sqlite-session-store.d.ts.map +0 -1
- package/dist/src/core/sqlite-session-store.js +0 -239
- package/dist/src/core/sqlite-session-store.js.map +0 -1
- package/dist/src/http/server.d.ts +0 -31
- package/dist/src/http/server.d.ts.map +0 -1
- package/dist/src/http/server.js +0 -164
- package/dist/src/http/server.js.map +0 -1
- package/dist/src/jsonrpc/server.d.ts +0 -15
- package/dist/src/jsonrpc/server.d.ts.map +0 -1
- package/dist/src/jsonrpc/server.js +0 -244
- package/dist/src/jsonrpc/server.js.map +0 -1
- package/dist/src/jsonrpc/types.d.ts +0 -12
- package/dist/src/jsonrpc/types.d.ts.map +0 -1
- package/dist/src/jsonrpc/types.js +0 -4
- package/dist/src/jsonrpc/types.js.map +0 -1
- package/dist/src/version.d.ts +0 -8
- package/dist/src/version.d.ts.map +0 -1
- package/dist/src/version.js +0 -27
- package/dist/src/version.js.map +0 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig
|
|
3
|
+
} from "./chunk-V6M6ISKI.js";
|
|
4
|
+
import {
|
|
5
|
+
createSessionManager
|
|
6
|
+
} from "./chunk-CTKCK7QT.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveInvestigation
|
|
9
|
+
} from "./chunk-QDK5PAI3.js";
|
|
10
|
+
import {
|
|
11
|
+
assertKnownFlags
|
|
12
|
+
} from "./chunk-PTBCAI34.js";
|
|
13
|
+
import {
|
|
14
|
+
HARNESS_REGISTRY,
|
|
15
|
+
SANDBOX_MODES,
|
|
16
|
+
conductRun,
|
|
17
|
+
resolveSandbox
|
|
18
|
+
} from "./chunk-5BT3XQOG.js";
|
|
19
|
+
import {
|
|
20
|
+
INVESTIGATION_DEFAULTS
|
|
21
|
+
} from "./chunk-6N4PHBII.js";
|
|
22
|
+
|
|
23
|
+
// src/cli/investigate.ts
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { writeFile } from "node:fs/promises";
|
|
26
|
+
import { join, resolve } from "node:path";
|
|
27
|
+
import { defineCommand } from "citty";
|
|
28
|
+
import consola from "consola";
|
|
29
|
+
|
|
30
|
+
// src/core/detect-repo.ts
|
|
31
|
+
import { execFile } from "node:child_process";
|
|
32
|
+
import { promisify } from "node:util";
|
|
33
|
+
var execFileAsync = promisify(execFile);
|
|
34
|
+
var REPO_PATTERN = /(?:github\.com|gitlab\.com|bitbucket\.org)[/:](.+?)(?:\.git)?$/;
|
|
35
|
+
async function detectRepo(cwd) {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await execFileAsync(
|
|
38
|
+
"git",
|
|
39
|
+
["remote", "get-url", "origin"],
|
|
40
|
+
{ cwd }
|
|
41
|
+
);
|
|
42
|
+
return stdout.trim().match(REPO_PATTERN)?.[1];
|
|
43
|
+
} catch {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function resolveRepoSlug(configRepo, cwd = process.cwd()) {
|
|
48
|
+
return configRepo ?? await detectRepo(cwd);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/core/file-session-store.ts
|
|
52
|
+
function createFileSessionStore(sessions, meta) {
|
|
53
|
+
let session;
|
|
54
|
+
const store = {
|
|
55
|
+
async create() {
|
|
56
|
+
session = await sessions.create(meta);
|
|
57
|
+
},
|
|
58
|
+
async append(event) {
|
|
59
|
+
await sessions.appendEvent(meta.runId, event);
|
|
60
|
+
},
|
|
61
|
+
async finish(report) {
|
|
62
|
+
await sessions.writeReport(meta.runId, report);
|
|
63
|
+
await sessions.update(meta.runId, {
|
|
64
|
+
status: "done",
|
|
65
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
async fail(error) {
|
|
69
|
+
await sessions.update(meta.runId, { status: "errored", error });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
store,
|
|
74
|
+
workspacePath() {
|
|
75
|
+
if (!session) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"workspacePath() called before store.create() resolved"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return session.workspacePath;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/core/parse-stdin.ts
|
|
86
|
+
async function parseStdin() {
|
|
87
|
+
if (process.stdin.isTTY) return void 0;
|
|
88
|
+
const chunks = [];
|
|
89
|
+
for await (const chunk of process.stdin) {
|
|
90
|
+
chunks.push(chunk);
|
|
91
|
+
}
|
|
92
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
93
|
+
if (!raw) return void 0;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(raw);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
throw new Error(`invalid FiringAlert JSON on stdin: ${msg}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/cli/investigate.ts
|
|
103
|
+
var investigate_default = defineCommand({
|
|
104
|
+
meta: {
|
|
105
|
+
name: "investigate",
|
|
106
|
+
description: "Run a root-cause investigation of a firing alert (two-tier engine, ADR-0008).\n\nExamples:\n $ pl investigate -q 'Why did the database crash?' --max-turns 30"
|
|
107
|
+
},
|
|
108
|
+
args: {
|
|
109
|
+
repo: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "Path to the repository the harness investigates (its cwd). Defaults to the current directory."
|
|
112
|
+
},
|
|
113
|
+
query: {
|
|
114
|
+
type: "string",
|
|
115
|
+
alias: "q",
|
|
116
|
+
description: "Investigate a synthesized alert from this one-line description (alternative to piping a FiringAlert JSON on stdin)."
|
|
117
|
+
},
|
|
118
|
+
config: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Path to a prismalens.config.yaml (skips the upward config search)."
|
|
121
|
+
},
|
|
122
|
+
model: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Override agent.model \u2014 a bare model id, e.g. gpt-oss:120b."
|
|
125
|
+
},
|
|
126
|
+
harness: {
|
|
127
|
+
type: "enum",
|
|
128
|
+
options: ["deepagents", "claude-code", "codex"],
|
|
129
|
+
description: "Tier-2 harness backend. Defaults to agent.default in config."
|
|
130
|
+
},
|
|
131
|
+
mode: {
|
|
132
|
+
type: "enum",
|
|
133
|
+
options: ["read-only", "supervised", "auto", "dangerous"],
|
|
134
|
+
description: "Permission posture the harness runs under (ADR-0017). Defaults to agent.permissions.mode in config (read-only)."
|
|
135
|
+
},
|
|
136
|
+
dangerouslySkipPermissions: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
default: false,
|
|
139
|
+
description: "Alias for --mode dangerous."
|
|
140
|
+
},
|
|
141
|
+
sandbox: {
|
|
142
|
+
type: "enum",
|
|
143
|
+
options: [...SANDBOX_MODES],
|
|
144
|
+
description: "Isolation boundary the harness runs in (ADR-0020): process (cooperative floor), srt (enforced), or auto. Defaults to agent.sandbox in config (auto \u2014 srt when its egress bridge is healthy, else the process floor)."
|
|
145
|
+
},
|
|
146
|
+
service: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Select the service context by name from `services` in config (overrides the alert's service label)."
|
|
149
|
+
},
|
|
150
|
+
json: {
|
|
151
|
+
type: "boolean",
|
|
152
|
+
default: false,
|
|
153
|
+
description: "Print the InvestigationReport as JSON to stdout (suppresses the human renderer)."
|
|
154
|
+
},
|
|
155
|
+
output: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Write the InvestigationReport JSON to this file."
|
|
158
|
+
},
|
|
159
|
+
quiet: {
|
|
160
|
+
type: "boolean",
|
|
161
|
+
default: false,
|
|
162
|
+
description: "Suppress progress + the human renderer (errors still go to stderr)."
|
|
163
|
+
},
|
|
164
|
+
"max-turns": {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Per-run turn ceiling for the default harness."
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
async run({ args, cmd }) {
|
|
170
|
+
try {
|
|
171
|
+
assertKnownFlags(args, cmd);
|
|
172
|
+
const json = Boolean(args.json);
|
|
173
|
+
const quiet = Boolean(args.quiet) || json;
|
|
174
|
+
const info = (msg) => {
|
|
175
|
+
if (!quiet) consola.info(msg);
|
|
176
|
+
};
|
|
177
|
+
const success = (msg) => {
|
|
178
|
+
if (!quiet) consola.success(msg);
|
|
179
|
+
};
|
|
180
|
+
const line = (msg) => {
|
|
181
|
+
if (!quiet) consola.log(msg);
|
|
182
|
+
};
|
|
183
|
+
const piped = await parseStdin();
|
|
184
|
+
const cwd = args.repo ? resolve(args.repo) : process.cwd();
|
|
185
|
+
const config = await loadConfig({
|
|
186
|
+
...args.config ? { configPath: args.config } : {},
|
|
187
|
+
cwd,
|
|
188
|
+
cliOverrides: { ...args.model ? { model: args.model } : {} }
|
|
189
|
+
});
|
|
190
|
+
const maxTurns = args["max-turns"] ? Number(args["max-turns"]) : config.agent.max_turns;
|
|
191
|
+
const repoSlug = await resolveRepoSlug(config.repo, cwd);
|
|
192
|
+
const permissionMode = args.dangerouslySkipPermissions ? "dangerous" : args.mode;
|
|
193
|
+
const harnessName = args.harness ?? config.agent.default;
|
|
194
|
+
const sandboxMode = args.sandbox ?? config.agent.sandbox;
|
|
195
|
+
let selection = null;
|
|
196
|
+
try {
|
|
197
|
+
const resolvedSandbox = await resolveRunSandbox(
|
|
198
|
+
harnessName,
|
|
199
|
+
sandboxMode,
|
|
200
|
+
config
|
|
201
|
+
);
|
|
202
|
+
selection = resolvedSandbox.selection;
|
|
203
|
+
if (selection && resolvedSandbox.degradeReason) {
|
|
204
|
+
if (resolvedSandbox.degradeExpected) {
|
|
205
|
+
info(
|
|
206
|
+
`Sandbox: ${selection.actual} (${resolvedSandbox.degradeReason}).`
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
consola.warn(
|
|
210
|
+
`Sandbox 'auto' degraded to the ${selection.actual} (cooperative, not an OS boundary): ${resolvedSandbox.degradeReason}.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
} else if (selection?.actual === "srt") {
|
|
214
|
+
info("Sandbox: srt (enforced OS boundary).");
|
|
215
|
+
}
|
|
216
|
+
const resolved = resolveInvestigation(
|
|
217
|
+
{
|
|
218
|
+
...piped ? { alert: piped } : {},
|
|
219
|
+
...args.query ? { query: args.query } : {},
|
|
220
|
+
...args.repo ? { repo: args.repo } : {},
|
|
221
|
+
...args.harness ? { harness: args.harness } : {},
|
|
222
|
+
...args.service ? { service: args.service } : {},
|
|
223
|
+
...permissionMode ? { permissionMode } : {},
|
|
224
|
+
...selection ? {
|
|
225
|
+
sandbox: selection.sandbox,
|
|
226
|
+
requestedSandbox: selection.requested
|
|
227
|
+
} : {},
|
|
228
|
+
...maxTurns ? { maxTurns } : {}
|
|
229
|
+
},
|
|
230
|
+
config
|
|
231
|
+
);
|
|
232
|
+
const { context, harness, synth, fidelity, maxBranches } = resolved;
|
|
233
|
+
const primaryAlert = context.alerts[0];
|
|
234
|
+
if (!synth.configured) {
|
|
235
|
+
info(
|
|
236
|
+
"No Tier-1 provider configured (checked env + _FILE for: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, GROQ_API_KEY, OLLAMA_API_KEY) \u2014 reports will be RAW harness pass-through (un-synthesized). This is supported."
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const runId = randomUUID();
|
|
240
|
+
const sessions = createSessionManager(config.workspace.dir);
|
|
241
|
+
const fileSession = createFileSessionStore(sessions, {
|
|
242
|
+
runId,
|
|
243
|
+
alertname: primaryAlert.alertname,
|
|
244
|
+
agent: harnessName,
|
|
245
|
+
...repoSlug ? { repo: repoSlug } : {}
|
|
246
|
+
});
|
|
247
|
+
info(
|
|
248
|
+
`Investigating "${primaryAlert.alertname}" with ${harnessName} in ${cwd} (run ${runId})`
|
|
249
|
+
);
|
|
250
|
+
const outcome = await conductRun(
|
|
251
|
+
{
|
|
252
|
+
context,
|
|
253
|
+
harness,
|
|
254
|
+
synth,
|
|
255
|
+
fidelity,
|
|
256
|
+
runId,
|
|
257
|
+
...maxBranches !== void 0 ? { maxBranches } : {}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
sink: (event) => {
|
|
261
|
+
const entry = liveTimelineEntry(event);
|
|
262
|
+
if (entry) line(entry);
|
|
263
|
+
},
|
|
264
|
+
store: fileSession.store
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
if (!outcome.report) {
|
|
268
|
+
consola.error(outcome.error);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const report = outcome.report;
|
|
273
|
+
if (args.output) {
|
|
274
|
+
const outPath = resolve(args.output);
|
|
275
|
+
await writeFile(
|
|
276
|
+
outPath,
|
|
277
|
+
`${JSON.stringify(report, null, 2)}
|
|
278
|
+
`,
|
|
279
|
+
"utf-8"
|
|
280
|
+
);
|
|
281
|
+
success(`Report written to ${outPath}`);
|
|
282
|
+
}
|
|
283
|
+
if (json) {
|
|
284
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
285
|
+
`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!quiet) {
|
|
289
|
+
renderReport(report);
|
|
290
|
+
success(
|
|
291
|
+
`Report saved to ${join(fileSession.workspacePath(), "report.json")}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
} finally {
|
|
295
|
+
if (selection) await selection.sandbox.destroy();
|
|
296
|
+
}
|
|
297
|
+
} catch (err) {
|
|
298
|
+
consola.error(err instanceof Error ? err.message : String(err));
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
function collectAllowedDomains(config) {
|
|
304
|
+
const llmBaseUrl = process.env.OLLAMA_BASE_URL ?? process.env.OPENAI_BASE_URL ?? INVESTIGATION_DEFAULTS.synth.baseURL;
|
|
305
|
+
const urls = [
|
|
306
|
+
llmBaseUrl,
|
|
307
|
+
config.telemetry.prometheus_url ?? INVESTIGATION_DEFAULTS.telemetry.prometheusUrl,
|
|
308
|
+
config.telemetry.alertmanager_url ?? INVESTIGATION_DEFAULTS.telemetry.alertmanagerUrl,
|
|
309
|
+
config.telemetry.api_url ?? INVESTIGATION_DEFAULTS.telemetry.apiUrl,
|
|
310
|
+
config.logs.url
|
|
311
|
+
];
|
|
312
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
313
|
+
for (const url of urls) {
|
|
314
|
+
if (!url) continue;
|
|
315
|
+
try {
|
|
316
|
+
hosts.add(new URL(url).hostname);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return [...hosts];
|
|
321
|
+
}
|
|
322
|
+
async function resolveRunSandbox(harness, mode, config) {
|
|
323
|
+
const guard = resolveSandboxGuard(harness, mode);
|
|
324
|
+
if (guard.blocked) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Harness "${harness}" cannot run inside an enforced sandbox (${mode}) yet \u2014 it is not spawned as a child process. Use the auto or process sandbox mode (no enforced boundary), or an ACP harness (deepagents).`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (!guard.takesSandbox)
|
|
330
|
+
return { selection: null, degradeReason: null, degradeExpected: false };
|
|
331
|
+
const probeUrl = firstProbeUrl(config);
|
|
332
|
+
const selection = await resolveSandbox(mode, {
|
|
333
|
+
allowedDomains: collectAllowedDomains(config),
|
|
334
|
+
...probeUrl ? { probeUrl } : {}
|
|
335
|
+
});
|
|
336
|
+
const degradeReason = mode === "auto" && selection.actual !== "srt" ? selection.degradeReason ?? "srt unavailable" : null;
|
|
337
|
+
return {
|
|
338
|
+
selection,
|
|
339
|
+
degradeReason,
|
|
340
|
+
degradeExpected: selection.degradeExpected ?? false
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function firstProbeUrl(config) {
|
|
344
|
+
const urls = [
|
|
345
|
+
config.telemetry.prometheus_url,
|
|
346
|
+
config.telemetry.alertmanager_url,
|
|
347
|
+
config.telemetry.api_url,
|
|
348
|
+
config.logs.url
|
|
349
|
+
];
|
|
350
|
+
for (const url of urls) {
|
|
351
|
+
if (!url) continue;
|
|
352
|
+
try {
|
|
353
|
+
return new URL(url).href;
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
function resolveSandboxGuard(harness, mode) {
|
|
360
|
+
const takesSandbox = HARNESS_REGISTRY[harness]?.transport === "acp";
|
|
361
|
+
const demandsEnforcedBoundary = mode === "srt" || mode === "e2b";
|
|
362
|
+
return { takesSandbox, blocked: !takesSandbox && demandsEnforcedBoundary };
|
|
363
|
+
}
|
|
364
|
+
function liveTimelineEntry(event) {
|
|
365
|
+
switch (event.kind) {
|
|
366
|
+
case "agent_step": {
|
|
367
|
+
const firstLine = event.text.trim().split("\n", 1)[0]?.trim() ?? "";
|
|
368
|
+
const n = event.toolCalls.length;
|
|
369
|
+
if (!firstLine && n === 0) return null;
|
|
370
|
+
const calls = n > 0 ? ` + ${n} tool call${n === 1 ? "" : "s"}` : "";
|
|
371
|
+
return `\u2022 ${firstLine}${calls}`;
|
|
372
|
+
}
|
|
373
|
+
case "tool_result":
|
|
374
|
+
return ` ${event.result.ok ? "ok" : "ERR"} ${event.result.source}`;
|
|
375
|
+
case "branch_done":
|
|
376
|
+
return ` [branch ${event.reason}]`;
|
|
377
|
+
case "error":
|
|
378
|
+
return ` [error] ${event.message}`;
|
|
379
|
+
default:
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
var HYPOTHESIS_MARK = {
|
|
384
|
+
confirmed: "[confirmed]",
|
|
385
|
+
supported: "[supported]",
|
|
386
|
+
speculative: "[speculative]",
|
|
387
|
+
refuted: "[refuted]"
|
|
388
|
+
};
|
|
389
|
+
function renderReport(report) {
|
|
390
|
+
consola.log("");
|
|
391
|
+
consola.log("=== Investigation Report ===");
|
|
392
|
+
consola.log(report.summary);
|
|
393
|
+
consola.log("");
|
|
394
|
+
if (report.rootCause) {
|
|
395
|
+
const cat = report.rootCauseCategory ? ` [${report.rootCauseCategory}]` : "";
|
|
396
|
+
consola.log(`Root cause${cat}:`);
|
|
397
|
+
consola.log(` ${report.rootCause}`);
|
|
398
|
+
} else {
|
|
399
|
+
consola.log("Root cause: not determined");
|
|
400
|
+
}
|
|
401
|
+
if (report.hypotheses.length > 0) {
|
|
402
|
+
consola.log("");
|
|
403
|
+
consola.log("Hypotheses (most -> least plausible):");
|
|
404
|
+
report.hypotheses.forEach((h, i) => {
|
|
405
|
+
renderHypothesis(h, i + 1);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (report.ruledOut.length > 0) {
|
|
409
|
+
consola.log("");
|
|
410
|
+
consola.log("Ruled out:");
|
|
411
|
+
for (const r of report.ruledOut) renderRuledOut(r);
|
|
412
|
+
}
|
|
413
|
+
const { queried, notQueried } = report.coverage;
|
|
414
|
+
if (queried.length > 0 || notQueried.length > 0) {
|
|
415
|
+
consola.log("");
|
|
416
|
+
consola.log(
|
|
417
|
+
`Coverage: ${queried.length} source(s) queried, ${notQueried.length} not queried.`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (report.nextSteps.length > 0) {
|
|
421
|
+
consola.log("");
|
|
422
|
+
consola.log("Next steps:");
|
|
423
|
+
for (const s of report.nextSteps) renderNextStep(s);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function renderHypothesis(h, n) {
|
|
427
|
+
consola.log(` ${n}. ${HYPOTHESIS_MARK[h.status]} ${h.statement}`);
|
|
428
|
+
for (const e of h.evidence) {
|
|
429
|
+
const sign = e.direction === "supports" ? "+" : "-";
|
|
430
|
+
consola.log(` ${sign} (${e.status}) ${e.observation} [${e.source}]`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function renderRuledOut(r) {
|
|
434
|
+
consola.log(` x ${r.statement} \u2014 ${r.why}`);
|
|
435
|
+
for (const e of r.evidence) {
|
|
436
|
+
consola.log(` - (${e.status}) ${e.observation} [${e.source}]`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function renderNextStep(s) {
|
|
440
|
+
const prio = s.priority ? `[${s.priority}] ` : "";
|
|
441
|
+
consola.log(` * ${prio}${s.title}: ${s.detail}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export {
|
|
445
|
+
resolveRepoSlug,
|
|
446
|
+
createFileSessionStore,
|
|
447
|
+
investigate_default,
|
|
448
|
+
collectAllowedDomains,
|
|
449
|
+
resolveRunSandbox,
|
|
450
|
+
resolveSandboxGuard,
|
|
451
|
+
liveTimelineEntry
|
|
452
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureV1,
|
|
3
|
+
resolveCredentials
|
|
4
|
+
} from "./chunk-CVSS5JPC.js";
|
|
5
|
+
import {
|
|
6
|
+
coerceFiringAlert,
|
|
7
|
+
resolveInvestigation
|
|
8
|
+
} from "./chunk-5BT3XQOG.js";
|
|
9
|
+
import {
|
|
10
|
+
INVESTIGATION_DEFAULTS
|
|
11
|
+
} from "./chunk-6N4PHBII.js";
|
|
12
|
+
import {
|
|
13
|
+
AUTO_SELECT_PROVIDER_IDS,
|
|
14
|
+
getDefaultModel
|
|
15
|
+
} from "./chunk-XH6DIKTA.js";
|
|
16
|
+
|
|
17
|
+
// src/core/run-investigation.ts
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
function isSynthConfigured(config) {
|
|
20
|
+
if (config.synth.provider) {
|
|
21
|
+
return resolveCredentials(config.synth.provider, config.synth.base_url).source !== "none";
|
|
22
|
+
}
|
|
23
|
+
for (const id of AUTO_SELECT_PROVIDER_IDS) {
|
|
24
|
+
if (resolveCredentials(id).source !== "none") {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function resolveInvestigation2(args, config) {
|
|
31
|
+
const cwd = args.repo ? resolve(args.repo) : process.cwd();
|
|
32
|
+
let providerId = config.synth.provider;
|
|
33
|
+
let creds;
|
|
34
|
+
if (providerId) {
|
|
35
|
+
creds = resolveCredentials(providerId, config.synth.base_url);
|
|
36
|
+
if (providerId === "custom" && !creds.baseURL) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Custom LLM provider requires synth.base_url to be configured."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
for (const id of AUTO_SELECT_PROVIDER_IDS) {
|
|
43
|
+
const candidate = resolveCredentials(id);
|
|
44
|
+
if (candidate.source !== "none") {
|
|
45
|
+
providerId = id;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (providerId) {
|
|
50
|
+
creds = resolveCredentials(
|
|
51
|
+
providerId,
|
|
52
|
+
config.synth.base_url
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
creds = { providerId: "ollama", source: "none" };
|
|
56
|
+
providerId = "ollama";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const synthModel = config.synth.model ?? getDefaultModel(providerId) ?? "";
|
|
60
|
+
if (!synthModel && creds.source !== "none") {
|
|
61
|
+
throw new Error("No synthesis model configured (set synth.model).");
|
|
62
|
+
}
|
|
63
|
+
const service = selectService(args, config);
|
|
64
|
+
const repos = config.repo ? [config.repo] : Object.keys(config.repos).length > 0 ? Object.keys(config.repos) : void 0;
|
|
65
|
+
const logs = config.logs.url ? {
|
|
66
|
+
...config.logs.kind ? { kind: config.logs.kind } : {},
|
|
67
|
+
url: config.logs.url
|
|
68
|
+
} : void 0;
|
|
69
|
+
const harnessName = args.harness ?? config.agent.default;
|
|
70
|
+
const permissionMode = args.permissionMode ?? config.agent.permissions.mode;
|
|
71
|
+
const harnessNative = config.harnesses[harnessName]?.native;
|
|
72
|
+
const limits = toSandboxLimits(config.agent.limits);
|
|
73
|
+
const telemetry = {
|
|
74
|
+
prometheusUrl: config.telemetry.prometheus_url ?? INVESTIGATION_DEFAULTS.telemetry.prometheusUrl,
|
|
75
|
+
alertmanagerUrl: config.telemetry.alertmanager_url ?? INVESTIGATION_DEFAULTS.telemetry.alertmanagerUrl,
|
|
76
|
+
apiUrl: config.telemetry.api_url ?? INVESTIGATION_DEFAULTS.telemetry.apiUrl
|
|
77
|
+
};
|
|
78
|
+
const request = {
|
|
79
|
+
query: args.query,
|
|
80
|
+
harness: harnessName,
|
|
81
|
+
permissionMode,
|
|
82
|
+
...harnessNative ? { harnessNative } : {},
|
|
83
|
+
...args.sandbox ? { sandbox: args.sandbox } : {},
|
|
84
|
+
...args.requestedSandbox ? { requestedSandbox: args.requestedSandbox } : {},
|
|
85
|
+
...limits ? { limits } : {},
|
|
86
|
+
// Per-alert fan-out cap (ADR-0016 decision 2): wire `agent.max_branches` when
|
|
87
|
+
// set; unset leaves the engine default (mirrors the `limits` thread-through).
|
|
88
|
+
...config.agent.max_branches ? { maxBranches: config.agent.max_branches } : {},
|
|
89
|
+
...args.maxTurns ? { maxTurns: args.maxTurns } : {},
|
|
90
|
+
...args.isolateSettings ? { isolateSettings: args.isolateSettings } : {},
|
|
91
|
+
model: config.agent.model,
|
|
92
|
+
cwd,
|
|
93
|
+
synth: {
|
|
94
|
+
providerId,
|
|
95
|
+
model: synthModel,
|
|
96
|
+
apiKey: creds.apiKey ?? "",
|
|
97
|
+
baseURL: creds.baseURL,
|
|
98
|
+
configured: creds.source !== "none"
|
|
99
|
+
},
|
|
100
|
+
harnessEnv: {
|
|
101
|
+
OPENAI_API_KEY: process.env.OLLAMA_API_KEY ?? process.env.OPENAI_API_KEY,
|
|
102
|
+
OPENAI_BASE_URL: ensureV1(
|
|
103
|
+
process.env.OLLAMA_BASE_URL ?? process.env.OPENAI_BASE_URL ?? INVESTIGATION_DEFAULTS.synth.baseURL
|
|
104
|
+
)
|
|
105
|
+
},
|
|
106
|
+
initTimeoutMs: INVESTIGATION_DEFAULTS.harnessInitTimeoutMs
|
|
107
|
+
};
|
|
108
|
+
if (args.alerts && args.alerts.length > 0) {
|
|
109
|
+
request.context = {
|
|
110
|
+
alerts: args.alerts.map(coerceFiringAlert),
|
|
111
|
+
telemetry,
|
|
112
|
+
...service ? { service } : {},
|
|
113
|
+
...repos ? { repos } : {},
|
|
114
|
+
...logs ? { logs } : {}
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
117
|
+
request.alert = args.alert;
|
|
118
|
+
request.telemetry = telemetry;
|
|
119
|
+
if (service) request.service = service;
|
|
120
|
+
if (repos) request.repos = repos;
|
|
121
|
+
if (logs) request.logs = logs;
|
|
122
|
+
}
|
|
123
|
+
return resolveInvestigation(request);
|
|
124
|
+
}
|
|
125
|
+
function toSandboxLimits(limits) {
|
|
126
|
+
const out = {
|
|
127
|
+
...limits.wall_clock_ms ? { wallClockMs: limits.wall_clock_ms } : {},
|
|
128
|
+
...limits.memory_mb ? { memoryMb: limits.memory_mb } : {},
|
|
129
|
+
...limits.cpu_cores ? { cpuCores: limits.cpu_cores } : {}
|
|
130
|
+
};
|
|
131
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
132
|
+
}
|
|
133
|
+
function selectService(args, config) {
|
|
134
|
+
const name = args.service ?? pickServiceLabel(args.alerts?.[0] ?? args.alert);
|
|
135
|
+
if (!name) return void 0;
|
|
136
|
+
const cfg = config.services[name];
|
|
137
|
+
if (!cfg) return void 0;
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
...cfg.tier ? { tier: cfg.tier } : {},
|
|
141
|
+
...cfg.repo ? { repo: cfg.repo } : {},
|
|
142
|
+
...cfg.depends_on.length > 0 ? { dependsOn: cfg.depends_on } : {}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function pickServiceLabel(alert) {
|
|
146
|
+
const labels = alert?.labels ?? {};
|
|
147
|
+
const pick = labels.service ?? labels.namespace ?? labels.job;
|
|
148
|
+
return typeof pick === "string" && pick ? pick : void 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
isSynthConfigured,
|
|
153
|
+
resolveInvestigation2 as resolveInvestigation,
|
|
154
|
+
pickServiceLabel
|
|
155
|
+
};
|