@kinetica/admin-agent 0.1.2 → 0.2.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 +83 -24
- package/dist/admin-agent.js +1838 -284
- package/knowledge/references/bundle/support-bundle.md +40 -0
- package/knowledge/references/version-quirks-7.2.md +7 -2
- package/package.json +1 -1
package/dist/admin-agent.js
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/cli/index.ts
|
|
32
32
|
var cli_exports = {};
|
|
33
33
|
__export(cli_exports, {
|
|
34
|
+
chooseBundleSessionVersion: () => chooseBundleSessionVersion,
|
|
34
35
|
getSession: () => getSession,
|
|
35
36
|
main: () => main,
|
|
36
37
|
verbose: () => verbose
|
|
@@ -84,11 +85,11 @@ function dimRgb(color, factor) {
|
|
|
84
85
|
}
|
|
85
86
|
var SHADOW_CHARS = /* @__PURE__ */ new Set(["\u2557", "\u2554", "\u2551", "\u255D", "\u255A", "\u2550"]);
|
|
86
87
|
var DIM_FACTOR = 0.4;
|
|
87
|
-
function gradientize(
|
|
88
|
+
function gradientize(text2) {
|
|
88
89
|
const PURPLE = [147, 51, 234];
|
|
89
90
|
const HOT_PINK = [236, 72, 153];
|
|
90
91
|
const RESET = "\x1B[0m";
|
|
91
|
-
const lines =
|
|
92
|
+
const lines = text2.split("\n");
|
|
92
93
|
const maxIdx = Math.max(lines.length - 1, 1);
|
|
93
94
|
return lines.map((line, i) => {
|
|
94
95
|
const bright = lerpRgb(PURPLE, HOT_PINK, i / maxIdx);
|
|
@@ -111,17 +112,17 @@ function printBanner(model) {
|
|
|
111
112
|
const version = getVersion();
|
|
112
113
|
const subtitle = `admin-agent ${import_picocolors.default.dim(`v${version}`)}`;
|
|
113
114
|
const header = model ? `${subtitle}
|
|
114
|
-
${import_picocolors.default.dim(`
|
|
115
|
+
${import_picocolors.default.dim(`Model: ${model}`)}` : subtitle;
|
|
115
116
|
process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
|
|
116
117
|
return subtitle;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// src/cli/select-model.ts
|
|
120
|
-
var
|
|
121
|
+
var import_prompts6 = require("@inquirer/prompts");
|
|
121
122
|
|
|
122
123
|
// src/agent/run-agent.ts
|
|
123
|
-
var
|
|
124
|
-
var
|
|
124
|
+
var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
|
|
125
|
+
var import_prompts5 = require("@inquirer/prompts");
|
|
125
126
|
var import_picocolors8 = __toESM(require("picocolors"));
|
|
126
127
|
|
|
127
128
|
// src/agent/diagnostic-sql.ts
|
|
@@ -556,7 +557,7 @@ function formatTableArray(rows) {
|
|
|
556
557
|
const colWidths = headers.map(
|
|
557
558
|
(h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
|
|
558
559
|
);
|
|
559
|
-
const pad = (
|
|
560
|
+
const pad = (text2, col) => text2.padEnd(colWidths[col]);
|
|
560
561
|
const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
|
|
561
562
|
const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
|
|
562
563
|
const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
|
|
@@ -585,13 +586,13 @@ var DEFAULT_TRUNCATION = {
|
|
|
585
586
|
};
|
|
586
587
|
|
|
587
588
|
// src/output/truncate.ts
|
|
588
|
-
function truncateOutput(
|
|
589
|
-
if (
|
|
590
|
-
const lines =
|
|
589
|
+
function truncateOutput(text2, options = DEFAULT_TRUNCATION) {
|
|
590
|
+
if (text2 === "") return "";
|
|
591
|
+
const lines = text2.split("\n");
|
|
591
592
|
const { headLines, tailLines } = options;
|
|
592
593
|
const threshold = headLines + tailLines;
|
|
593
594
|
if (lines.length <= threshold) {
|
|
594
|
-
return
|
|
595
|
+
return text2;
|
|
595
596
|
}
|
|
596
597
|
const truncatedCount = lines.length - headLines - tailLines;
|
|
597
598
|
const head = lines.slice(0, headLines);
|
|
@@ -1221,7 +1222,7 @@ var DEFAULT_SCRUB_PATTERNS = [
|
|
|
1221
1222
|
];
|
|
1222
1223
|
function scrubCredentials(content, patterns = DEFAULT_SCRUB_PATTERNS) {
|
|
1223
1224
|
const configRedacted = redactConfigSecrets(content);
|
|
1224
|
-
return patterns.reduce((
|
|
1225
|
+
return patterns.reduce((text2, pattern) => text2.replace(pattern, "[REDACTED]"), configRedacted);
|
|
1225
1226
|
}
|
|
1226
1227
|
|
|
1227
1228
|
// src/tools/rest/show-configuration.ts
|
|
@@ -2714,7 +2715,7 @@ function fingerprint(value) {
|
|
|
2714
2715
|
}
|
|
2715
2716
|
function scrubCredentialPatterns(value) {
|
|
2716
2717
|
return CREDENTIAL_PATTERNS.reduce(
|
|
2717
|
-
(
|
|
2718
|
+
(text2, { regex, replacement }) => text2.replace(regex, replacement),
|
|
2718
2719
|
value
|
|
2719
2720
|
);
|
|
2720
2721
|
}
|
|
@@ -2769,15 +2770,13 @@ var DIAGNOSTIC_TOOL_NAMES = [
|
|
|
2769
2770
|
"kinetica_resource_objects",
|
|
2770
2771
|
"kinetica_host_manager_status"
|
|
2771
2772
|
];
|
|
2772
|
-
function createDiagnosticRegistry() {
|
|
2773
|
-
return DIAGNOSTIC_TOOL_NAMES.reduce(
|
|
2774
|
-
(registry, name) => registry.registerReadOnlyTool(name),
|
|
2775
|
-
createRegistry()
|
|
2776
|
-
);
|
|
2777
|
-
}
|
|
2778
2773
|
function applyOutputPipeline(result) {
|
|
2779
2774
|
const payload = result.ok ? result.data : result;
|
|
2780
|
-
|
|
2775
|
+
const body = formatOutput(payload);
|
|
2776
|
+
const withNote = result.ok && result.note ? `${result.note}
|
|
2777
|
+
|
|
2778
|
+
${body}` : body;
|
|
2779
|
+
return truncateOutput(withNote);
|
|
2781
2780
|
}
|
|
2782
2781
|
function logMutationAudit(toolName, result, input5) {
|
|
2783
2782
|
const statusLabel = result.ok ? import_picocolors4.default.bold(import_picocolors4.default.green("EXECUTED")) : import_picocolors4.default.bold(import_picocolors4.default.red("FAILED"));
|
|
@@ -3191,6 +3190,26 @@ function buildEvidenceChecklist() {
|
|
|
3191
3190
|
].join("\n");
|
|
3192
3191
|
}
|
|
3193
3192
|
|
|
3193
|
+
// src/agent/prompt-sections.ts
|
|
3194
|
+
function buildFailurePatternsSection(playbooks) {
|
|
3195
|
+
if (!playbooks || playbooks.length === 0) return "";
|
|
3196
|
+
const entries = playbooks.map((p) => `**${p.title}:**
|
|
3197
|
+
|
|
3198
|
+
${p.body}`).join("\n\n");
|
|
3199
|
+
return `### Common Failure Patterns
|
|
3200
|
+
|
|
3201
|
+
${entries}`;
|
|
3202
|
+
}
|
|
3203
|
+
function buildReferenceSection(references) {
|
|
3204
|
+
if (!references || references.length === 0) return "";
|
|
3205
|
+
const entries = references.map((r) => `**${r.title}:**
|
|
3206
|
+
|
|
3207
|
+
${r.body}`).join("\n\n");
|
|
3208
|
+
return `### Reference Knowledge
|
|
3209
|
+
|
|
3210
|
+
${entries}`;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3194
3213
|
// src/agent/report-template.ts
|
|
3195
3214
|
var import_node_fs2 = require("fs");
|
|
3196
3215
|
var import_node_path2 = require("path");
|
|
@@ -3301,25 +3320,7 @@ ${sqls.join("\n\n")}
|
|
|
3301
3320
|
}
|
|
3302
3321
|
return result.trimEnd();
|
|
3303
3322
|
}
|
|
3304
|
-
function
|
|
3305
|
-
if (!playbooks || playbooks.length === 0) return "";
|
|
3306
|
-
const entries = playbooks.map((p) => `**${p.title}:**
|
|
3307
|
-
|
|
3308
|
-
${p.body}`).join("\n\n");
|
|
3309
|
-
return `### Common Failure Patterns
|
|
3310
|
-
|
|
3311
|
-
${entries}`;
|
|
3312
|
-
}
|
|
3313
|
-
function buildReferenceSection(references) {
|
|
3314
|
-
if (!references || references.length === 0) return "";
|
|
3315
|
-
const entries = references.map((r) => `**${r.title}:**
|
|
3316
|
-
|
|
3317
|
-
${r.body}`).join("\n\n");
|
|
3318
|
-
return `### Reference Knowledge
|
|
3319
|
-
|
|
3320
|
-
${entries}`;
|
|
3321
|
-
}
|
|
3322
|
-
function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded) {
|
|
3323
|
+
function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded, bundleCapability, bundleReferences) {
|
|
3323
3324
|
const versionSection = kineticaVersion ? `**Kinetica Version:** ${kineticaVersion} (provided at session start)` : "**Kinetica Version:** Unknown \u2014 detect via kinetica_health_check as the first action of every investigation.";
|
|
3324
3325
|
const t = "`";
|
|
3325
3326
|
const degradedSection = degraded ? `
|
|
@@ -3352,6 +3353,21 @@ function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, reference
|
|
|
3352
3353
|
- Do NOT attempt Round 4 (mutations) or Round 5 (verification) \u2014 the DB engine must be running first
|
|
3353
3354
|
|
|
3354
3355
|
` : "";
|
|
3356
|
+
const bundleSection = bundleCapability === void 0 ? "" : `
|
|
3357
|
+
---
|
|
3358
|
+
|
|
3359
|
+
## Support Bundle Capability
|
|
3360
|
+
|
|
3361
|
+
${bundleCapability === "attached" ? `A Kinetica support bundle (offline ${t}gpudb_sysinfo${t} snapshot) IS attached to this session. You now have **two complementary evidence sources**:
|
|
3362
|
+
- **The bundle** (${t}kinetica_bundle_*${t} tools) \u2014 frozen point-in-time history: per-rank logs (the incident narrative the live endpoints can't show), gpudb.conf at capture time, and host diagnostics. Call ${t}kinetica_bundle_list_files${t} first, then ${t}kinetica_bundle_log_timeline${t}.
|
|
3363
|
+
- **The live system** (the live diagnostic tools) \u2014 current state, right now.` : `The operator can attach an offline Kinetica support bundle for analysis. If they ask to "analyze a support bundle" (or you need historical logs the live endpoints don't expose \u2014 Kinetica has no log endpoint), call ${t}kinetica_load_bundle${t} **with no path** \u2014 they will be shown an interactive directory picker to choose the bundle. Do NOT ask for the path in chat. The ${t}kinetica_bundle_*${t} tools then read its logs/config/host-diagnostics.
|
|
3364
|
+
|
|
3365
|
+
**Attaching a bundle is SETUP, not an investigation.** After ${t}kinetica_load_bundle${t} succeeds, do NOT start gathering evidence. Confirm what the operator wants to investigate first (briefly note the bundle is ready), then wait for their answer before calling any ${t}kinetica_bundle_*${t} tools. Do not waste turns investigating something they did not ask about.`}
|
|
3366
|
+
|
|
3367
|
+
**Correlate the two:** the bundle tells you what HAPPENED (e.g. a crash, an error spike, config at capture time); the live tools tell you what is TRUE NOW (did it recover? is the config still drifted? did the issue recur?). Use the bundle for the historical narrative and the live tools to verify current state. Note in the report which findings came from the bundle (and its capture time) versus the live system.
|
|
3368
|
+
${bundleReferences && bundleReferences.length > 0 ? `
|
|
3369
|
+
${buildReferenceSection(bundleReferences)}
|
|
3370
|
+
` : ""}`;
|
|
3355
3371
|
return `You are an expert Kinetica GPU database administrator and diagnostician with deep knowledge of Kinetica's internals, system tables, REST API, and common failure patterns. Your job is to autonomously investigate database issues reported by operators, gather diagnostic evidence, reason over that evidence to identify root causes, and produce a structured diagnostic report with actionable remediation steps.
|
|
3356
3372
|
|
|
3357
3373
|
${versionSection}
|
|
@@ -3479,7 +3495,7 @@ ${t}kinetica_show_table${t} (e.g., ${t}is_shard_key${t}, ${t}is_primary_key${t},
|
|
|
3479
3495
|
${buildFailurePatternsSection(playbooks)}
|
|
3480
3496
|
|
|
3481
3497
|
${buildReferenceSection(references)}
|
|
3482
|
-
|
|
3498
|
+
${bundleSection}
|
|
3483
3499
|
---
|
|
3484
3500
|
|
|
3485
3501
|
## Analysis Instructions
|
|
@@ -3536,10 +3552,14 @@ Include specific, actionable remediation steps tied to your findings. Structure
|
|
|
3536
3552
|
|
|
3537
3553
|
## Post-Report Behavior
|
|
3538
3554
|
|
|
3539
|
-
1.
|
|
3540
|
-
2. After the report
|
|
3541
|
-
|
|
3542
|
-
|
|
3555
|
+
1. Present the finished report in your response so the operator can read it.
|
|
3556
|
+
2. **Ask BEFORE saving \u2014 never save unprompted.** After presenting the report, ask exactly: "Would you like me to save this report to disk? (yes/no)" and then STOP \u2014 end your turn and wait for the operator's answer. Do NOT call ${t}save_report${t} in the same turn as the question; the question must come first.
|
|
3557
|
+
- If the operator answers yes \u2192 call ${t}save_report${t} with the complete report markdown content.
|
|
3558
|
+
- If the operator answers no \u2192 do not save; acknowledge and continue.
|
|
3559
|
+
- **Only exception:** when checkpointing under budget pressure (the operator warned the budget guard is approaching, or you are preserving work with a ${t}partial: true${t} report before an early cutoff), save immediately WITHOUT asking \u2014 preserving findings outweighs the prompt.
|
|
3560
|
+
3. After saving (or after the operator declines), ask: "Would you like to investigate another issue, or end the session?"
|
|
3561
|
+
4. If the operator wants another investigation, start fresh with the same 5-round protocol.
|
|
3562
|
+
5. On session end: summarize all issues investigated and list the saved report file paths, then exit.
|
|
3543
3563
|
|
|
3544
3564
|
---
|
|
3545
3565
|
|
|
@@ -3550,6 +3570,13 @@ Monitor your context window usage during long investigations:
|
|
|
3550
3570
|
- If you detect that context is getting full (many rounds, many large tool responses), warn the operator: "The session context is getting long. Consider starting a fresh session after this report to maintain investigation quality. Your reports are saved to disk."
|
|
3551
3571
|
- Do NOT continue investigating when context is too full \u2014 write the report with evidence gathered so far.
|
|
3552
3572
|
|
|
3573
|
+
## Budget & Length Awareness
|
|
3574
|
+
|
|
3575
|
+
The session has a per-session budget guard that can end the run before you finish \u2014 and the operator may see a warning that you are "approaching the budget guard". To make sure a diagnostic always survives an early cutoff:
|
|
3576
|
+
- During a long or expensive investigation (many rounds or many large tool responses), proactively call \`save_report\` with \`partial: true\` to checkpoint your findings so far. A partial report is far better than none.
|
|
3577
|
+
- If the operator warns you that the budget guard is approaching, STOP gathering new evidence: immediately save a \`partial: true\` report with the evidence you have, state your best current hypothesis, and wind down the turn.
|
|
3578
|
+
- Treat the guard as a normal limit, not an error \u2014 never apologize for it; just preserve the work.
|
|
3579
|
+
|
|
3553
3580
|
---
|
|
3554
3581
|
|
|
3555
3582
|
## Output Formatting
|
|
@@ -3635,9 +3662,8 @@ async function discoverCatalogSchemas(session2) {
|
|
|
3635
3662
|
var import_promises2 = require("fs/promises");
|
|
3636
3663
|
var import_node_path3 = require("path");
|
|
3637
3664
|
var import_node_fs3 = require("fs");
|
|
3638
|
-
async function
|
|
3665
|
+
async function loadReferencesFrom(dir) {
|
|
3639
3666
|
try {
|
|
3640
|
-
const dir = refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references");
|
|
3641
3667
|
if (!(0, import_node_fs3.existsSync)(dir)) return [];
|
|
3642
3668
|
const files = await (0, import_promises2.readdir)(dir);
|
|
3643
3669
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
@@ -3659,13 +3685,21 @@ async function loadReferences(refsDir) {
|
|
|
3659
3685
|
return [];
|
|
3660
3686
|
}
|
|
3661
3687
|
}
|
|
3688
|
+
function loadReferences(refsDir) {
|
|
3689
|
+
return loadReferencesFrom(refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references"));
|
|
3690
|
+
}
|
|
3691
|
+
function loadBundleReferences(refsDir) {
|
|
3692
|
+
return loadReferencesFrom(
|
|
3693
|
+
refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references", "bundle")
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3662
3696
|
|
|
3663
3697
|
// src/agent/prompt-budget.ts
|
|
3664
3698
|
var CHARS_PER_TOKEN = 4;
|
|
3665
3699
|
var DEFAULT_PROMPT_BUDGET_TOKENS = 2e4;
|
|
3666
|
-
function estimateTokens(
|
|
3667
|
-
if (!
|
|
3668
|
-
return Math.ceil(
|
|
3700
|
+
function estimateTokens(text2) {
|
|
3701
|
+
if (!text2) return 0;
|
|
3702
|
+
return Math.ceil(text2.length / CHARS_PER_TOKEN);
|
|
3669
3703
|
}
|
|
3670
3704
|
function checkPromptBudget(prompt, opts) {
|
|
3671
3705
|
const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
|
|
@@ -3678,6 +3712,69 @@ function checkPromptBudget(prompt, opts) {
|
|
|
3678
3712
|
};
|
|
3679
3713
|
}
|
|
3680
3714
|
|
|
3715
|
+
// src/agent/session-budget.ts
|
|
3716
|
+
var DEFAULT_MAX_BUDGET_USD = 5;
|
|
3717
|
+
var DEFAULT_WARN_FRACTION = 0.8;
|
|
3718
|
+
var BUDGET_ENV_VAR = "ADMIN_AGENT_MAX_BUDGET";
|
|
3719
|
+
var MODEL_PRICING = {
|
|
3720
|
+
sonnet: { inputPerMTok: 3, outputPerMTok: 15, cacheReadPerMTok: 0.3, cacheCreationPerMTok: 3.75 },
|
|
3721
|
+
haiku: { inputPerMTok: 1, outputPerMTok: 5, cacheReadPerMTok: 0.1, cacheCreationPerMTok: 1.25 },
|
|
3722
|
+
opus: { inputPerMTok: 15, outputPerMTok: 75, cacheReadPerMTok: 1.5, cacheCreationPerMTok: 18.75 }
|
|
3723
|
+
};
|
|
3724
|
+
function fromSdkUsage(raw) {
|
|
3725
|
+
const u = raw ?? {};
|
|
3726
|
+
return {
|
|
3727
|
+
inputTokens: u.input_tokens,
|
|
3728
|
+
outputTokens: u.output_tokens,
|
|
3729
|
+
cacheReadInputTokens: u.cache_read_input_tokens,
|
|
3730
|
+
cacheCreationInputTokens: u.cache_creation_input_tokens
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
function safeCount(value) {
|
|
3734
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
3735
|
+
}
|
|
3736
|
+
function isValidBudget(value) {
|
|
3737
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3738
|
+
}
|
|
3739
|
+
function estimateTurnCostUsd(usage, model) {
|
|
3740
|
+
if (!usage) return 0;
|
|
3741
|
+
const price = MODEL_PRICING[model];
|
|
3742
|
+
const input5 = safeCount(usage.inputTokens);
|
|
3743
|
+
const output = safeCount(usage.outputTokens);
|
|
3744
|
+
const cacheRead = safeCount(usage.cacheReadInputTokens);
|
|
3745
|
+
const cacheCreation = safeCount(usage.cacheCreationInputTokens);
|
|
3746
|
+
return (input5 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
|
|
3747
|
+
}
|
|
3748
|
+
function resolveMaxBudgetUsd(flagValue2, env = process.env) {
|
|
3749
|
+
if (isValidBudget(flagValue2)) return flagValue2;
|
|
3750
|
+
const raw = env[BUDGET_ENV_VAR];
|
|
3751
|
+
if (raw !== void 0 && raw !== "") {
|
|
3752
|
+
const parsed = Number(raw);
|
|
3753
|
+
if (isValidBudget(parsed)) return parsed;
|
|
3754
|
+
}
|
|
3755
|
+
return DEFAULT_MAX_BUDGET_USD;
|
|
3756
|
+
}
|
|
3757
|
+
function createBudgetTracker(opts) {
|
|
3758
|
+
const warnFraction = opts.warnFraction ?? DEFAULT_WARN_FRACTION;
|
|
3759
|
+
const warnAt = opts.maxUsd * warnFraction;
|
|
3760
|
+
let spent = 0;
|
|
3761
|
+
let warned = false;
|
|
3762
|
+
return {
|
|
3763
|
+
add(usage, model) {
|
|
3764
|
+
spent += estimateTurnCostUsd(usage, model);
|
|
3765
|
+
},
|
|
3766
|
+
spentUsd() {
|
|
3767
|
+
return spent;
|
|
3768
|
+
},
|
|
3769
|
+
shouldWarn() {
|
|
3770
|
+
return !warned && spent > warnAt;
|
|
3771
|
+
},
|
|
3772
|
+
markWarned() {
|
|
3773
|
+
warned = true;
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3681
3778
|
// src/report/save-report.ts
|
|
3682
3779
|
var import_promises3 = require("fs/promises");
|
|
3683
3780
|
var import_node_path4 = require("path");
|
|
@@ -3696,7 +3793,7 @@ function formatTimestamp(date) {
|
|
|
3696
3793
|
function makeSaveReportTool() {
|
|
3697
3794
|
return (0, import_claude_agent_sdk3.tool)(
|
|
3698
3795
|
"save_report",
|
|
3699
|
-
"Save a diagnostic report to disk. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory.
|
|
3796
|
+
"Save a diagnostic report to disk. Call this ONLY after the operator has agreed to save (you must ask 'save this report? (yes/no)' and get a yes first) \u2014 or when checkpointing a partial report under budget pressure. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory.",
|
|
3700
3797
|
{
|
|
3701
3798
|
content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
|
|
3702
3799
|
partial: import_zod17.z.boolean().optional().describe(
|
|
@@ -3720,170 +3817,1386 @@ function makeSaveReportTool() {
|
|
|
3720
3817
|
);
|
|
3721
3818
|
}
|
|
3722
3819
|
|
|
3723
|
-
// src/
|
|
3724
|
-
var
|
|
3820
|
+
// src/tools/bundle/index.ts
|
|
3821
|
+
var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
|
|
3725
3822
|
|
|
3726
|
-
// src/
|
|
3727
|
-
var
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
var
|
|
3731
|
-
|
|
3732
|
-
|
|
3823
|
+
// src/tools/bundle/list-files.ts
|
|
3824
|
+
var import_zod18 = require("zod");
|
|
3825
|
+
|
|
3826
|
+
// src/bundle/known-files.ts
|
|
3827
|
+
var KNOWN_BUNDLE_FILES = {
|
|
3828
|
+
// Host resources
|
|
3829
|
+
"cpu.txt": "CPU topology, NUMA, and interrupts (lscpu, numactl, /proc/cpuinfo, /proc/interrupts)",
|
|
3830
|
+
"mem.txt": "Memory usage, /proc/meminfo, and transparent-hugepage setting (free -m -t)",
|
|
3831
|
+
"disk.txt": "Filesystems, mounts, block devices, and disk stats (df, mount, lsblk, fdisk, /etc/fstab, /proc/diskstats)",
|
|
3832
|
+
"gpu.txt": "NVIDIA GPU inventory and state (nvidia-smi -L/-q, modinfo nvidia)",
|
|
3833
|
+
"net.txt": "Network interfaces, sockets, and DNS (hostname, ifconfig, netstat, /etc/resolv.conf)",
|
|
3834
|
+
// Processes
|
|
3835
|
+
"ps.txt": "Full process list (ps -auxww, ps -ejHlfww)",
|
|
3836
|
+
"gpudb-exe.txt": "Running gpudb processes (ps auxfwww | grep gpudb)",
|
|
3837
|
+
// Hardware / firmware
|
|
3838
|
+
"dmidecode.txt": "BIOS / DMI hardware inventory (dmidecode)",
|
|
3839
|
+
"lshw.txt": "Hardware listing (lshw -short -numeric)",
|
|
3840
|
+
"pci.txt": "PCI devices and I/O resources (lspci, /proc/ioports, /proc/iomem)",
|
|
3841
|
+
// Kernel / OS
|
|
3842
|
+
"dmesg.txt": "Kernel ring buffer \u2014 boot and runtime kernel messages (dmesg -T)",
|
|
3843
|
+
"dmesg-timestamp.txt": "Kernel ring buffer with human-readable timestamps",
|
|
3844
|
+
"sysctl.txt": "Kernel tunables (sysctl -a)",
|
|
3845
|
+
"sys.txt": "OS identity, uptime, ulimits, kernel cmdline, clocksource, and loaded modules (uname, ulimit, /proc/cmdline, lsmod)",
|
|
3846
|
+
"lsof.txt": "Open files and network sockets (lsof -n -P)",
|
|
3847
|
+
"lslocks.txt": "Held file locks (lslocks)",
|
|
3848
|
+
// Packages / linker / accounts
|
|
3849
|
+
"deb.txt": "Installed Debian packages and verification (dpkg -l, dpkg -V)",
|
|
3850
|
+
"rpm.txt": "Installed RPM packages (rpm -qa)",
|
|
3851
|
+
"ld.so.conf.txt": "Dynamic-linker library search paths (/etc/ld.so.conf)",
|
|
3852
|
+
"user.txt": "Users, groups, and the gpudb service account (whoami, id, /etc/passwd, /etc/group)",
|
|
3853
|
+
"sudoers.txt": "Sudo configuration (/etc/sudoers)",
|
|
3854
|
+
"etc_profile.txt": "Login shell profile (/etc/profile)",
|
|
3855
|
+
"etc_bashrc.txt": "System bashrc (/etc/bashrc)",
|
|
3856
|
+
"etc_host.txt": "Static hostname resolution (/etc/hosts)",
|
|
3857
|
+
// Kinetica-specific
|
|
3858
|
+
"gpudb.txt": "GPUdb version/build, binary md5 + ldd, and the captured gpudb.conf / gpudb_logger.conf ($GPUDB_EXE -v)",
|
|
3859
|
+
"gpudb_core_etc_gpudb.conf": "The live gpudb.conf at capture time (the database's main config)",
|
|
3860
|
+
"gpudb_core_etc_gpudb_logger.conf": "The logging configuration (gpudb_logger.conf)",
|
|
3861
|
+
"loki-info.txt": "Loki log-index stats: labels, series, and per-class volume (logcli)",
|
|
3862
|
+
"sql-queries.txt": "SQL query log extracted from Loki (logcli)",
|
|
3863
|
+
"tables.txt": "Table schemas and column types (gadmin --schema), when collected",
|
|
3864
|
+
"logfiles.txt": "Manifest: the log directories/files the collector enumerated",
|
|
3865
|
+
"errors.txt": "Collection commands that FAILED during capture (Evidence Gaps)",
|
|
3866
|
+
"proc-logs-erros.txt": "Per-process log-collection failures during capture (Evidence Gaps)"
|
|
3867
|
+
};
|
|
3868
|
+
var KIND_DESCRIPTIONS = {
|
|
3869
|
+
"core-log": "Per-rank rolling Kinetica core log (the primary incident narrative)",
|
|
3870
|
+
"component-log": "Component service log (sql-engine, httpd, reveal, tomcat, stats, \u2026)",
|
|
3871
|
+
"loki-tail": "Last-2h Loki tail for a service (small; searched only when no core logs exist)",
|
|
3872
|
+
"process-info": "Per-rank process snapshot: command line, PID, and environment (/proc/<pid>/environ)",
|
|
3873
|
+
config: "Kinetica configuration file",
|
|
3874
|
+
"version-info": "GPUdb version/build information",
|
|
3875
|
+
"collection-errors": "Collection commands that FAILED during capture (Evidence Gaps)",
|
|
3876
|
+
manifest: "Manifest of log directories/files the collector enumerated"
|
|
3877
|
+
};
|
|
3878
|
+
function basename(relPath) {
|
|
3879
|
+
const parts = relPath.split("/");
|
|
3880
|
+
return parts[parts.length - 1] ?? relPath;
|
|
3733
3881
|
}
|
|
3734
|
-
function
|
|
3735
|
-
|
|
3736
|
-
const action = `${formatLabel("Action")}${import_picocolors5.default.bold(formatToolName(toolName))}`;
|
|
3737
|
-
const paramEntries = Object.entries(toolInput);
|
|
3738
|
-
const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
|
|
3739
|
-
const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
3740
|
-
return ` ${import_picocolors5.default.dim(key)}: ${formatted}`;
|
|
3741
|
-
}).join("\n");
|
|
3742
|
-
const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
|
|
3743
|
-
const prompt = import_picocolors5.default.dim(
|
|
3744
|
-
`${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
|
|
3745
|
-
);
|
|
3746
|
-
const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
|
|
3747
|
-
const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
|
|
3748
|
-
(entry) => ` ${import_picocolors5.default.dim(entry.key)}: ${entry.current} ${import_picocolors5.default.yellow("->")} ${entry.proposed}`
|
|
3749
|
-
).join("\n") : null;
|
|
3750
|
-
const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
|
|
3751
|
-
const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
|
|
3752
|
-
const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
|
|
3753
|
-
if (beforeAfterSection !== null) {
|
|
3754
|
-
sections.push(beforeAfterSection, "");
|
|
3755
|
-
}
|
|
3756
|
-
if (reasoningSection !== null) {
|
|
3757
|
-
sections.push(reasoningSection, "");
|
|
3758
|
-
}
|
|
3759
|
-
sections.push(impactLine, "", prompt, DIVIDER2, "");
|
|
3760
|
-
return sections.join("\n");
|
|
3882
|
+
function describeBundleFile(entry) {
|
|
3883
|
+
return KNOWN_BUNDLE_FILES[basename(entry.relPath)] ?? KIND_DESCRIPTIONS[entry.kind] ?? "";
|
|
3761
3884
|
}
|
|
3762
3885
|
|
|
3763
|
-
// src/
|
|
3764
|
-
var
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
return {
|
|
3793
|
-
behavior: "deny",
|
|
3794
|
-
message: DENY_MESSAGE,
|
|
3795
|
-
toolUseID: options.toolUseID
|
|
3796
|
-
};
|
|
3797
|
-
}
|
|
3798
|
-
if (normalized === "explain") {
|
|
3799
|
-
const reasoning = options.decisionReason;
|
|
3800
|
-
if (reasoning) {
|
|
3801
|
-
console.error(`
|
|
3802
|
-
Agent reasoning: ${reasoning}
|
|
3803
|
-
`);
|
|
3804
|
-
} else {
|
|
3805
|
-
console.error(`
|
|
3806
|
-
${REASONING_FALLBACK}
|
|
3807
|
-
`);
|
|
3808
|
-
}
|
|
3809
|
-
}
|
|
3810
|
-
} catch {
|
|
3811
|
-
return {
|
|
3812
|
-
behavior: "deny",
|
|
3813
|
-
message: DENY_MESSAGE,
|
|
3814
|
-
toolUseID: options.toolUseID
|
|
3815
|
-
};
|
|
3816
|
-
}
|
|
3886
|
+
// src/tools/bundle/list-files.ts
|
|
3887
|
+
var BundleListFilesSchema = import_zod18.z.object({
|
|
3888
|
+
kind: import_zod18.z.string().optional()
|
|
3889
|
+
});
|
|
3890
|
+
async function bundleListFiles(source, args = {}) {
|
|
3891
|
+
const all = source.listFiles();
|
|
3892
|
+
const filtered = args.kind ? all.filter((e) => e.kind === args.kind) : all;
|
|
3893
|
+
const { totalFiles, totalBytes, byKind, ranks, services } = source.inventory();
|
|
3894
|
+
const version = await source.detectVersion();
|
|
3895
|
+
const errors = await source.collectionErrors();
|
|
3896
|
+
const files = filtered.map((e) => ({
|
|
3897
|
+
file: e.relPath,
|
|
3898
|
+
kind: e.kind,
|
|
3899
|
+
rank: e.rank ?? "",
|
|
3900
|
+
size_kb: Math.round(e.sizeBytes / 1024),
|
|
3901
|
+
// What the file contains — so the agent can pick the right one without reading it.
|
|
3902
|
+
description: describeBundleFile(e)
|
|
3903
|
+
}));
|
|
3904
|
+
return {
|
|
3905
|
+
ok: true,
|
|
3906
|
+
data: {
|
|
3907
|
+
detected_version: version ?? "unknown",
|
|
3908
|
+
ranks_present: ranks.join(", ") || "none",
|
|
3909
|
+
services_present: services.join(", ") || "none",
|
|
3910
|
+
total_files: totalFiles,
|
|
3911
|
+
total_size_mb: Number((totalBytes / 1e6).toFixed(1)),
|
|
3912
|
+
counts_by_kind: byKind,
|
|
3913
|
+
failed_collections: errors.length,
|
|
3914
|
+
files
|
|
3817
3915
|
}
|
|
3818
3916
|
};
|
|
3819
3917
|
}
|
|
3820
3918
|
|
|
3821
|
-
// src/
|
|
3822
|
-
|
|
3823
|
-
|
|
3919
|
+
// src/tools/bundle/log-timeline.ts
|
|
3920
|
+
var import_zod19 = require("zod");
|
|
3921
|
+
var BundleLogTimelineSchema = import_zod19.z.object({
|
|
3922
|
+
min_severity: import_zod19.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
|
|
3923
|
+
granularity: import_zod19.z.enum(["day", "hour", "minute"]).optional(),
|
|
3924
|
+
rank: import_zod19.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
|
|
3925
|
+
host_manager: import_zod19.z.boolean().describe("Bucket the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
3926
|
+
component: import_zod19.z.string().optional(),
|
|
3927
|
+
include_components: import_zod19.z.boolean().optional()
|
|
3928
|
+
});
|
|
3929
|
+
async function bundleLogTimeline(source, args = {}) {
|
|
3930
|
+
const query3 = {
|
|
3931
|
+
...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
|
|
3932
|
+
...args.granularity !== void 0 ? { granularity: args.granularity } : {},
|
|
3933
|
+
...args.rank !== void 0 ? { rank: args.rank } : {},
|
|
3934
|
+
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
3935
|
+
...args.component !== void 0 ? { component: args.component } : {},
|
|
3936
|
+
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {}
|
|
3824
3937
|
};
|
|
3825
|
-
|
|
3826
|
-
|
|
3938
|
+
const result = await source.logTimeline(query3);
|
|
3939
|
+
const severities = [...new Set(result.buckets.flatMap((b) => Object.keys(b.counts)))];
|
|
3940
|
+
const order = ["FATAL", "ERROR", "UERR", "WARN", "INFO"];
|
|
3941
|
+
severities.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
3942
|
+
const rows = result.buckets.map((b) => {
|
|
3943
|
+
const row = { time_bucket: b.bucket };
|
|
3944
|
+
for (const sev of severities) row[sev] = b.counts[sev] ?? 0;
|
|
3945
|
+
row.total = b.total;
|
|
3946
|
+
return row;
|
|
3827
3947
|
});
|
|
3828
|
-
return
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
resolve2 = r;
|
|
3836
|
-
});
|
|
3948
|
+
return {
|
|
3949
|
+
ok: true,
|
|
3950
|
+
note: result.totalCounted === 0 ? "No lines at or above the severity threshold \u2014 try a lower min_severity." : `${result.totalCounted} event(s) across ${result.buckets.length} bucket(s), ${result.filesScanned.length} file(s).`,
|
|
3951
|
+
data: {
|
|
3952
|
+
lines_scanned: result.linesScanned,
|
|
3953
|
+
files_scanned: result.filesScanned.join(", ") || "none",
|
|
3954
|
+
buckets: rows
|
|
3837
3955
|
}
|
|
3838
|
-
}
|
|
3956
|
+
};
|
|
3839
3957
|
}
|
|
3840
3958
|
|
|
3841
|
-
// src/
|
|
3842
|
-
var
|
|
3843
|
-
var
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3959
|
+
// src/tools/bundle/search-logs.ts
|
|
3960
|
+
var import_zod20 = require("zod");
|
|
3961
|
+
var BundleSearchLogsSchema = import_zod20.z.object({
|
|
3962
|
+
regex: import_zod20.z.string().optional(),
|
|
3963
|
+
min_severity: import_zod20.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
|
|
3964
|
+
from_ts: import_zod20.z.string().optional(),
|
|
3965
|
+
to_ts: import_zod20.z.string().optional(),
|
|
3966
|
+
rank: import_zod20.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
|
|
3967
|
+
host_manager: import_zod20.z.boolean().describe("Search the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
3968
|
+
component: import_zod20.z.string().optional(),
|
|
3969
|
+
include_components: import_zod20.z.boolean().optional(),
|
|
3970
|
+
max_matches: import_zod20.z.number().int().min(1).max(1e3).optional()
|
|
3971
|
+
});
|
|
3972
|
+
async function bundleSearchLogs(source, args = {}) {
|
|
3973
|
+
const query3 = {
|
|
3974
|
+
...args.regex !== void 0 ? { regex: args.regex } : {},
|
|
3975
|
+
...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
|
|
3976
|
+
...args.from_ts !== void 0 ? { fromTs: args.from_ts } : {},
|
|
3977
|
+
...args.to_ts !== void 0 ? { toTs: args.to_ts } : {},
|
|
3978
|
+
...args.rank !== void 0 ? { rank: args.rank } : {},
|
|
3979
|
+
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
3980
|
+
...args.component !== void 0 ? { component: args.component } : {},
|
|
3981
|
+
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {},
|
|
3982
|
+
...args.max_matches !== void 0 ? { maxMatches: args.max_matches } : {}
|
|
3983
|
+
};
|
|
3984
|
+
const result = await source.searchLogs(query3);
|
|
3985
|
+
const note = result.capped ? `Showing ${result.matches.length} of ${result.totalMatched} matches across ${result.filesScanned.length} file(s) (display capped). Narrow with a tighter regex, severity, or time window to surface the specific lines.` : `${result.totalMatched} match(es) across ${result.filesScanned.length} file(s).`;
|
|
3986
|
+
return {
|
|
3987
|
+
ok: true,
|
|
3988
|
+
note,
|
|
3989
|
+
data: {
|
|
3990
|
+
total_matched: result.totalMatched,
|
|
3991
|
+
lines_scanned: result.linesScanned,
|
|
3992
|
+
files_scanned: result.filesScanned.join(", ") || "none",
|
|
3993
|
+
capped: result.capped,
|
|
3994
|
+
matches: result.matches.map((m) => ({
|
|
3995
|
+
file: m.file,
|
|
3996
|
+
line: m.lineNumber,
|
|
3997
|
+
timestamp: m.timestamp ?? "",
|
|
3998
|
+
severity: m.severity ?? "",
|
|
3999
|
+
rank: m.rank ?? "",
|
|
4000
|
+
message: m.message
|
|
4001
|
+
}))
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
// src/tools/bundle/read-config.ts
|
|
4007
|
+
var import_zod21 = require("zod");
|
|
4008
|
+
var BundleReadConfigSchema = import_zod21.z.object({
|
|
4009
|
+
section: import_zod21.z.string().optional(),
|
|
4010
|
+
key: import_zod21.z.string().optional()
|
|
4011
|
+
});
|
|
4012
|
+
async function bundleReadConfig(source, args = {}) {
|
|
4013
|
+
const result = await source.readConfig({
|
|
4014
|
+
...args.section !== void 0 ? { section: args.section } : {},
|
|
4015
|
+
...args.key !== void 0 ? { key: args.key } : {}
|
|
4016
|
+
});
|
|
4017
|
+
if ("error" in result) {
|
|
4018
|
+
return { ok: false, status: 0, error: result.error, raw: "" };
|
|
3849
4019
|
}
|
|
3850
|
-
if (
|
|
3851
|
-
|
|
4020
|
+
if (result.entries.length === 0 && args.section !== void 0) {
|
|
4021
|
+
const all = await source.readConfig(args.key !== void 0 ? { key: args.key } : {});
|
|
4022
|
+
const sections = "error" in all ? [] : [...new Set(all.entries.map((e) => e.section))].sort();
|
|
4023
|
+
const sectionList = sections.map((s) => s === "" ? "(flat/top-level)" : s).join(", ");
|
|
4024
|
+
return {
|
|
4025
|
+
ok: true,
|
|
4026
|
+
note: `No entries in section "${args.section}" of ${result.file}. gpudb.conf is largely flat \u2014 retry filtering by key only. Sections present: ${sectionList || "(none)"}.`,
|
|
4027
|
+
data: { section_not_found: args.section, available_sections: sections }
|
|
4028
|
+
};
|
|
3852
4029
|
}
|
|
3853
|
-
return
|
|
4030
|
+
return {
|
|
4031
|
+
ok: true,
|
|
4032
|
+
note: `${result.entries.length} entr(y/ies) from ${result.file}.`,
|
|
4033
|
+
data: result.entries.map((e) => ({ section: e.section, key: e.key, value: e.value }))
|
|
4034
|
+
};
|
|
3854
4035
|
}
|
|
3855
4036
|
|
|
3856
|
-
// src/
|
|
3857
|
-
var
|
|
3858
|
-
var
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
}
|
|
3865
|
-
|
|
3866
|
-
return
|
|
4037
|
+
// src/tools/bundle/read-sysinfo.ts
|
|
4038
|
+
var import_zod22 = require("zod");
|
|
4039
|
+
var BundleReadSysinfoSchema = import_zod22.z.object({
|
|
4040
|
+
name: import_zod22.z.string().min(1)
|
|
4041
|
+
});
|
|
4042
|
+
async function bundleReadSysinfo(source, args) {
|
|
4043
|
+
const result = await source.readSysinfo(args.name);
|
|
4044
|
+
if ("error" in result) {
|
|
4045
|
+
return { ok: false, status: 0, error: result.error, raw: "" };
|
|
4046
|
+
}
|
|
4047
|
+
return {
|
|
4048
|
+
ok: true,
|
|
4049
|
+
data: {
|
|
4050
|
+
...result.header !== void 0 ? { source_file: result.header } : {},
|
|
4051
|
+
blocks: result.blocks.map((b) => ({
|
|
4052
|
+
command: b.command,
|
|
4053
|
+
...b.exitCode !== void 0 ? { exit_code: b.exitCode } : {},
|
|
4054
|
+
output: b.output
|
|
4055
|
+
}))
|
|
4056
|
+
}
|
|
4057
|
+
};
|
|
3867
4058
|
}
|
|
3868
|
-
|
|
3869
|
-
|
|
4059
|
+
|
|
4060
|
+
// src/tools/bundle/load-bundle.ts
|
|
4061
|
+
var import_zod23 = require("zod");
|
|
4062
|
+
|
|
4063
|
+
// src/bundle/verify-bundle.ts
|
|
4064
|
+
var import_promises6 = require("fs/promises");
|
|
4065
|
+
|
|
4066
|
+
// src/bundle/BundleSource.ts
|
|
4067
|
+
var import_promises5 = require("fs/promises");
|
|
4068
|
+
var import_node_path6 = require("path");
|
|
4069
|
+
|
|
4070
|
+
// src/bundle/sysinfo-block.ts
|
|
4071
|
+
var SEPARATOR_RE = /^-{3,}$/;
|
|
4072
|
+
var EXEC_CMD_RE = /^EXEC_CMD:\s?(.*)$/;
|
|
4073
|
+
var EXEC_END_RE = /^EXEC_END with exit code (\d+)\s*:?\s*(.*)$/;
|
|
4074
|
+
var SHOWING_RE = /^### Showing whole log file\s*:/;
|
|
4075
|
+
function trimBlankEdges(lines) {
|
|
4076
|
+
let start = 0;
|
|
4077
|
+
let end = lines.length;
|
|
4078
|
+
while (start < end && lines[start].trim() === "") start++;
|
|
4079
|
+
while (end > start && lines[end - 1].trim() === "") end--;
|
|
4080
|
+
return lines.slice(start, end).join("\n");
|
|
4081
|
+
}
|
|
4082
|
+
function parseSysinfo(content) {
|
|
4083
|
+
const lines = content.split("\n");
|
|
4084
|
+
let header;
|
|
4085
|
+
const blocks = [];
|
|
4086
|
+
let current;
|
|
4087
|
+
let sawCommand = false;
|
|
4088
|
+
const closeBlock = (exitCode, exitMessage) => {
|
|
4089
|
+
if (!current) return;
|
|
4090
|
+
blocks.push({
|
|
4091
|
+
command: current.command,
|
|
4092
|
+
output: trimBlankEdges(current.output),
|
|
4093
|
+
...exitCode !== void 0 ? { exitCode } : {},
|
|
4094
|
+
...exitMessage !== void 0 && exitMessage !== "" ? { exitMessage } : {}
|
|
4095
|
+
});
|
|
4096
|
+
current = void 0;
|
|
4097
|
+
};
|
|
4098
|
+
for (const line of lines) {
|
|
4099
|
+
if (SEPARATOR_RE.test(line)) continue;
|
|
4100
|
+
const cmdMatch = EXEC_CMD_RE.exec(line);
|
|
4101
|
+
if (cmdMatch) {
|
|
4102
|
+
closeBlock();
|
|
4103
|
+
current = { command: cmdMatch[1].trim(), output: [] };
|
|
4104
|
+
sawCommand = true;
|
|
4105
|
+
continue;
|
|
4106
|
+
}
|
|
4107
|
+
const endMatch = EXEC_END_RE.exec(line);
|
|
4108
|
+
if (endMatch && current) {
|
|
4109
|
+
closeBlock(Number(endMatch[1]), endMatch[2].trim());
|
|
4110
|
+
continue;
|
|
4111
|
+
}
|
|
4112
|
+
if (current) {
|
|
4113
|
+
if (SHOWING_RE.test(line)) continue;
|
|
4114
|
+
current.output.push(line);
|
|
4115
|
+
continue;
|
|
4116
|
+
}
|
|
4117
|
+
if (header === void 0 && !sawCommand && line.trim() !== "") {
|
|
4118
|
+
header = line.trim();
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
closeBlock();
|
|
4122
|
+
return { ...header !== void 0 ? { header } : {}, blocks };
|
|
3870
4123
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
4124
|
+
|
|
4125
|
+
// src/bundle/parse-ini.ts
|
|
4126
|
+
var SECTION_RE = /^\[(.+)\]$/;
|
|
4127
|
+
function parseIni(content) {
|
|
4128
|
+
const entries = [];
|
|
4129
|
+
let section = "";
|
|
4130
|
+
for (const rawLine of content.split("\n")) {
|
|
4131
|
+
const line = rawLine.trim();
|
|
4132
|
+
if (line === "" || line.startsWith("#") || line.startsWith(";")) continue;
|
|
4133
|
+
const sectionMatch = SECTION_RE.exec(line);
|
|
4134
|
+
if (sectionMatch) {
|
|
4135
|
+
section = sectionMatch[1].trim();
|
|
4136
|
+
continue;
|
|
3878
4137
|
}
|
|
3879
|
-
|
|
4138
|
+
const eq = line.indexOf("=");
|
|
4139
|
+
if (eq === -1) continue;
|
|
4140
|
+
const key = line.slice(0, eq).trim();
|
|
4141
|
+
const value = line.slice(eq + 1).trim();
|
|
4142
|
+
if (key) entries.push({ section, key, value });
|
|
4143
|
+
}
|
|
4144
|
+
return entries;
|
|
4145
|
+
}
|
|
4146
|
+
function filterIni(entries, opts = {}) {
|
|
4147
|
+
const section = opts.section?.toLowerCase();
|
|
4148
|
+
const key = opts.key?.toLowerCase();
|
|
4149
|
+
return entries.filter((e) => {
|
|
4150
|
+
if (section !== void 0 && e.section.toLowerCase() !== section) return false;
|
|
4151
|
+
if (key !== void 0 && !e.key.toLowerCase().includes(key)) return false;
|
|
4152
|
+
return true;
|
|
3880
4153
|
});
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
// src/bundle/log-search.ts
|
|
4157
|
+
var import_node_fs4 = require("fs");
|
|
4158
|
+
var import_node_readline = require("readline");
|
|
4159
|
+
|
|
4160
|
+
// src/bundle/parse-log-line.ts
|
|
4161
|
+
var PREFIX_RE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+)\s+([A-Z]+)\s+\(([^)]*)\)\s*(.*)$/;
|
|
4162
|
+
var CORE_TAIL_RE = /^(\S+)\s+(\S+:\d+)\s+-\s+(.*)$/;
|
|
4163
|
+
var RANK_RE = /^(r\d+)\b/;
|
|
4164
|
+
var SEVERITY_RANK = {
|
|
4165
|
+
TRACE: 0,
|
|
4166
|
+
DEBUG: 1,
|
|
4167
|
+
INFO: 2,
|
|
4168
|
+
WARN: 3,
|
|
4169
|
+
UERR: 4,
|
|
4170
|
+
ERROR: 5,
|
|
4171
|
+
FATAL: 6
|
|
4172
|
+
};
|
|
4173
|
+
function severityRank(severity) {
|
|
4174
|
+
if (severity === void 0) return -1;
|
|
4175
|
+
return SEVERITY_RANK[severity] ?? -1;
|
|
4176
|
+
}
|
|
4177
|
+
function parseLogLine(line) {
|
|
4178
|
+
const match = PREFIX_RE.exec(line);
|
|
4179
|
+
if (!match) {
|
|
4180
|
+
return { message: line, raw: line };
|
|
4181
|
+
}
|
|
4182
|
+
const [, timestamp, severity, paren, rest] = match;
|
|
4183
|
+
const parts = paren.split(",");
|
|
4184
|
+
const pid = parts[0]?.trim() || void 0;
|
|
4185
|
+
const tid = parts[1]?.trim() || void 0;
|
|
4186
|
+
const context = parts.slice(2).join(",").trim() || void 0;
|
|
4187
|
+
const rank = context ? RANK_RE.exec(context)?.[1] ?? void 0 : void 0;
|
|
4188
|
+
const coreTail = CORE_TAIL_RE.exec(rest);
|
|
4189
|
+
if (coreTail) {
|
|
4190
|
+
const [, host, source, message] = coreTail;
|
|
4191
|
+
return { timestamp, severity, pid, tid, context, rank, host, source, message, raw: line };
|
|
4192
|
+
}
|
|
4193
|
+
return { timestamp, severity, pid, tid, context, rank, message: rest, raw: line };
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
// src/bundle/log-search.ts
|
|
4197
|
+
var DEFAULT_MAX_MATCHES = 200;
|
|
4198
|
+
var REGEX_SCAN_MAX = 8192;
|
|
4199
|
+
var GRANULARITY_LEN = {
|
|
4200
|
+
day: 10,
|
|
4201
|
+
// "2026-06-11"
|
|
4202
|
+
hour: 13,
|
|
4203
|
+
// "2026-06-11 15"
|
|
4204
|
+
minute: 16
|
|
4205
|
+
// "2026-06-11 15:18"
|
|
4206
|
+
};
|
|
4207
|
+
function compileRegex(query3) {
|
|
4208
|
+
if (query3.regex === void 0) return void 0;
|
|
4209
|
+
return new RegExp(query3.regex, query3.caseSensitive ? void 0 : "i");
|
|
4210
|
+
}
|
|
4211
|
+
var TS_FLOOR = "0000-01-01 00:00:00.000";
|
|
4212
|
+
var TS_CEIL = "9999-12-31 23:59:59.999";
|
|
4213
|
+
var SAFE_PREFIX_LENS = [4, 7, 10, 13, 16, 19];
|
|
4214
|
+
function alignPrefixLen(len) {
|
|
4215
|
+
let aligned = 0;
|
|
4216
|
+
for (const n of SAFE_PREFIX_LENS) if (n <= len) aligned = n;
|
|
4217
|
+
return aligned;
|
|
4218
|
+
}
|
|
4219
|
+
function floorTimestamp(ts) {
|
|
4220
|
+
if (ts.length >= TS_FLOOR.length) return ts;
|
|
4221
|
+
const len = alignPrefixLen(ts.length);
|
|
4222
|
+
return ts.slice(0, len) + TS_FLOOR.slice(len);
|
|
4223
|
+
}
|
|
4224
|
+
function ceilTimestamp(ts) {
|
|
4225
|
+
if (ts.length >= TS_CEIL.length) return ts;
|
|
4226
|
+
const len = alignPrefixLen(ts.length);
|
|
4227
|
+
return ts.slice(0, len) + TS_CEIL.slice(len);
|
|
4228
|
+
}
|
|
4229
|
+
function matchesFilters(parsed, query3, regex, minRank) {
|
|
4230
|
+
if (regex && !regex.test(parsed.raw.slice(0, REGEX_SCAN_MAX))) return false;
|
|
4231
|
+
if (query3.minSeverity !== void 0 && severityRank(parsed.severity) < minRank) return false;
|
|
4232
|
+
if (query3.rank !== void 0 && parsed.rank !== query3.rank) return false;
|
|
4233
|
+
if (query3.fromTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp < query3.fromTs))
|
|
4234
|
+
return false;
|
|
4235
|
+
if (query3.toTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp > query3.toTs))
|
|
4236
|
+
return false;
|
|
4237
|
+
return true;
|
|
4238
|
+
}
|
|
4239
|
+
async function searchLogFile(filePath, query3) {
|
|
4240
|
+
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4241
|
+
const minRank = query3.minSeverity !== void 0 ? severityRank(query3.minSeverity) : -Infinity;
|
|
4242
|
+
let regex;
|
|
4243
|
+
try {
|
|
4244
|
+
regex = compileRegex(query3);
|
|
4245
|
+
} catch (err) {
|
|
4246
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4247
|
+
return {
|
|
4248
|
+
matches: [],
|
|
4249
|
+
totalMatched: 0,
|
|
4250
|
+
linesScanned: 0,
|
|
4251
|
+
capped: false,
|
|
4252
|
+
error: `invalid regex: ${message}`
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
const boundedQuery = {
|
|
4256
|
+
...query3,
|
|
4257
|
+
...query3.fromTs !== void 0 ? { fromTs: floorTimestamp(query3.fromTs) } : {},
|
|
4258
|
+
...query3.toTs !== void 0 ? { toTs: ceilTimestamp(query3.toTs) } : {}
|
|
4259
|
+
};
|
|
4260
|
+
const matches = [];
|
|
4261
|
+
let totalMatched = 0;
|
|
4262
|
+
let linesScanned = 0;
|
|
4263
|
+
try {
|
|
4264
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4265
|
+
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
4266
|
+
crlfDelay: Infinity
|
|
4267
|
+
});
|
|
4268
|
+
for await (const line of rl) {
|
|
4269
|
+
linesScanned++;
|
|
4270
|
+
const parsed = parseLogLine(line);
|
|
4271
|
+
if (!matchesFilters(parsed, boundedQuery, regex, minRank)) continue;
|
|
4272
|
+
totalMatched++;
|
|
4273
|
+
if (matches.length < maxMatches) {
|
|
4274
|
+
matches.push({
|
|
4275
|
+
lineNumber: linesScanned,
|
|
4276
|
+
...parsed.timestamp !== void 0 ? { timestamp: parsed.timestamp } : {},
|
|
4277
|
+
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4278
|
+
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4279
|
+
message: parsed.message,
|
|
4280
|
+
raw: parsed.raw
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
} catch (err) {
|
|
4285
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4286
|
+
return {
|
|
4287
|
+
matches,
|
|
4288
|
+
totalMatched,
|
|
4289
|
+
linesScanned,
|
|
4290
|
+
capped: totalMatched > matches.length,
|
|
4291
|
+
error: message
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
return { matches, totalMatched, linesScanned, capped: totalMatched > matches.length };
|
|
4295
|
+
}
|
|
4296
|
+
async function aggregateTimeline(filePath, query3 = {}) {
|
|
4297
|
+
const granularity = query3.granularity ?? "hour";
|
|
4298
|
+
const prefixLen = GRANULARITY_LEN[granularity];
|
|
4299
|
+
const minRank = severityRank(query3.minSeverity ?? "WARN");
|
|
4300
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
4301
|
+
let linesScanned = 0;
|
|
4302
|
+
let totalCounted = 0;
|
|
4303
|
+
try {
|
|
4304
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4305
|
+
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
4306
|
+
crlfDelay: Infinity
|
|
4307
|
+
});
|
|
4308
|
+
for await (const line of rl) {
|
|
4309
|
+
linesScanned++;
|
|
4310
|
+
const parsed = parseLogLine(line);
|
|
4311
|
+
if (parsed.timestamp === void 0 || parsed.severity === void 0) continue;
|
|
4312
|
+
if (severityRank(parsed.severity) < minRank) continue;
|
|
4313
|
+
if (query3.rank !== void 0 && parsed.rank !== query3.rank) continue;
|
|
4314
|
+
const key = parsed.timestamp.slice(0, prefixLen);
|
|
4315
|
+
const bucket = buckets.get(key) ?? {};
|
|
4316
|
+
bucket[parsed.severity] = (bucket[parsed.severity] ?? 0) + 1;
|
|
4317
|
+
buckets.set(key, bucket);
|
|
4318
|
+
totalCounted++;
|
|
4319
|
+
}
|
|
4320
|
+
} catch (err) {
|
|
4321
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4322
|
+
return { buckets: [], linesScanned, totalCounted, error: message };
|
|
4323
|
+
}
|
|
4324
|
+
const result = [];
|
|
4325
|
+
for (const [bucket, counts] of buckets) {
|
|
4326
|
+
const total = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
4327
|
+
result.push({ bucket, counts, total });
|
|
4328
|
+
}
|
|
4329
|
+
return { buckets: result, linesScanned, totalCounted };
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
// src/bundle/bundle-index.ts
|
|
4333
|
+
var import_promises4 = require("fs/promises");
|
|
4334
|
+
var import_node_path5 = require("path");
|
|
4335
|
+
|
|
4336
|
+
// src/bundle/classify-file.ts
|
|
4337
|
+
var ROLLING_ID_RE = /core-gpudb-rolling-(r\d+|hm)\.log$/;
|
|
4338
|
+
var EXE_ID_RE = /gpudb-exe-(r\d+|hm)-/;
|
|
4339
|
+
var HOST_RE = /\b(node\w+)\b/;
|
|
4340
|
+
function rankOrService(id) {
|
|
4341
|
+
return id === "hm" ? { service: "host-manager" } : { rank: id };
|
|
4342
|
+
}
|
|
4343
|
+
function basename2(relPath) {
|
|
4344
|
+
const parts = relPath.split("/");
|
|
4345
|
+
return parts[parts.length - 1] ?? relPath;
|
|
4346
|
+
}
|
|
4347
|
+
function dirOf(relPath) {
|
|
4348
|
+
const parts = relPath.split("/");
|
|
4349
|
+
return parts.length > 1 ? parts[parts.length - 2] : "";
|
|
4350
|
+
}
|
|
4351
|
+
function inferHost(relPath) {
|
|
4352
|
+
return HOST_RE.exec(relPath)?.[1] ?? void 0;
|
|
4353
|
+
}
|
|
4354
|
+
function componentName(base) {
|
|
4355
|
+
return base.replace(/(\.log)+$/, "").replace(/^core-gpudb-/, "").replace(/^gpudb-/, "").replace(/-node\w+$/, "");
|
|
4356
|
+
}
|
|
4357
|
+
function classifyFile(relPath) {
|
|
4358
|
+
const base = basename2(relPath);
|
|
4359
|
+
const dir = dirOf(relPath);
|
|
4360
|
+
const host = inferHost(relPath);
|
|
4361
|
+
if (base.endsWith(".conf")) {
|
|
4362
|
+
return { kind: "config", ...host ? { host } : {} };
|
|
4363
|
+
}
|
|
4364
|
+
if (base === "logfiles.txt") {
|
|
4365
|
+
return { kind: "manifest", ...host ? { host } : {} };
|
|
4366
|
+
}
|
|
4367
|
+
if (base === "errors.txt" || base.endsWith("erros.txt")) {
|
|
4368
|
+
return { kind: "collection-errors", ...host ? { host } : {} };
|
|
4369
|
+
}
|
|
4370
|
+
if (base === "gpudb.txt") {
|
|
4371
|
+
return { kind: "version-info", ...host ? { host } : {} };
|
|
4372
|
+
}
|
|
4373
|
+
const exeId = EXE_ID_RE.exec(base);
|
|
4374
|
+
if (exeId) {
|
|
4375
|
+
return { kind: "process-info", ...rankOrService(exeId[1]), ...host ? { host } : {} };
|
|
4376
|
+
}
|
|
4377
|
+
if (base.endsWith(".log")) {
|
|
4378
|
+
const rolling = ROLLING_ID_RE.exec(base);
|
|
4379
|
+
if (rolling) {
|
|
4380
|
+
return { kind: "core-log", ...rankOrService(rolling[1]), ...host ? { host } : {} };
|
|
4381
|
+
}
|
|
4382
|
+
if (dir === "logs") {
|
|
4383
|
+
return { kind: "loki-tail", component: componentName(base), ...host ? { host } : {} };
|
|
4384
|
+
}
|
|
4385
|
+
return { kind: "component-log", component: componentName(base), ...host ? { host } : {} };
|
|
4386
|
+
}
|
|
4387
|
+
if (base.endsWith(".txt")) {
|
|
4388
|
+
return { kind: "os-diag", ...host ? { host } : {} };
|
|
4389
|
+
}
|
|
4390
|
+
return { kind: "unknown", ...host ? { host } : {} };
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
// src/bundle/bundle-index.ts
|
|
4394
|
+
async function buildIndex(rootDir) {
|
|
4395
|
+
let relPaths;
|
|
4396
|
+
try {
|
|
4397
|
+
relPaths = await (0, import_promises4.readdir)(rootDir, { recursive: true });
|
|
4398
|
+
} catch {
|
|
4399
|
+
return [];
|
|
4400
|
+
}
|
|
4401
|
+
const settled = await Promise.all(
|
|
4402
|
+
relPaths.map(async (rel) => {
|
|
4403
|
+
const relPath = rel.split("\\").join("/");
|
|
4404
|
+
const absPath = (0, import_node_path5.join)(rootDir, rel);
|
|
4405
|
+
try {
|
|
4406
|
+
const s = await (0, import_promises4.lstat)(absPath);
|
|
4407
|
+
if (s.isSymbolicLink() || !s.isFile()) return null;
|
|
4408
|
+
const c = classifyFile(relPath);
|
|
4409
|
+
return {
|
|
4410
|
+
relPath,
|
|
4411
|
+
absPath,
|
|
4412
|
+
kind: c.kind,
|
|
4413
|
+
...c.rank !== void 0 ? { rank: c.rank } : {},
|
|
4414
|
+
...c.service !== void 0 ? { service: c.service } : {},
|
|
4415
|
+
...c.host !== void 0 ? { host: c.host } : {},
|
|
4416
|
+
...c.component !== void 0 ? { component: c.component } : {},
|
|
4417
|
+
sizeBytes: s.size
|
|
4418
|
+
};
|
|
4419
|
+
} catch {
|
|
4420
|
+
return null;
|
|
4421
|
+
}
|
|
4422
|
+
})
|
|
4423
|
+
);
|
|
4424
|
+
return settled.filter((e) => e !== null).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
// src/bundle/BundleSource.ts
|
|
4428
|
+
var GPUDB_VERSION_RE = /GPUdb version\s*:\s*(\S+)/;
|
|
4429
|
+
function selectLogFiles(index, opts) {
|
|
4430
|
+
if (opts.component !== void 0) {
|
|
4431
|
+
return index.filter((e) => e.kind === "component-log" && e.component === opts.component);
|
|
4432
|
+
}
|
|
4433
|
+
if (opts.hostManager) {
|
|
4434
|
+
return index.filter((e) => e.kind === "core-log" && e.service === "host-manager");
|
|
4435
|
+
}
|
|
4436
|
+
let core = index.filter(
|
|
4437
|
+
(e) => e.kind === "core-log" && (opts.rank === void 0 || e.rank === opts.rank)
|
|
4438
|
+
);
|
|
4439
|
+
if (core.length === 0) {
|
|
4440
|
+
core = index.filter((e) => e.kind === "loki-tail");
|
|
4441
|
+
}
|
|
4442
|
+
if (opts.includeComponents) {
|
|
4443
|
+
return [...core, ...index.filter((e) => e.kind === "component-log")];
|
|
4444
|
+
}
|
|
4445
|
+
return core;
|
|
4446
|
+
}
|
|
4447
|
+
function toLineQuery(q) {
|
|
4448
|
+
return {
|
|
4449
|
+
...q.regex !== void 0 ? { regex: q.regex } : {},
|
|
4450
|
+
...q.caseSensitive !== void 0 ? { caseSensitive: q.caseSensitive } : {},
|
|
4451
|
+
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4452
|
+
...q.fromTs !== void 0 ? { fromTs: q.fromTs } : {},
|
|
4453
|
+
...q.toTs !== void 0 ? { toTs: q.toTs } : {},
|
|
4454
|
+
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {}
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
function toTimelineLineQuery(q) {
|
|
4458
|
+
return {
|
|
4459
|
+
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4460
|
+
...q.granularity !== void 0 ? { granularity: q.granularity } : {}
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
async function createBundleSource(rootDir) {
|
|
4464
|
+
const root = (0, import_node_path6.resolve)(rootDir);
|
|
4465
|
+
const index = await buildIndex(root);
|
|
4466
|
+
const resolve3 = (relPath) => {
|
|
4467
|
+
const abs = (0, import_node_path6.resolve)(root, relPath);
|
|
4468
|
+
if (abs !== root && !abs.startsWith(root + import_node_path6.sep)) return void 0;
|
|
4469
|
+
return abs;
|
|
4470
|
+
};
|
|
4471
|
+
const findByKind = (kind) => index.find((e) => e.kind === kind);
|
|
4472
|
+
const inventoryValue = (() => {
|
|
4473
|
+
const byKind = {};
|
|
4474
|
+
const rankSet = /* @__PURE__ */ new Set();
|
|
4475
|
+
const serviceSet = /* @__PURE__ */ new Set();
|
|
4476
|
+
let totalBytes = 0;
|
|
4477
|
+
for (const e of index) {
|
|
4478
|
+
byKind[e.kind] = (byKind[e.kind] ?? 0) + 1;
|
|
4479
|
+
totalBytes += e.sizeBytes;
|
|
4480
|
+
if (e.rank) rankSet.add(e.rank);
|
|
4481
|
+
if (e.service) serviceSet.add(e.service);
|
|
4482
|
+
}
|
|
4483
|
+
return {
|
|
4484
|
+
totalFiles: index.length,
|
|
4485
|
+
totalBytes,
|
|
4486
|
+
byKind,
|
|
4487
|
+
ranks: [...rankSet].sort(),
|
|
4488
|
+
services: [...serviceSet].sort()
|
|
4489
|
+
};
|
|
4490
|
+
})();
|
|
4491
|
+
const detectVersion = async () => {
|
|
4492
|
+
const versionFile = findByKind("version-info");
|
|
4493
|
+
if (versionFile) {
|
|
4494
|
+
try {
|
|
4495
|
+
const parsed = parseSysinfo(await (0, import_promises5.readFile)(versionFile.absPath, "utf-8"));
|
|
4496
|
+
for (const block of parsed.blocks) {
|
|
4497
|
+
const m = GPUDB_VERSION_RE.exec(block.output);
|
|
4498
|
+
if (m) return m[1];
|
|
4499
|
+
}
|
|
4500
|
+
} catch {
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
const configFile = findByKind("config");
|
|
4504
|
+
if (configFile) {
|
|
4505
|
+
try {
|
|
4506
|
+
const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
|
|
4507
|
+
return entries.find((e) => e.key === "file_version")?.value;
|
|
4508
|
+
} catch {
|
|
4509
|
+
return void 0;
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
return void 0;
|
|
4513
|
+
};
|
|
4514
|
+
const readConfig = async (opts = {}) => {
|
|
4515
|
+
const configFile = index.find((e) => e.kind === "config" && e.relPath.endsWith("gpudb.conf")) ?? findByKind("config");
|
|
4516
|
+
if (!configFile) return { error: "no gpudb.conf found in bundle" };
|
|
4517
|
+
try {
|
|
4518
|
+
const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
|
|
4519
|
+
return { entries: filterIni(entries, opts), file: configFile.relPath };
|
|
4520
|
+
} catch (err) {
|
|
4521
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
const readSysinfo = async (name) => {
|
|
4525
|
+
const entry = index.find(
|
|
4526
|
+
(e) => e.relPath === name || e.relPath.endsWith("/" + name) || e.relPath.split("/").pop() === name
|
|
4527
|
+
);
|
|
4528
|
+
if (!entry) return { error: `no bundle file named "${name}"` };
|
|
4529
|
+
const abs = resolve3(entry.relPath);
|
|
4530
|
+
if (!abs) return { error: `path "${name}" escapes the bundle root` };
|
|
4531
|
+
try {
|
|
4532
|
+
return parseSysinfo(await (0, import_promises5.readFile)(abs, "utf-8"));
|
|
4533
|
+
} catch (err) {
|
|
4534
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
4535
|
+
}
|
|
4536
|
+
};
|
|
4537
|
+
const searchLogs = async (query3) => {
|
|
4538
|
+
const files = selectLogFiles(index, query3);
|
|
4539
|
+
const lineQuery = toLineQuery(query3);
|
|
4540
|
+
const matches = [];
|
|
4541
|
+
const filesScanned = [];
|
|
4542
|
+
let totalMatched = 0;
|
|
4543
|
+
let linesScanned = 0;
|
|
4544
|
+
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4545
|
+
for (const file of files) {
|
|
4546
|
+
const remaining = Math.max(0, maxMatches - matches.length);
|
|
4547
|
+
const r = await searchLogFile(file.absPath, { ...lineQuery, maxMatches: remaining });
|
|
4548
|
+
filesScanned.push(file.relPath);
|
|
4549
|
+
totalMatched += r.totalMatched;
|
|
4550
|
+
linesScanned += r.linesScanned;
|
|
4551
|
+
for (const m of r.matches) matches.push({ ...m, file: file.relPath });
|
|
4552
|
+
}
|
|
4553
|
+
return {
|
|
4554
|
+
matches,
|
|
4555
|
+
totalMatched,
|
|
4556
|
+
linesScanned,
|
|
4557
|
+
filesScanned,
|
|
4558
|
+
capped: totalMatched > matches.length
|
|
4559
|
+
};
|
|
4560
|
+
};
|
|
4561
|
+
const logTimeline = async (query3) => {
|
|
4562
|
+
const files = selectLogFiles(index, query3);
|
|
4563
|
+
const lineQuery = toTimelineLineQuery(query3);
|
|
4564
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4565
|
+
const filesScanned = [];
|
|
4566
|
+
let linesScanned = 0;
|
|
4567
|
+
let totalCounted = 0;
|
|
4568
|
+
for (const file of files) {
|
|
4569
|
+
const r = await aggregateTimeline(file.absPath, lineQuery);
|
|
4570
|
+
filesScanned.push(file.relPath);
|
|
4571
|
+
linesScanned += r.linesScanned;
|
|
4572
|
+
totalCounted += r.totalCounted;
|
|
4573
|
+
for (const b of r.buckets) {
|
|
4574
|
+
const existing = merged.get(b.bucket) ?? {};
|
|
4575
|
+
for (const [sev, n] of Object.entries(b.counts)) existing[sev] = (existing[sev] ?? 0) + n;
|
|
4576
|
+
merged.set(b.bucket, existing);
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
const buckets = [...merged.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([bucket, counts]) => ({
|
|
4580
|
+
bucket,
|
|
4581
|
+
counts,
|
|
4582
|
+
total: Object.values(counts).reduce((x, y) => x + y, 0)
|
|
4583
|
+
}));
|
|
4584
|
+
return { buckets, linesScanned, totalCounted, filesScanned };
|
|
4585
|
+
};
|
|
4586
|
+
const collectionErrors = async () => {
|
|
4587
|
+
const files = index.filter((e) => e.kind === "collection-errors");
|
|
4588
|
+
const lines = [];
|
|
4589
|
+
for (const file of files) {
|
|
4590
|
+
try {
|
|
4591
|
+
const content = await (0, import_promises5.readFile)(file.absPath, "utf-8");
|
|
4592
|
+
for (const line of content.split("\n")) {
|
|
4593
|
+
const trimmed = line.trim();
|
|
4594
|
+
if (trimmed !== "" && !/^-{3,}$/.test(trimmed)) lines.push(trimmed);
|
|
4595
|
+
}
|
|
4596
|
+
} catch {
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
return lines;
|
|
4600
|
+
};
|
|
4601
|
+
return {
|
|
4602
|
+
root,
|
|
4603
|
+
listFiles: () => index,
|
|
4604
|
+
inventory: () => inventoryValue,
|
|
4605
|
+
resolve: resolve3,
|
|
4606
|
+
detectVersion,
|
|
4607
|
+
readConfig,
|
|
4608
|
+
readSysinfo,
|
|
4609
|
+
searchLogs,
|
|
4610
|
+
logTimeline,
|
|
4611
|
+
collectionErrors
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
// src/bundle/verify-bundle.ts
|
|
4616
|
+
var ARCHIVE_RE = /\.(tgz|tar\.gz|tar|gz|zip)$/i;
|
|
4617
|
+
var EXPECTED_KINDS = ["config", "core-log"];
|
|
4618
|
+
async function verifyBundle(bundlePath) {
|
|
4619
|
+
let info;
|
|
4620
|
+
try {
|
|
4621
|
+
info = await (0, import_promises6.stat)(bundlePath);
|
|
4622
|
+
} catch {
|
|
4623
|
+
return { ok: false, error: `bundle path does not exist: ${bundlePath}` };
|
|
4624
|
+
}
|
|
4625
|
+
if (!info.isDirectory()) {
|
|
4626
|
+
if (ARCHIVE_RE.test(bundlePath)) {
|
|
4627
|
+
return {
|
|
4628
|
+
ok: false,
|
|
4629
|
+
error: `bundle mode expects an extracted directory, not an archive. Run \`tar xzf ${bundlePath}\` and pass the resulting directory.`
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
return { ok: false, error: `bundle path is not a directory: ${bundlePath}` };
|
|
4633
|
+
}
|
|
4634
|
+
const bundleSource = await createBundleSource(bundlePath);
|
|
4635
|
+
const inventory = bundleSource.inventory();
|
|
4636
|
+
if (inventory.totalFiles === 0) {
|
|
4637
|
+
return { ok: false, error: `no readable files found in bundle directory: ${bundlePath}` };
|
|
4638
|
+
}
|
|
4639
|
+
const missingExpected = EXPECTED_KINDS.filter((k) => (inventory.byKind[k] ?? 0) === 0);
|
|
4640
|
+
const kineticaVersion = await bundleSource.detectVersion();
|
|
4641
|
+
return {
|
|
4642
|
+
ok: true,
|
|
4643
|
+
bundleSource,
|
|
4644
|
+
...kineticaVersion !== void 0 ? { kineticaVersion } : {},
|
|
4645
|
+
inventory,
|
|
4646
|
+
missingExpected
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4650
|
+
// src/tools/bundle/load-bundle.ts
|
|
4651
|
+
var BundleLoadSchema = import_zod23.z.object({
|
|
4652
|
+
path: import_zod23.z.string().min(1).optional()
|
|
4653
|
+
});
|
|
4654
|
+
async function bundleLoad(holder, args, promptForPath, confirmPath) {
|
|
4655
|
+
let path2;
|
|
4656
|
+
if (args.path !== void 0) {
|
|
4657
|
+
if (confirmPath && !await confirmPath(args.path)) {
|
|
4658
|
+
return {
|
|
4659
|
+
ok: false,
|
|
4660
|
+
status: 0,
|
|
4661
|
+
error: `Operator declined to load a bundle from "${args.path}".`,
|
|
4662
|
+
raw: args.path
|
|
4663
|
+
};
|
|
4664
|
+
}
|
|
4665
|
+
path2 = args.path;
|
|
4666
|
+
} else {
|
|
4667
|
+
path2 = promptForPath ? await promptForPath() : void 0;
|
|
4668
|
+
}
|
|
4669
|
+
if (!path2) {
|
|
4670
|
+
return {
|
|
4671
|
+
ok: false,
|
|
4672
|
+
status: 0,
|
|
4673
|
+
error: "No bundle path provided and no directory picker is available. Ask the operator for the extracted bundle directory path and pass it as `path`.",
|
|
4674
|
+
raw: ""
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
const result = await verifyBundle(path2);
|
|
4678
|
+
if (!result.ok) {
|
|
4679
|
+
return { ok: false, status: 0, error: result.error, raw: path2 };
|
|
4680
|
+
}
|
|
4681
|
+
holder.set(result.bundleSource);
|
|
4682
|
+
const missingNote = result.missingExpected.length > 0 ? ` Missing expected artifact(s): ${result.missingExpected.join(", ")} (treat as Evidence Gaps).` : "";
|
|
4683
|
+
return {
|
|
4684
|
+
ok: true,
|
|
4685
|
+
// Loading a bundle is SETUP, not an investigation. Do not auto-proceed — the
|
|
4686
|
+
// operator hasn't said what they want yet. End the turn and ask.
|
|
4687
|
+
note: `Bundle attached. Do NOT start investigating yet \u2014 ask the operator what they want to investigate, then proceed.${missingNote}`,
|
|
4688
|
+
data: {
|
|
4689
|
+
loaded: true,
|
|
4690
|
+
path: path2,
|
|
4691
|
+
detected_version: result.kineticaVersion ?? "unknown",
|
|
4692
|
+
total_files: result.inventory.totalFiles,
|
|
4693
|
+
ranks_present: result.inventory.ranks.join(", ") || "none",
|
|
4694
|
+
counts_by_kind: result.inventory.byKind,
|
|
4695
|
+
missing_expected: result.missingExpected.join(", ") || "none"
|
|
4696
|
+
}
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4700
|
+
// src/tools/bundle/index.ts
|
|
4701
|
+
var BUNDLE_TOOL_NAMES = [
|
|
4702
|
+
"kinetica_load_bundle",
|
|
4703
|
+
"kinetica_bundle_list_files",
|
|
4704
|
+
"kinetica_bundle_log_timeline",
|
|
4705
|
+
"kinetica_bundle_search_logs",
|
|
4706
|
+
"kinetica_bundle_read_config",
|
|
4707
|
+
"kinetica_bundle_read_sysinfo"
|
|
4708
|
+
];
|
|
4709
|
+
var text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
4710
|
+
function notLoaded() {
|
|
4711
|
+
return {
|
|
4712
|
+
ok: false,
|
|
4713
|
+
status: 0,
|
|
4714
|
+
error: "No support bundle is loaded. Ask the operator for the extracted bundle directory path and call kinetica_load_bundle first.",
|
|
4715
|
+
raw: ""
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
async function withSource(holder, fn) {
|
|
4719
|
+
const source = holder.get();
|
|
4720
|
+
if (!source) return applyOutputPipeline(notLoaded());
|
|
4721
|
+
return applyOutputPipeline(await fn(source));
|
|
4722
|
+
}
|
|
4723
|
+
function makeLoadBundleTool(holder, deps) {
|
|
4724
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4725
|
+
"kinetica_load_bundle",
|
|
4726
|
+
"Attach an extracted Kinetica support bundle (gpudb_sysinfo directory) so the kinetica_bundle_* tools can read its logs/config/host-diagnostics. When the operator wants to analyze a support bundle, call this tool WITHOUT a path \u2014 they will be shown an interactive directory picker to choose it. Do NOT ask for the path in chat. (You may pass an explicit `path` if the operator already gave you one; it must be a directory, not a .tgz.)",
|
|
4727
|
+
BundleLoadSchema.shape,
|
|
4728
|
+
async (args) => text(
|
|
4729
|
+
applyOutputPipeline(await bundleLoad(holder, args, deps?.promptForPath, deps?.confirmPath))
|
|
4730
|
+
),
|
|
4731
|
+
{ annotations: { readOnly: true } }
|
|
4732
|
+
);
|
|
4733
|
+
}
|
|
4734
|
+
function makeListFilesTool(holder) {
|
|
4735
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4736
|
+
"kinetica_bundle_list_files",
|
|
4737
|
+
"Inventory the attached support bundle: detected GPUdb version, ranks present (numeric ranks only), services present (e.g. host-manager \u2014 a singleton service, NOT a rank), file counts/sizes by kind, and how many collection commands failed. Each file row includes a `description` of what it contains (e.g. mem.txt \u2192 memory/THP, gpu.txt \u2192 nvidia-smi) so you can pick the right file without reading it. Call this FIRST after a bundle is attached. Optional `kind` filters the file list (e.g. core-log, component-log, config, os-diag).",
|
|
4738
|
+
BundleListFilesSchema.shape,
|
|
4739
|
+
async (args) => text(await withSource(holder, (s) => bundleListFiles(s, args))),
|
|
4740
|
+
{ annotations: { readOnly: true } }
|
|
4741
|
+
);
|
|
4742
|
+
}
|
|
4743
|
+
function makeLogTimelineTool(holder) {
|
|
4744
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4745
|
+
"kinetica_bundle_log_timeline",
|
|
4746
|
+
"Aggregate bundle log lines into per-time-bucket severity counts across ranks \u2014 the incident shape. Call this BEFORE search_logs to find WHEN errors spiked, then drill in with a tight time window. Defaults: min_severity=WARN, granularity=hour, core logs (all ranks AND the host manager). Narrow with rank=<r0|r1|\u2026> (numeric ranks only) or host_manager=true for the host-manager log (a service, not a rank). Set include_components=true or component=<name> to include component logs. Note severity order is WARN < UERR < ERROR < FATAL, so min_severity=ERROR EXCLUDES UERR (user-error) lines \u2014 use UERR or WARN to include them.",
|
|
4747
|
+
BundleLogTimelineSchema.shape,
|
|
4748
|
+
async (args) => text(await withSource(holder, (s) => bundleLogTimeline(s, args))),
|
|
4749
|
+
{ annotations: { readOnly: true } }
|
|
4750
|
+
);
|
|
4751
|
+
}
|
|
4752
|
+
function makeSearchLogsTool(holder) {
|
|
4753
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4754
|
+
"kinetica_bundle_search_logs",
|
|
4755
|
+
"Search bundle logs for matching lines by regex (case-insensitive), min_severity, time window (from_ts/to_ts as 'YYYY-MM-DD HH:MM:SS.mmm'; a partial prefix like a timeline bucket label '2026-06-11 15' also works \u2014 it is widened to cover that whole period), and rank/host_manager/component. Streamed and bounded \u2014 the default 200-match cap is shared across all files; when capped, narrow the query (the total may be a lower bound). Defaults to core logs across all ranks AND the host manager; narrow with rank=<r0|r1|\u2026> (numeric ranks only) or host_manager=true for the host-manager log (a service, not a rank); set component or include_components for component logs. Severity order is WARN < UERR < ERROR < FATAL, so min_severity=ERROR EXCLUDES UERR (user-error) lines.",
|
|
4756
|
+
BundleSearchLogsSchema.shape,
|
|
4757
|
+
async (args) => text(await withSource(holder, (s) => bundleSearchLogs(s, args))),
|
|
4758
|
+
{ annotations: { readOnly: true } }
|
|
4759
|
+
);
|
|
4760
|
+
}
|
|
4761
|
+
function makeReadConfigTool(holder) {
|
|
4762
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4763
|
+
"kinetica_bundle_read_config",
|
|
4764
|
+
"Read gpudb.conf from the attached bundle (the real on-disk config). Optionally filter by `section` (exact, case-insensitive) and/or `key` (substring, case-insensitive). Interpolation references like ${gaia.host0.address} are returned verbatim.",
|
|
4765
|
+
BundleReadConfigSchema.shape,
|
|
4766
|
+
async (args) => text(await withSource(holder, (s) => bundleReadConfig(s, args))),
|
|
4767
|
+
{ annotations: { readOnly: true } }
|
|
4768
|
+
);
|
|
4769
|
+
}
|
|
4770
|
+
function makeReadSysinfoTool(holder) {
|
|
4771
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4772
|
+
"kinetica_bundle_read_sysinfo",
|
|
4773
|
+
"Read an OS-diagnostic / process / version file's command blocks by name (e.g. mem.txt, cpu.txt, disk.txt, gpu.txt, net.txt, ps.txt, gpudb.txt, gpudb-exe-r0-*.txt). Returns each wrapped shell command and its output \u2014 host-level facts (memory, GPU, disk, THP, process args) the live endpoints never expose.",
|
|
4774
|
+
BundleReadSysinfoSchema.shape,
|
|
4775
|
+
async (args) => text(await withSource(holder, (s) => bundleReadSysinfo(s, args))),
|
|
4776
|
+
{ annotations: { readOnly: true } }
|
|
4777
|
+
);
|
|
4778
|
+
}
|
|
4779
|
+
function makeBundleTools(holder, deps) {
|
|
4780
|
+
return [
|
|
4781
|
+
makeLoadBundleTool(holder, deps),
|
|
4782
|
+
makeListFilesTool(holder),
|
|
4783
|
+
makeLogTimelineTool(holder),
|
|
4784
|
+
makeSearchLogsTool(holder),
|
|
4785
|
+
makeReadConfigTool(holder),
|
|
4786
|
+
makeReadSysinfoTool(holder)
|
|
4787
|
+
];
|
|
4788
|
+
}
|
|
4789
|
+
function createBundleRegistry() {
|
|
4790
|
+
return BUNDLE_TOOL_NAMES.reduce(
|
|
4791
|
+
(registry, name) => registry.registerReadOnlyTool(name),
|
|
4792
|
+
createRegistry()
|
|
4793
|
+
);
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
// src/tools/bundle/catalog.ts
|
|
4797
|
+
var BUNDLE_TOOL_CATALOG = {
|
|
4798
|
+
kinetica_load_bundle: {
|
|
4799
|
+
reveals: "Attaches an extracted support bundle (directory path) for offline analysis",
|
|
4800
|
+
whenToUse: "When the operator wants to analyze a support bundle \u2014 ask for the path, then load"
|
|
4801
|
+
},
|
|
4802
|
+
kinetica_bundle_list_files: {
|
|
4803
|
+
reveals: "Bundle inventory: detected version, ranks, file kinds/sizes, failed collections",
|
|
4804
|
+
whenToUse: "First action of every bundle investigation (orientation)"
|
|
4805
|
+
},
|
|
4806
|
+
kinetica_bundle_log_timeline: {
|
|
4807
|
+
reveals: "WARN+ log lines bucketed by time + severity across ranks (incident shape)",
|
|
4808
|
+
whenToUse: "Right after list_files \u2014 find WHEN errors spiked before drilling in"
|
|
4809
|
+
},
|
|
4810
|
+
kinetica_bundle_search_logs: {
|
|
4811
|
+
reveals: "Matching log lines by regex/severity/time-window/rank (bounded, streamed)",
|
|
4812
|
+
whenToUse: "Drill into a time window or error pattern surfaced by the timeline"
|
|
4813
|
+
},
|
|
4814
|
+
kinetica_bundle_read_config: {
|
|
4815
|
+
reveals: "gpudb.conf entries (the real on-disk config), filterable by section/key",
|
|
4816
|
+
whenToUse: "Config drift, misconfiguration, parameter verification"
|
|
4817
|
+
},
|
|
4818
|
+
kinetica_bundle_read_sysinfo: {
|
|
4819
|
+
reveals: "OS-diag / process / version command blocks (mem, cpu, disk, gpu, ps, gpudb.txt)",
|
|
4820
|
+
whenToUse: "Host-level facts: memory pressure, GPU presence, disk, THP, process args"
|
|
4821
|
+
}
|
|
4822
|
+
};
|
|
4823
|
+
function buildBundleEvidenceChecklist() {
|
|
4824
|
+
const rows = BUNDLE_TOOL_NAMES.map((name) => {
|
|
4825
|
+
const entry = BUNDLE_TOOL_CATALOG[name];
|
|
4826
|
+
return `| ${name} | ${entry.reveals} | ${entry.whenToUse} |`;
|
|
4827
|
+
});
|
|
4828
|
+
return [
|
|
4829
|
+
"| Tool | What it reveals | When to use |",
|
|
4830
|
+
"|------|----------------|-------------|",
|
|
4831
|
+
...rows
|
|
4832
|
+
].join("\n");
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4835
|
+
// src/agent/bundle-system-prompt.ts
|
|
4836
|
+
function buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences) {
|
|
4837
|
+
const t = "`";
|
|
4838
|
+
const versionSection = kineticaVersion ? `**Kinetica Version:** ${kineticaVersion} (detected from the bundle's gpudb.txt / gpudb.conf)` : "**Kinetica Version:** Unknown \u2014 check gpudb.txt via kinetica_bundle_read_sysinfo, or file_version via kinetica_bundle_read_config.";
|
|
4839
|
+
return `You are an expert Kinetica GPU database administrator and diagnostician. You are operating in OFFLINE BUNDLE MODE: instead of a live database, you are investigating an extracted support bundle (gpudb_sysinfo) \u2014 a snapshot of logs, configuration, and host diagnostics captured from a node at a point in time.
|
|
4840
|
+
|
|
4841
|
+
${versionSection}
|
|
4842
|
+
|
|
4843
|
+
---
|
|
4844
|
+
|
|
4845
|
+
## OFFLINE BUNDLE MODE \u2014 What This Means
|
|
4846
|
+
|
|
4847
|
+
**You are reading frozen, point-in-time evidence \u2014 not a live system.**
|
|
4848
|
+
|
|
4849
|
+
- You CANNOT run SQL, query system tables, re-probe the cluster, or apply fixes. There are no mutation tools. Your job is to diagnose from the captured files and RECOMMEND remediation for the operator to apply against the live system later.
|
|
4850
|
+
- The single highest-value evidence here is the **logs** \u2014 the live system exposes no log endpoint, so this is the one place the incident's narrative (the lead-up, the error cascade, the crash) is visible. Lean on them.
|
|
4851
|
+
- A bundle covers **one node**. It may contain multiple ranks (e.g. r0, r1) plus the host manager. The host manager is a singleton service (port 9300), **not a rank** \u2014 to search/timeline its log, pass ${t}host_manager: true${t} (NOT ${t}rank: "hm"${t}); ${t}kinetica_bundle_list_files${t} lists it under ${t}services_present${t}. Cross-node correlation is not possible from a single bundle.
|
|
4852
|
+
- **Do not assume cluster-wide clock synchronization.** Correlate events by message content as well as timestamp; note when timing is ambiguous.
|
|
4853
|
+
- Some collection commands may have FAILED (e.g. nvidia-smi on a CPU-only host). ${t}kinetica_bundle_list_files${t} reports how many \u2014 treat absent artifacts as Evidence Gaps, not as healthy.
|
|
4854
|
+
|
|
4855
|
+
---
|
|
4856
|
+
|
|
4857
|
+
## Investigation Protocol (read-only)
|
|
4858
|
+
|
|
4859
|
+
### Pre-Investigation: Announce Your Plan
|
|
4860
|
+
|
|
4861
|
+
Before gathering evidence, announce a brief 2-3 line plan: restate the issue, list the first tools you'll use, then begin immediately.
|
|
4862
|
+
|
|
4863
|
+
### Round 1 \u2014 Orient
|
|
4864
|
+
|
|
4865
|
+
- ${t}kinetica_bundle_list_files${t} \u2014 **ALWAYS FIRST.** Learn the detected version, which ranks are present, what file kinds exist, and how many collections failed.
|
|
4866
|
+
- ${t}kinetica_bundle_log_timeline${t} (min_severity: WARN) \u2014 get the incident shape: when did WARN/ERROR/FATAL spike, and on which rank?
|
|
4867
|
+
|
|
4868
|
+
### Round 2 \u2014 Drill Down
|
|
4869
|
+
|
|
4870
|
+
Based on the timeline, narrow in:
|
|
4871
|
+
- ${t}kinetica_bundle_search_logs${t} \u2014 search the spike window by regex/severity/rank. You can pass the timeline's hot bucket label straight into from_ts/to_ts (e.g. from_ts/to_ts = ${t}2026-06-11 15${t} searches that whole hour). Look for FATAL/ERROR clusters, stack traces, OOM, segfaults, failed allocations, stale-rank/heartbeat loss, rebalance failures. Remember UERR (user errors) rank below ERROR \u2014 use min_severity=WARN or UERR to include them.
|
|
4872
|
+
- ${t}kinetica_bundle_read_sysinfo${t} \u2014 corroborate with host facts: mem.txt (memory pressure, swap, THP), gpu.txt (GPU presence/OOM), disk.txt (disk full), cpu.txt, ps.txt, gpudb-exe-*.txt (process args/limits).
|
|
4873
|
+
|
|
4874
|
+
### Round 3 \u2014 Confirm
|
|
4875
|
+
|
|
4876
|
+
- ${t}kinetica_bundle_read_config${t} \u2014 check gpudb.conf for misconfiguration / config-drift relevant to your hypothesis (tier limits, thread pools, ports, HA).
|
|
4877
|
+
- Re-search logs to confirm the root-cause sequence.
|
|
4878
|
+
|
|
4879
|
+
After Round 3 you MUST write the report \u2014 even if uncertainty remains. There are no mutation or verification rounds in bundle mode: you recommend, you do not apply.
|
|
4880
|
+
|
|
4881
|
+
### Parallel Tool Calls
|
|
4882
|
+
|
|
4883
|
+
Issue independent reads together where possible (e.g. timeline + list_files, or a log search alongside a sysinfo read).
|
|
4884
|
+
|
|
4885
|
+
---
|
|
4886
|
+
|
|
4887
|
+
## Evidence Checklist \u2014 Bundle Tools
|
|
4888
|
+
|
|
4889
|
+
${buildBundleEvidenceChecklist()}
|
|
4890
|
+
|
|
4891
|
+
---
|
|
4892
|
+
|
|
4893
|
+
${buildFailurePatternsSection(playbooks)}
|
|
4894
|
+
|
|
4895
|
+
${buildReferenceSection([...bundleReferences ?? [], ...references ?? []])}
|
|
4896
|
+
|
|
4897
|
+
---
|
|
4898
|
+
|
|
4899
|
+
## Analysis Instructions
|
|
4900
|
+
|
|
4901
|
+
### Commit to the Best Hypothesis
|
|
4902
|
+
|
|
4903
|
+
After gathering evidence, name specific root causes \u2014 no generic hedging.
|
|
4904
|
+
|
|
4905
|
+
**DO:**
|
|
4906
|
+
- "Root cause: rank 0 crashed with a segmentation fault (signal 11) at 15:18:52 (core-gpudb-rolling-r0.log:Job.cpp:9), preceded by 57 ERROR lines in the 15:00 hour."
|
|
4907
|
+
- "If uncertain, rank top 2-3 hypotheses by likelihood: Primary (70%): X; Secondary (25%): Y."
|
|
4908
|
+
|
|
4909
|
+
**DO NOT:**
|
|
4910
|
+
- "There could be various reasons..." / "Further investigation may be needed..."
|
|
4911
|
+
|
|
4912
|
+
### Tie Evidence to Conclusions
|
|
4913
|
+
|
|
4914
|
+
Every conclusion must cite specific evidence \u2014 a file, a timestamp, a log line, a config key. Example: "GPU OOM is unlikely: gpu.txt shows nvidia-smi FAILED (exit 127) and gpudb-exe shows no --gpu rank args \u2014 this is a CPU-only host."
|
|
4915
|
+
|
|
4916
|
+
### Evidence Gap Handling
|
|
4917
|
+
|
|
4918
|
+
Note gaps and continue \u2014 never halt on a missing artifact:
|
|
4919
|
+
- "Host memory at crash: unavailable (mem.txt is a point-in-time snapshot taken during collection, not at crash time)."
|
|
4920
|
+
- "GPU metrics: unavailable (nvidia-smi collection FAILED \u2014 CPU-only host)."
|
|
4921
|
+
|
|
4922
|
+
---
|
|
4923
|
+
|
|
4924
|
+
## Fix Instructions
|
|
4925
|
+
|
|
4926
|
+
Provide specific, actionable remediation tied to your findings, as a numbered list. Because you cannot act on the bundle, frame everything as recommendations for the operator to apply to the live system:
|
|
4927
|
+
|
|
4928
|
+
1. Immediate manual actions the operator should take on the live system
|
|
4929
|
+
2. Configuration changes to prevent recurrence (cite the gpudb.conf key + value)
|
|
4930
|
+
3. Monitoring/alerting improvements
|
|
4931
|
+
4. What to capture or verify on the live system to close remaining Evidence Gaps
|
|
4932
|
+
|
|
4933
|
+
---
|
|
4934
|
+
|
|
4935
|
+
## Post-Report Behavior
|
|
4936
|
+
|
|
4937
|
+
1. Present the finished report in your response so the operator can read it.
|
|
4938
|
+
2. **Ask BEFORE saving \u2014 never save unprompted.** After presenting the report, ask exactly: "Would you like me to save this report to disk? (yes/no)" and then STOP \u2014 end your turn and wait for the operator's answer. Do NOT call ${t}save_report${t} in the same turn as the question; the question must come first. Save only if they answer yes. (Exception: if checkpointing under budget pressure with a ${t}partial: true${t} report, save immediately without asking \u2014 preserving findings beats the prompt.)
|
|
4939
|
+
3. After saving (or after the operator declines), ask: "Would you like to investigate another issue in this bundle, or end the session?"
|
|
4940
|
+
4. On session end: summarize issues investigated and list saved report paths, then exit.
|
|
4941
|
+
|
|
4942
|
+
---
|
|
4943
|
+
|
|
4944
|
+
## Budget & Length Awareness
|
|
4945
|
+
|
|
4946
|
+
The session has a per-session budget guard that can end the run early. If the operator warns that the guard is approaching, STOP gathering evidence, call ${t}save_report${t} with ${t}partial: true${t}, state your best current hypothesis, and wind down. A partial report beats none. Treat the guard as a normal limit, never an error.
|
|
4947
|
+
|
|
4948
|
+
---
|
|
4949
|
+
|
|
4950
|
+
## Output Formatting
|
|
4951
|
+
|
|
4952
|
+
Synthesize findings into clean markdown tables (3-6 columns, **bold** key identifiers, consistent ${t}OK${t}/${t}WARN${t}/${t}ERROR${t} indicators). Do NOT dump raw log output \u2014 extract the most relevant lines.
|
|
4953
|
+
|
|
4954
|
+
---
|
|
4955
|
+
|
|
4956
|
+
## REPORT TEMPLATE
|
|
4957
|
+
|
|
4958
|
+
At the end of each investigation, generate a structured markdown report using this EXACT template and section order:
|
|
4959
|
+
|
|
4960
|
+
\`\`\`markdown
|
|
4961
|
+
` + REPORT_TEMPLATE + `\`\`\`
|
|
4962
|
+
|
|
4963
|
+
**CRITICAL:** Use this exact section order. The metadata table comes first. Summary before Remediation. Evidence Collected before Evidence Gaps.
|
|
4964
|
+
|
|
4965
|
+
**Bundle-mode report notes:**
|
|
4966
|
+
- In the metadata, make clear this diagnosis is from an offline support bundle (note the node and detected version).
|
|
4967
|
+
- "Mutations Applied" will always be "None (offline bundle \u2014 read-only)". "Post-Remediation Verification" should state that verification requires re-running diagnostics against the live system.
|
|
4968
|
+
- "Evidence Collected" \u2014 cite specific files, timestamps, and log lines (no raw dumps; the 3-10 most relevant findings).
|
|
4969
|
+
`;
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
// src/bundle/bundle-holder.ts
|
|
4973
|
+
function createBundleHolder(initial) {
|
|
4974
|
+
let current = initial;
|
|
4975
|
+
return {
|
|
4976
|
+
get: () => current,
|
|
4977
|
+
set: (source) => {
|
|
4978
|
+
current = source;
|
|
4979
|
+
},
|
|
4980
|
+
isLoaded: () => current !== void 0
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4984
|
+
// src/cli/pick-bundle-path.ts
|
|
4985
|
+
var import_prompts3 = require("@inquirer/prompts");
|
|
4986
|
+
var import_promises7 = require("fs/promises");
|
|
4987
|
+
var import_node_path7 = require("path");
|
|
4988
|
+
function isPermissionError(err) {
|
|
4989
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return false;
|
|
4990
|
+
const code = err.code;
|
|
4991
|
+
return code === "EACCES" || code === "EPERM";
|
|
4992
|
+
}
|
|
4993
|
+
async function listDirectoryCandidates(term) {
|
|
4994
|
+
const input5 = term.trim() === "" ? "." : term;
|
|
4995
|
+
const endsWithSep = input5.endsWith("/");
|
|
4996
|
+
const baseDir = endsWithSep ? input5 : (0, import_node_path7.dirname)(input5) || ".";
|
|
4997
|
+
const prefix = endsWithSep ? "" : (0, import_node_path7.basename)(input5);
|
|
4998
|
+
const resolved = (0, import_node_path7.resolve)(baseDir);
|
|
4999
|
+
let entries;
|
|
5000
|
+
try {
|
|
5001
|
+
entries = await (0, import_promises7.readdir)(resolved, { withFileTypes: true });
|
|
5002
|
+
} catch (err) {
|
|
5003
|
+
if (isPermissionError(err)) return { kind: "denied", dir: resolved };
|
|
5004
|
+
return { kind: "ok", candidates: [] };
|
|
5005
|
+
}
|
|
5006
|
+
const candidates = entries.filter((e) => e.isDirectory() && e.name.startsWith(prefix)).map((e) => {
|
|
5007
|
+
const value = (0, import_node_path7.join)(baseDir, e.name);
|
|
5008
|
+
return { name: `${value}/`, value };
|
|
5009
|
+
}).sort((a, b) => a.value.localeCompare(b.value));
|
|
5010
|
+
return { kind: "ok", candidates };
|
|
5011
|
+
}
|
|
5012
|
+
function listingToChoices(listing) {
|
|
5013
|
+
if (listing.kind === "denied") {
|
|
5014
|
+
return [
|
|
5015
|
+
{
|
|
5016
|
+
name: `Permission denied reading ${listing.dir} \u2014 grant your terminal access in System Settings \u203A Privacy & Security \u203A Files & Folders (or Full Disk Access), then retry`,
|
|
5017
|
+
value: "",
|
|
5018
|
+
disabled: true
|
|
5019
|
+
}
|
|
5020
|
+
];
|
|
5021
|
+
}
|
|
5022
|
+
return listing.candidates.map((c) => ({ name: c.name, value: c.value }));
|
|
5023
|
+
}
|
|
5024
|
+
async function promptBundleDirectory() {
|
|
5025
|
+
if (!process.stdin.isTTY) return void 0;
|
|
5026
|
+
try {
|
|
5027
|
+
return await (0, import_prompts3.search)({
|
|
5028
|
+
message: "Select the support bundle directory (type to filter):",
|
|
5029
|
+
source: async (term) => listingToChoices(await listDirectoryCandidates(term ?? ""))
|
|
5030
|
+
});
|
|
5031
|
+
} catch {
|
|
5032
|
+
return void 0;
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
|
|
5036
|
+
// src/approval/gate.ts
|
|
5037
|
+
var import_prompts4 = require("@inquirer/prompts");
|
|
5038
|
+
|
|
5039
|
+
// src/approval/display.ts
|
|
5040
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
5041
|
+
var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
|
|
5042
|
+
var DIVIDER2 = import_picocolors5.default.dim("\u2500".repeat(50));
|
|
5043
|
+
var LABEL_WIDTH = 8;
|
|
5044
|
+
function formatLabel(label) {
|
|
5045
|
+
return ` ${label.padEnd(LABEL_WIDTH)}: `;
|
|
5046
|
+
}
|
|
5047
|
+
function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
|
|
5048
|
+
const header = import_picocolors5.default.bold(import_picocolors5.default.yellow(" Mutation Approval Required"));
|
|
5049
|
+
const action = `${formatLabel("Action")}${import_picocolors5.default.bold(formatToolName(toolName))}`;
|
|
5050
|
+
const paramEntries = Object.entries(toolInput);
|
|
5051
|
+
const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
|
|
5052
|
+
const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
5053
|
+
return ` ${import_picocolors5.default.dim(key)}: ${formatted}`;
|
|
5054
|
+
}).join("\n");
|
|
5055
|
+
const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
|
|
5056
|
+
const prompt = import_picocolors5.default.dim(
|
|
5057
|
+
`${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
|
|
5058
|
+
);
|
|
5059
|
+
const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
|
|
5060
|
+
const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
|
|
5061
|
+
(entry) => ` ${import_picocolors5.default.dim(entry.key)}: ${entry.current} ${import_picocolors5.default.yellow("->")} ${entry.proposed}`
|
|
5062
|
+
).join("\n") : null;
|
|
5063
|
+
const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
|
|
5064
|
+
const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
|
|
5065
|
+
const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
|
|
5066
|
+
if (beforeAfterSection !== null) {
|
|
5067
|
+
sections.push(beforeAfterSection, "");
|
|
5068
|
+
}
|
|
5069
|
+
if (reasoningSection !== null) {
|
|
5070
|
+
sections.push(reasoningSection, "");
|
|
5071
|
+
}
|
|
5072
|
+
sections.push(impactLine, "", prompt, DIVIDER2, "");
|
|
5073
|
+
return sections.join("\n");
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
// src/approval/gate.ts
|
|
5077
|
+
var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
|
|
5078
|
+
var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
|
|
5079
|
+
function createApprovalGate(isReadOnly) {
|
|
5080
|
+
return async (toolName, toolInput, options) => {
|
|
5081
|
+
if (isReadOnly(toolName)) {
|
|
5082
|
+
return {
|
|
5083
|
+
behavior: "allow",
|
|
5084
|
+
updatedInput: toolInput,
|
|
5085
|
+
toolUseID: options.toolUseID
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
const impact = options.decisionReason;
|
|
5089
|
+
const panel = renderApprovalPanel(toolName, toolInput, impact);
|
|
5090
|
+
console.error(panel);
|
|
5091
|
+
while (true) {
|
|
5092
|
+
try {
|
|
5093
|
+
const raw = await (0, import_prompts4.input)({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
|
|
5094
|
+
const normalized = raw.trim().toLowerCase();
|
|
5095
|
+
if (normalized === "y") {
|
|
5096
|
+
process.stderr.write("\n");
|
|
5097
|
+
return {
|
|
5098
|
+
behavior: "allow",
|
|
5099
|
+
updatedInput: toolInput,
|
|
5100
|
+
toolUseID: options.toolUseID
|
|
5101
|
+
};
|
|
5102
|
+
}
|
|
5103
|
+
if (normalized === "n") {
|
|
5104
|
+
process.stderr.write("\n");
|
|
5105
|
+
return {
|
|
5106
|
+
behavior: "deny",
|
|
5107
|
+
message: DENY_MESSAGE,
|
|
5108
|
+
toolUseID: options.toolUseID
|
|
5109
|
+
};
|
|
5110
|
+
}
|
|
5111
|
+
if (normalized === "explain") {
|
|
5112
|
+
const reasoning = options.decisionReason;
|
|
5113
|
+
if (reasoning) {
|
|
5114
|
+
console.error(`
|
|
5115
|
+
Agent reasoning: ${reasoning}
|
|
5116
|
+
`);
|
|
5117
|
+
} else {
|
|
5118
|
+
console.error(`
|
|
5119
|
+
${REASONING_FALLBACK}
|
|
5120
|
+
`);
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
} catch {
|
|
5124
|
+
return {
|
|
5125
|
+
behavior: "deny",
|
|
5126
|
+
message: DENY_MESSAGE,
|
|
5127
|
+
toolUseID: options.toolUseID
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
};
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
// src/agent/turn-gate.ts
|
|
5135
|
+
function createTurnGate() {
|
|
5136
|
+
let resolve3 = () => {
|
|
5137
|
+
};
|
|
5138
|
+
let promise = new Promise((r) => {
|
|
5139
|
+
resolve3 = r;
|
|
5140
|
+
});
|
|
5141
|
+
return Object.freeze({
|
|
5142
|
+
wait: () => promise,
|
|
5143
|
+
open: () => {
|
|
5144
|
+
resolve3();
|
|
5145
|
+
},
|
|
5146
|
+
close: () => {
|
|
5147
|
+
promise = new Promise((r) => {
|
|
5148
|
+
resolve3 = r;
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
5151
|
+
});
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
// src/output/render-markdown.ts
|
|
5155
|
+
var import_picocolors6 = __toESM(require("picocolors"));
|
|
5156
|
+
var BOLD_RE = /\*\*(.+?)\*\*/g;
|
|
5157
|
+
var HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
5158
|
+
function renderMarkdownLine(line) {
|
|
5159
|
+
const headingMatch = HEADING_RE.exec(line);
|
|
5160
|
+
if (headingMatch) {
|
|
5161
|
+
return import_picocolors6.default.bold(headingMatch[2]);
|
|
5162
|
+
}
|
|
5163
|
+
if (line.includes("**")) {
|
|
5164
|
+
return line.replace(BOLD_RE, (_, text2) => import_picocolors6.default.bold(text2));
|
|
5165
|
+
}
|
|
5166
|
+
return line;
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
// src/output/reformat-tables.ts
|
|
5170
|
+
var SEPARATOR_CELL_RE = /^:?-+:?$/;
|
|
5171
|
+
var BOLD_MARKERS_RE = /\*\*(.+?)\*\*/g;
|
|
5172
|
+
function visualWidth(text2) {
|
|
5173
|
+
return text2.replace(BOLD_MARKERS_RE, "$1").length;
|
|
5174
|
+
}
|
|
5175
|
+
function isSeparatorCell(cell) {
|
|
5176
|
+
return SEPARATOR_CELL_RE.test(cell);
|
|
5177
|
+
}
|
|
5178
|
+
function isSeparatorRow(cells) {
|
|
5179
|
+
return cells.length > 0 && cells.every(isSeparatorCell);
|
|
5180
|
+
}
|
|
5181
|
+
function parseCells(line) {
|
|
5182
|
+
return line.split("|").slice(1, -1).map((c) => c.trim());
|
|
5183
|
+
}
|
|
5184
|
+
function reformatTableBlock(lines) {
|
|
5185
|
+
const parsed = lines.map(parseCells);
|
|
5186
|
+
const colCount = Math.max(...parsed.map((row) => row.length));
|
|
5187
|
+
const normalised = parsed.map((row) => {
|
|
5188
|
+
const padded = [...row];
|
|
5189
|
+
while (padded.length < colCount) {
|
|
5190
|
+
padded.push("");
|
|
5191
|
+
}
|
|
5192
|
+
return padded;
|
|
5193
|
+
});
|
|
5194
|
+
const colWidths = Array.from(
|
|
5195
|
+
{ length: colCount },
|
|
5196
|
+
(_, col) => Math.max(
|
|
5197
|
+
3,
|
|
5198
|
+
...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
|
|
5199
|
+
)
|
|
3887
5200
|
);
|
|
3888
5201
|
const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
|
|
3889
5202
|
const bodyRows = normalised.map((row) => {
|
|
@@ -3911,9 +5224,9 @@ function createStreamingTableAligner() {
|
|
|
3911
5224
|
tableLines = [];
|
|
3912
5225
|
return aligned.join("\n") + "\n";
|
|
3913
5226
|
}
|
|
3914
|
-
function push(
|
|
3915
|
-
if (!
|
|
3916
|
-
const combined = lineBuffer +
|
|
5227
|
+
function push(text2) {
|
|
5228
|
+
if (!text2) return "";
|
|
5229
|
+
const combined = lineBuffer + text2;
|
|
3917
5230
|
const segments = combined.split("\n");
|
|
3918
5231
|
lineBuffer = segments[segments.length - 1];
|
|
3919
5232
|
const completeLines = segments.slice(0, -1);
|
|
@@ -3970,15 +5283,37 @@ function createSpinner() {
|
|
|
3970
5283
|
|
|
3971
5284
|
// src/agent/run-agent.ts
|
|
3972
5285
|
var MCP_SERVER_NAME = "kinetica-diagnostics";
|
|
5286
|
+
var SAVE_REPORT_TOOL_NAME = `mcp__${MCP_SERVER_NAME}__save_report`;
|
|
5287
|
+
function contentCallsSaveReport(content) {
|
|
5288
|
+
if (!Array.isArray(content)) return false;
|
|
5289
|
+
return content.some((block) => {
|
|
5290
|
+
if (typeof block !== "object" || block === null) return false;
|
|
5291
|
+
const { type, name } = block;
|
|
5292
|
+
return type === "tool_use" && name === SAVE_REPORT_TOOL_NAME;
|
|
5293
|
+
});
|
|
5294
|
+
}
|
|
5295
|
+
function formatCostSuffix(costUsd) {
|
|
5296
|
+
return costUsd !== void 0 && costUsd > 0 ? ` Cost: $${costUsd.toFixed(4)}.` : "";
|
|
5297
|
+
}
|
|
5298
|
+
function formatMetricsLine(turns, durationMs, durationApiMs, costUsd) {
|
|
5299
|
+
const durationSec = Math.round(durationMs / 1e3);
|
|
5300
|
+
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
5301
|
+
return `Turns: ${turns}. Duration: ${durationSec}s (${apiPct}% API).${formatCostSuffix(costUsd)}`;
|
|
5302
|
+
}
|
|
3973
5303
|
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
|
|
3974
5304
|
var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
|
|
3975
5305
|
var DEFAULT_AGENT_MODEL = "sonnet";
|
|
3976
|
-
var
|
|
5306
|
+
var LIVE_MAX_TURNS = 100;
|
|
5307
|
+
var BUNDLE_MAX_TURNS = 40;
|
|
3977
5308
|
var ALLOWED_TOOL_NAMES = [
|
|
3978
5309
|
...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
3979
|
-
|
|
5310
|
+
SAVE_REPORT_TOOL_NAME,
|
|
3980
5311
|
`mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
|
|
3981
5312
|
];
|
|
5313
|
+
var BUNDLE_ALLOWED_TOOL_NAMES = [
|
|
5314
|
+
...BUNDLE_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
5315
|
+
SAVE_REPORT_TOOL_NAME
|
|
5316
|
+
];
|
|
3982
5317
|
var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
|
|
3983
5318
|
var ERROR_LABELS = {
|
|
3984
5319
|
authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
|
|
@@ -3989,8 +5324,8 @@ var ERROR_LABELS = {
|
|
|
3989
5324
|
max_output_tokens: "Response exceeded maximum output length",
|
|
3990
5325
|
unknown: "Unknown API error"
|
|
3991
5326
|
};
|
|
3992
|
-
function isExitCommand(
|
|
3993
|
-
return EXIT_COMMANDS.has(
|
|
5327
|
+
function isExitCommand(text2) {
|
|
5328
|
+
return EXIT_COMMANDS.has(text2.trim().toLowerCase());
|
|
3994
5329
|
}
|
|
3995
5330
|
function makeUserMessage(content) {
|
|
3996
5331
|
return {
|
|
@@ -4004,7 +5339,7 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
|
|
|
4004
5339
|
while (!abortController.signal.aborted) {
|
|
4005
5340
|
try {
|
|
4006
5341
|
process.stderr.write("\n");
|
|
4007
|
-
const issue = await (0,
|
|
5342
|
+
const issue = await (0, import_prompts5.input)({ message: "Describe the issue to investigate:" });
|
|
4008
5343
|
process.stderr.write("\n");
|
|
4009
5344
|
const trimmed = issue.trim();
|
|
4010
5345
|
if (!trimmed) continue;
|
|
@@ -4021,7 +5356,7 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
|
|
|
4021
5356
|
await turnGate.wait();
|
|
4022
5357
|
if (abortController.signal.aborted) break;
|
|
4023
5358
|
process.stderr.write("\n");
|
|
4024
|
-
const response = await (0,
|
|
5359
|
+
const response = await (0, import_prompts5.input)({ message: "You:" });
|
|
4025
5360
|
process.stderr.write("\n");
|
|
4026
5361
|
const trimmed = response.trim();
|
|
4027
5362
|
if (!trimmed) continue;
|
|
@@ -4070,23 +5405,31 @@ async function displayDegradedStatus(session2) {
|
|
|
4070
5405
|
}
|
|
4071
5406
|
process.stderr.write("\n");
|
|
4072
5407
|
}
|
|
4073
|
-
async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
4074
|
-
const
|
|
4075
|
-
|
|
5408
|
+
async function runAgent(session2, kineticaVersion, degraded, model, runOptions) {
|
|
5409
|
+
const bundleSource = runOptions?.bundleSource;
|
|
5410
|
+
const authMethod = runOptions?.authMethod ?? "api_key";
|
|
5411
|
+
const dollarCapped = authMethod === "api_key";
|
|
5412
|
+
const resolvedBudgetUsd = runOptions?.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
|
|
5413
|
+
const [catalogSchemas, playbooks, references, bundleReferences] = await Promise.all([
|
|
5414
|
+
degraded || !session2 ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
|
|
4076
5415
|
loadPlaybooks(),
|
|
4077
|
-
loadReferences()
|
|
5416
|
+
loadReferences(),
|
|
5417
|
+
loadBundleReferences()
|
|
4078
5418
|
]);
|
|
4079
|
-
const
|
|
5419
|
+
const bundleHolder = createBundleHolder(bundleSource);
|
|
5420
|
+
const systemPrompt = session2 ? buildSystemPrompt(
|
|
4080
5421
|
kineticaVersion,
|
|
4081
5422
|
catalogSchemas,
|
|
4082
5423
|
playbooks,
|
|
4083
5424
|
references,
|
|
4084
|
-
degraded
|
|
4085
|
-
|
|
5425
|
+
degraded,
|
|
5426
|
+
bundleHolder.isLoaded() ? "attached" : "available",
|
|
5427
|
+
bundleReferences
|
|
5428
|
+
) : buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences);
|
|
4086
5429
|
const budget = checkPromptBudget(systemPrompt);
|
|
4087
5430
|
if (process.env.DEBUG) {
|
|
4088
5431
|
process.stderr.write(
|
|
4089
|
-
import_picocolors8.default.dim(`
|
|
5432
|
+
import_picocolors8.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
|
|
4090
5433
|
`)
|
|
4091
5434
|
);
|
|
4092
5435
|
}
|
|
@@ -4098,17 +5441,49 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4098
5441
|
)
|
|
4099
5442
|
);
|
|
4100
5443
|
}
|
|
4101
|
-
const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
|
|
4102
|
-
const mutationTools = makeMutationTools(session2);
|
|
4103
5444
|
const saveReportTool = makeSaveReportTool();
|
|
4104
|
-
|
|
4105
|
-
|
|
5445
|
+
if (!session2 && !bundleSource) {
|
|
5446
|
+
throw new Error("runAgent requires a Kinetica session, a bundleSource, or both.");
|
|
5447
|
+
}
|
|
5448
|
+
const spinner = createSpinner();
|
|
5449
|
+
const promptForPath = async () => {
|
|
5450
|
+
spinner.stop();
|
|
5451
|
+
return promptBundleDirectory();
|
|
5452
|
+
};
|
|
5453
|
+
const confirmPath = async (path2) => {
|
|
5454
|
+
spinner.stop();
|
|
5455
|
+
try {
|
|
5456
|
+
const answer = await (0, import_prompts5.input)({
|
|
5457
|
+
message: `Load support bundle from "${path2}"? The agent will be able to read files under that directory. (y/n):`
|
|
5458
|
+
});
|
|
5459
|
+
return answer.trim().toLowerCase() === "y";
|
|
5460
|
+
} catch {
|
|
5461
|
+
return false;
|
|
5462
|
+
}
|
|
5463
|
+
};
|
|
5464
|
+
const bundleTools = makeBundleTools(bundleHolder, { promptForPath, confirmPath });
|
|
5465
|
+
const liveTools = session2 ? [
|
|
5466
|
+
...makeDiagnosticTools(session2, catalogSchemas),
|
|
5467
|
+
...makeMutationTools(session2),
|
|
5468
|
+
makeAlterTableColumnsToolWithDeps(session2)
|
|
5469
|
+
] : [];
|
|
5470
|
+
const serverTools = [...liveTools, ...bundleTools, saveReportTool];
|
|
5471
|
+
const allowedTools = [
|
|
5472
|
+
.../* @__PURE__ */ new Set([...session2 ? ALLOWED_TOOL_NAMES : [], ...BUNDLE_ALLOWED_TOOL_NAMES])
|
|
5473
|
+
];
|
|
5474
|
+
let registry = createBundleRegistry();
|
|
5475
|
+
if (session2) {
|
|
5476
|
+
registry = DIAGNOSTIC_TOOL_NAMES.reduce(
|
|
5477
|
+
(reg, name) => reg.registerReadOnlyTool(name),
|
|
5478
|
+
registry
|
|
5479
|
+
);
|
|
5480
|
+
}
|
|
5481
|
+
const maxTurns = session2 ? LIVE_MAX_TURNS : BUNDLE_MAX_TURNS;
|
|
5482
|
+
const server = (0, import_claude_agent_sdk5.createSdkMcpServer)({
|
|
4106
5483
|
name: MCP_SERVER_NAME,
|
|
4107
5484
|
version: "1.0.0",
|
|
4108
|
-
tools:
|
|
5485
|
+
tools: serverTools
|
|
4109
5486
|
});
|
|
4110
|
-
const spinner = createSpinner();
|
|
4111
|
-
const registry = createDiagnosticRegistry();
|
|
4112
5487
|
const approvalGate = createApprovalGate(registry.isReadOnlyTool);
|
|
4113
5488
|
const canUseTool = async (toolName, toolInput, options2) => {
|
|
4114
5489
|
spinner.stop();
|
|
@@ -4118,33 +5493,64 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4118
5493
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4119
5494
|
const options = {
|
|
4120
5495
|
mcpServers: { [MCP_SERVER_NAME]: server },
|
|
4121
|
-
allowedTools
|
|
5496
|
+
allowedTools,
|
|
4122
5497
|
disallowedTools: [...DISALLOWED_TOOLS],
|
|
4123
5498
|
canUseTool,
|
|
4124
5499
|
systemPrompt,
|
|
4125
5500
|
model: effectiveModel,
|
|
4126
5501
|
fallbackModel: "haiku",
|
|
4127
5502
|
thinking: { type: "adaptive" },
|
|
4128
|
-
maxTurns
|
|
4129
|
-
|
|
5503
|
+
maxTurns,
|
|
5504
|
+
// Only impose a dollar cap for per-token billing. For OAuth subscription users
|
|
5505
|
+
// the SDK would otherwise cut them off at a notional dollar figure they never pay;
|
|
5506
|
+
// omitting it leaves the turn limit (maxTurns) as their guard.
|
|
5507
|
+
...dollarCapped ? { maxBudgetUsd: resolvedBudgetUsd } : {},
|
|
4130
5508
|
persistSession: false,
|
|
4131
5509
|
includePartialMessages: true,
|
|
4132
5510
|
abortController,
|
|
4133
5511
|
env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
|
|
4134
5512
|
};
|
|
4135
|
-
|
|
4136
|
-
|
|
5513
|
+
const guardLine = dollarCapped ? import_picocolors8.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
|
|
5514
|
+
`) : import_picocolors8.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
|
|
5515
|
+
const bundleSummary = () => {
|
|
5516
|
+
const src = bundleHolder.get();
|
|
5517
|
+
if (!src) return "";
|
|
5518
|
+
const { totalFiles, ranks } = src.inventory();
|
|
5519
|
+
const versionStr = kineticaVersion ? ` (version ${kineticaVersion})` : "";
|
|
5520
|
+
return `${totalFiles} files, ranks: ${ranks.join(", ") || "none"}${versionStr}`;
|
|
5521
|
+
};
|
|
5522
|
+
if (session2) {
|
|
5523
|
+
if (degraded) {
|
|
5524
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
|
|
5525
|
+
process.stderr.write(
|
|
5526
|
+
"DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
|
|
5527
|
+
);
|
|
5528
|
+
await displayDegradedStatus(session2);
|
|
5529
|
+
} else {
|
|
5530
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
5531
|
+
}
|
|
5532
|
+
if (bundleHolder.isLoaded()) {
|
|
5533
|
+
process.stderr.write(
|
|
5534
|
+
`Support bundle attached: ${bundleSummary()}. Live + bundle tools available.
|
|
5535
|
+
`
|
|
5536
|
+
);
|
|
5537
|
+
} else {
|
|
5538
|
+
process.stderr.write(
|
|
5539
|
+
import_picocolors8.default.dim("Tip: ask me to analyze a support bundle to add offline log/config analysis.\n")
|
|
5540
|
+
);
|
|
5541
|
+
}
|
|
5542
|
+
} else {
|
|
5543
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready (OFFLINE BUNDLE MODE)\n");
|
|
5544
|
+
process.stderr.write(`Analyzing extracted support bundle: ${bundleSummary()}.
|
|
5545
|
+
`);
|
|
4137
5546
|
process.stderr.write(
|
|
4138
|
-
"
|
|
5547
|
+
"Read-only \u2014 diagnoses from captured logs/config; live verification unavailable (no DB connection).\n"
|
|
4139
5548
|
);
|
|
4140
|
-
await displayDegradedStatus(session2);
|
|
4141
|
-
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4142
|
-
} else {
|
|
4143
|
-
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
4144
|
-
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4145
5549
|
}
|
|
5550
|
+
process.stderr.write(guardLine);
|
|
5551
|
+
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4146
5552
|
const turnGate = createTurnGate();
|
|
4147
|
-
const agentQuery = (0,
|
|
5553
|
+
const agentQuery = (0, import_claude_agent_sdk5.query)({
|
|
4148
5554
|
prompt: makeInteractivePrompt(abortController, turnGate, spinner),
|
|
4149
5555
|
options
|
|
4150
5556
|
});
|
|
@@ -4163,15 +5569,18 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4163
5569
|
let lastStreamCharWasNewline = true;
|
|
4164
5570
|
const tableAligner = createStreamingTableAligner();
|
|
4165
5571
|
let hadNonAbortError = false;
|
|
5572
|
+
let reportSavedThisRun = false;
|
|
5573
|
+
let invBase = { turns: 0, duration: 0, api: 0, cost: 0 };
|
|
5574
|
+
const budgetTracker = dollarCapped ? createBudgetTracker({ maxUsd: resolvedBudgetUsd }) : void 0;
|
|
4166
5575
|
try {
|
|
4167
5576
|
for await (const message of agentQuery) {
|
|
4168
5577
|
if (message.type === "stream_event") {
|
|
4169
5578
|
const { event: evt } = message;
|
|
4170
5579
|
if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
|
|
4171
|
-
const
|
|
4172
|
-
if (
|
|
5580
|
+
const text2 = evt.delta.text ?? "";
|
|
5581
|
+
if (text2) {
|
|
4173
5582
|
spinner.stop();
|
|
4174
|
-
const output = tableAligner.push(
|
|
5583
|
+
const output = tableAligner.push(text2);
|
|
4175
5584
|
if (output) {
|
|
4176
5585
|
process.stderr.write(output);
|
|
4177
5586
|
lastStreamCharWasNewline = output.endsWith("\n");
|
|
@@ -4189,6 +5598,23 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4189
5598
|
process.stderr.write("\n");
|
|
4190
5599
|
lastStreamCharWasNewline = true;
|
|
4191
5600
|
}
|
|
5601
|
+
if (budgetTracker) {
|
|
5602
|
+
budgetTracker.add(fromSdkUsage(assistantMsg.message.usage), effectiveModel);
|
|
5603
|
+
if (budgetTracker.shouldWarn()) {
|
|
5604
|
+
spinner.stop();
|
|
5605
|
+
process.stderr.write(
|
|
5606
|
+
import_picocolors8.default.yellow(
|
|
5607
|
+
`
|
|
5608
|
+
\u26A0 Approaching budget guard (~$${budgetTracker.spentUsd().toFixed(2)} / $${resolvedBudgetUsd.toFixed(2)}) \u2014 wrapping up soon. Save a partial report now if you want to preserve findings.
|
|
5609
|
+
`
|
|
5610
|
+
)
|
|
5611
|
+
);
|
|
5612
|
+
budgetTracker.markWarned();
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
if (contentCallsSaveReport(assistantMsg.message.content)) {
|
|
5616
|
+
reportSavedThisRun = true;
|
|
5617
|
+
}
|
|
4192
5618
|
if (assistantMsg.message.stop_reason === "end_turn") {
|
|
4193
5619
|
spinner.stop();
|
|
4194
5620
|
turnGate.open();
|
|
@@ -4217,10 +5643,21 @@ API error: ${label}
|
|
|
4217
5643
|
cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
|
|
4218
5644
|
if (resultMsg.subtype === "error_max_turns") {
|
|
4219
5645
|
process.stderr.write(
|
|
4220
|
-
|
|
5646
|
+
import_picocolors8.default.yellow(
|
|
5647
|
+
`
|
|
5648
|
+
Reached the turn limit (${numTurns} turns) \u2014 a safety guard, not an error. Any report the agent saved is in reports/. Start a fresh session to continue.
|
|
5649
|
+
`
|
|
5650
|
+
)
|
|
4221
5651
|
);
|
|
4222
5652
|
} else if (resultMsg.subtype === "error_max_budget_usd") {
|
|
4223
|
-
|
|
5653
|
+
const spentStr = totalCostUsd > 0 ? ` ($${totalCostUsd.toFixed(2)} spent)` : "";
|
|
5654
|
+
process.stderr.write(
|
|
5655
|
+
import_picocolors8.default.yellow(
|
|
5656
|
+
`
|
|
5657
|
+
Reached the $${resolvedBudgetUsd.toFixed(2)} budget guard${spentStr} \u2014 a safety limit, not an error. Re-run with --max-budget=<amount> (or set ADMIN_AGENT_MAX_BUDGET) for more headroom. Any report the agent saved is in reports/.
|
|
5658
|
+
`
|
|
5659
|
+
)
|
|
5660
|
+
);
|
|
4224
5661
|
} else if (resultMsg.subtype === "error_during_execution") {
|
|
4225
5662
|
process.stderr.write(
|
|
4226
5663
|
"\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
|
|
@@ -4236,6 +5673,24 @@ Agent session ended with error: ${resultMsg.subtype}
|
|
|
4236
5673
|
Permission denials: ${denied}
|
|
4237
5674
|
`);
|
|
4238
5675
|
}
|
|
5676
|
+
if (reportSavedThisRun) {
|
|
5677
|
+
const line = formatMetricsLine(
|
|
5678
|
+
numTurns - invBase.turns,
|
|
5679
|
+
durationMs - invBase.duration,
|
|
5680
|
+
durationApiMs - invBase.api,
|
|
5681
|
+
dollarCapped ? totalCostUsd - invBase.cost : void 0
|
|
5682
|
+
);
|
|
5683
|
+
process.stderr.write(`
|
|
5684
|
+
Investigation complete \u2014 ${line}
|
|
5685
|
+
`);
|
|
5686
|
+
invBase = {
|
|
5687
|
+
turns: numTurns,
|
|
5688
|
+
duration: durationMs,
|
|
5689
|
+
api: durationApiMs,
|
|
5690
|
+
cost: totalCostUsd
|
|
5691
|
+
};
|
|
5692
|
+
reportSavedThisRun = false;
|
|
5693
|
+
}
|
|
4239
5694
|
turnGate.open();
|
|
4240
5695
|
} else if (message.type === "system") {
|
|
4241
5696
|
const sysMsg = message;
|
|
@@ -4291,7 +5746,7 @@ Rate limited \u2014 requests rejected.${resetStr}
|
|
|
4291
5746
|
}
|
|
4292
5747
|
} catch (error) {
|
|
4293
5748
|
spinner.stop();
|
|
4294
|
-
if (error instanceof
|
|
5749
|
+
if (error instanceof import_claude_agent_sdk5.AbortError) {
|
|
4295
5750
|
hadNonAbortError = false;
|
|
4296
5751
|
} else {
|
|
4297
5752
|
hadNonAbortError = true;
|
|
@@ -4307,27 +5762,26 @@ Agent error: ${message}
|
|
|
4307
5762
|
process.stderr.write(remaining);
|
|
4308
5763
|
}
|
|
4309
5764
|
turnGate.open();
|
|
4310
|
-
const
|
|
4311
|
-
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
4312
|
-
const costStr = totalCostUsd > 0 ? ` Cost: $${totalCostUsd.toFixed(4)}.` : "";
|
|
5765
|
+
const sessionCost = dollarCapped ? totalCostUsd : void 0;
|
|
4313
5766
|
if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
|
|
4314
5767
|
process.stderr.write(
|
|
4315
5768
|
import_picocolors8.default.dim(
|
|
4316
|
-
`
|
|
5769
|
+
`Cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
|
|
4317
5770
|
`
|
|
4318
5771
|
)
|
|
4319
5772
|
);
|
|
4320
5773
|
}
|
|
4321
5774
|
if (hadNonAbortError) {
|
|
4322
|
-
process.stderr.write(`
|
|
4323
|
-
Session ended due to error. Turns: ${numTurns}.${costStr}
|
|
4324
|
-
`);
|
|
4325
|
-
} else {
|
|
4326
5775
|
process.stderr.write(
|
|
4327
5776
|
`
|
|
4328
|
-
Session ended. Turns: ${numTurns}
|
|
5777
|
+
Session ended due to error. Turns: ${numTurns}.${formatCostSuffix(sessionCost)}
|
|
4329
5778
|
`
|
|
4330
5779
|
);
|
|
5780
|
+
} else {
|
|
5781
|
+
const line = formatMetricsLine(numTurns, durationMs, durationApiMs, sessionCost);
|
|
5782
|
+
process.stderr.write(`
|
|
5783
|
+
Session ended. ${line}
|
|
5784
|
+
`);
|
|
4331
5785
|
}
|
|
4332
5786
|
}
|
|
4333
5787
|
}
|
|
@@ -4339,7 +5793,7 @@ var MODEL_LABELS = {
|
|
|
4339
5793
|
opus: "Opus \u2014 deepest reasoning, slower & pricier"
|
|
4340
5794
|
};
|
|
4341
5795
|
async function selectModel() {
|
|
4342
|
-
return (0,
|
|
5796
|
+
return (0, import_prompts6.select)({
|
|
4343
5797
|
message: "Select model for this session:",
|
|
4344
5798
|
default: DEFAULT_AGENT_MODEL,
|
|
4345
5799
|
choices: SUPPORTED_MODELS.map((value) => ({ value, name: MODEL_LABELS[value] }))
|
|
@@ -4347,7 +5801,7 @@ async function selectModel() {
|
|
|
4347
5801
|
}
|
|
4348
5802
|
|
|
4349
5803
|
// src/auth/preflight.ts
|
|
4350
|
-
var
|
|
5804
|
+
var import_claude_agent_sdk6 = require("@anthropic-ai/claude-agent-sdk");
|
|
4351
5805
|
|
|
4352
5806
|
// src/auth/oauth-flow.ts
|
|
4353
5807
|
var import_picocolors9 = __toESM(require("picocolors"));
|
|
@@ -4435,7 +5889,7 @@ async function authenticateAnthropic(options) {
|
|
|
4435
5889
|
await new Promise(() => {
|
|
4436
5890
|
});
|
|
4437
5891
|
}
|
|
4438
|
-
const authQuery = (0,
|
|
5892
|
+
const authQuery = (0, import_claude_agent_sdk6.query)({
|
|
4439
5893
|
prompt: hangingPrompt(),
|
|
4440
5894
|
options: {
|
|
4441
5895
|
persistSession: false,
|
|
@@ -4467,12 +5921,12 @@ async function authenticateAnthropic(options) {
|
|
|
4467
5921
|
// src/auth/logout.ts
|
|
4468
5922
|
var import_child_process2 = require("child_process");
|
|
4469
5923
|
var import_node_module = require("module");
|
|
4470
|
-
var
|
|
5924
|
+
var import_node_path8 = __toESM(require("path"));
|
|
4471
5925
|
var import_util = require("util");
|
|
4472
5926
|
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
4473
5927
|
function resolveSdkCliPath() {
|
|
4474
5928
|
const require_ = (0, import_node_module.createRequire)(__filename);
|
|
4475
|
-
return
|
|
5929
|
+
return import_node_path8.default.join(import_node_path8.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
|
|
4476
5930
|
}
|
|
4477
5931
|
async function logout() {
|
|
4478
5932
|
try {
|
|
@@ -4492,9 +5946,9 @@ async function logout() {
|
|
|
4492
5946
|
|
|
4493
5947
|
// src/session/env-file.ts
|
|
4494
5948
|
var import_fs2 = require("fs");
|
|
4495
|
-
var
|
|
5949
|
+
var import_promises8 = require("fs/promises");
|
|
4496
5950
|
var import_path2 = require("path");
|
|
4497
|
-
var
|
|
5951
|
+
var import_prompts7 = require("@inquirer/prompts");
|
|
4498
5952
|
var import_picocolors10 = __toESM(require("picocolors"));
|
|
4499
5953
|
function parseEnvContent(content) {
|
|
4500
5954
|
const entries = [];
|
|
@@ -4577,7 +6031,7 @@ function loadEnvFile(dir, env = process.env) {
|
|
|
4577
6031
|
async function offerSaveCredentials(url, user, dir) {
|
|
4578
6032
|
if (!process.stdin.isTTY) return;
|
|
4579
6033
|
try {
|
|
4580
|
-
const shouldSave = await (0,
|
|
6034
|
+
const shouldSave = await (0, import_prompts7.confirm)({
|
|
4581
6035
|
message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
|
|
4582
6036
|
default: true
|
|
4583
6037
|
});
|
|
@@ -4585,11 +6039,11 @@ async function offerSaveCredentials(url, user, dir) {
|
|
|
4585
6039
|
const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
|
|
4586
6040
|
let existing;
|
|
4587
6041
|
try {
|
|
4588
|
-
existing = await (0,
|
|
6042
|
+
existing = await (0, import_promises8.readFile)(filePath, "utf8");
|
|
4589
6043
|
} catch {
|
|
4590
6044
|
}
|
|
4591
6045
|
const content = buildEnvContent(url, user, existing);
|
|
4592
|
-
await (0,
|
|
6046
|
+
await (0, import_promises8.writeFile)(filePath, content, "utf8");
|
|
4593
6047
|
console.error(import_picocolors10.default.dim("Saved to .env"));
|
|
4594
6048
|
} catch (err) {
|
|
4595
6049
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4599,10 +6053,10 @@ async function offerSaveCredentials(url, user, dir) {
|
|
|
4599
6053
|
|
|
4600
6054
|
// src/session/verify.ts
|
|
4601
6055
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
4602
|
-
var
|
|
6056
|
+
var import_prompts10 = require("@inquirer/prompts");
|
|
4603
6057
|
|
|
4604
6058
|
// src/session/collect.ts
|
|
4605
|
-
var
|
|
6059
|
+
var import_prompts8 = require("@inquirer/prompts");
|
|
4606
6060
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
4607
6061
|
async function collectCredentials() {
|
|
4608
6062
|
const prompted = /* @__PURE__ */ new Set();
|
|
@@ -4610,27 +6064,27 @@ async function collectCredentials() {
|
|
|
4610
6064
|
const envUser = process.env.KINETICA_USER;
|
|
4611
6065
|
if (envUrl && envUser && process.stdin.isTTY) {
|
|
4612
6066
|
console.error(import_picocolors11.default.dim(`Saved connection: ${envUrl} (${envUser})`));
|
|
4613
|
-
const useSaved = await (0,
|
|
6067
|
+
const useSaved = await (0, import_prompts8.confirm)({
|
|
4614
6068
|
message: "Use saved connection?",
|
|
4615
6069
|
default: true
|
|
4616
6070
|
});
|
|
4617
6071
|
if (!useSaved) {
|
|
4618
6072
|
prompted.add("url");
|
|
4619
6073
|
prompted.add("user");
|
|
4620
|
-
const url2 = await (0,
|
|
4621
|
-
const user2 = await (0,
|
|
4622
|
-
const pass2 = await (0,
|
|
6074
|
+
const url2 = await (0, import_prompts8.input)({ message: "Kinetica endpoint URL:" });
|
|
6075
|
+
const user2 = await (0, import_prompts8.input)({ message: "Admin username:" });
|
|
6076
|
+
const pass2 = await (0, import_prompts8.password)({ message: "Admin password:", mask: "*" });
|
|
4623
6077
|
return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
|
|
4624
6078
|
}
|
|
4625
6079
|
}
|
|
4626
|
-
const url = envUrl ?? (prompted.add("url"), await (0,
|
|
4627
|
-
const user = envUser ?? (prompted.add("user"), await (0,
|
|
4628
|
-
const pass = process.env.KINETICA_PASS ?? await (0,
|
|
6080
|
+
const url = envUrl ?? (prompted.add("url"), await (0, import_prompts8.input)({ message: "Kinetica endpoint URL:" }));
|
|
6081
|
+
const user = envUser ?? (prompted.add("user"), await (0, import_prompts8.input)({ message: "Admin username:" }));
|
|
6082
|
+
const pass = process.env.KINETICA_PASS ?? await (0, import_prompts8.password)({ message: "Admin password:", mask: "*" });
|
|
4629
6083
|
return { credentials: { url, user, pass }, prompted };
|
|
4630
6084
|
}
|
|
4631
6085
|
async function repromptCredentials() {
|
|
4632
|
-
const user = await (0,
|
|
4633
|
-
const pass = await (0,
|
|
6086
|
+
const user = await (0, import_prompts8.input)({ message: "Admin username:" });
|
|
6087
|
+
const pass = await (0, import_prompts8.password)({ message: "Admin password:", mask: "*" });
|
|
4634
6088
|
return { user, pass };
|
|
4635
6089
|
}
|
|
4636
6090
|
|
|
@@ -4641,8 +6095,9 @@ function replacePort(baseUrl, port) {
|
|
|
4641
6095
|
parsed.port = String(port);
|
|
4642
6096
|
return parsed.origin;
|
|
4643
6097
|
}
|
|
4644
|
-
function createSession(url, user, pass) {
|
|
6098
|
+
function createSession(url, user, pass, options) {
|
|
4645
6099
|
const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
|
|
6100
|
+
const timeoutMs = options?.timeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
4646
6101
|
const doFetch = async (fullUrl, body) => {
|
|
4647
6102
|
if (process.env.DEBUG) {
|
|
4648
6103
|
console.error(`[DEBUG] POST ${fullUrl}`);
|
|
@@ -4654,7 +6109,7 @@ function createSession(url, user, pass) {
|
|
|
4654
6109
|
"Content-Type": "application/json"
|
|
4655
6110
|
},
|
|
4656
6111
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
4657
|
-
signal: AbortSignal.timeout(
|
|
6112
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
4658
6113
|
});
|
|
4659
6114
|
};
|
|
4660
6115
|
return {
|
|
@@ -4666,7 +6121,7 @@ function createSession(url, user, pass) {
|
|
|
4666
6121
|
|
|
4667
6122
|
// src/session/resolve-url.ts
|
|
4668
6123
|
var import_picocolors12 = __toESM(require("picocolors"));
|
|
4669
|
-
var
|
|
6124
|
+
var import_prompts9 = require("@inquirer/prompts");
|
|
4670
6125
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
4671
6126
|
var HTTP_PROTOCOL_RE = /^https?:\/\//i;
|
|
4672
6127
|
var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
@@ -4708,7 +6163,7 @@ async function confirmHttpFallback(host) {
|
|
|
4708
6163
|
)
|
|
4709
6164
|
);
|
|
4710
6165
|
try {
|
|
4711
|
-
return await (0,
|
|
6166
|
+
return await (0, import_prompts9.confirm)({
|
|
4712
6167
|
message: "Continue over plaintext HTTP?",
|
|
4713
6168
|
default: false
|
|
4714
6169
|
});
|
|
@@ -4716,7 +6171,7 @@ async function confirmHttpFallback(host) {
|
|
|
4716
6171
|
return false;
|
|
4717
6172
|
}
|
|
4718
6173
|
}
|
|
4719
|
-
async function resolveUrl(input5) {
|
|
6174
|
+
async function resolveUrl(input5, options = {}) {
|
|
4720
6175
|
const trimmed = input5.trim();
|
|
4721
6176
|
if (trimmed === "") {
|
|
4722
6177
|
return { ok: false, error: "URL is empty" };
|
|
@@ -4750,7 +6205,7 @@ async function resolveUrl(input5) {
|
|
|
4750
6205
|
error: `Could not connect to ${normalized} via https:// or http://`
|
|
4751
6206
|
};
|
|
4752
6207
|
}
|
|
4753
|
-
if (!isInteractive()) {
|
|
6208
|
+
if (options.nonInteractive || !isInteractive()) {
|
|
4754
6209
|
return {
|
|
4755
6210
|
ok: false,
|
|
4756
6211
|
error: `HTTPS unavailable at ${normalized} and terminal is non-interactive. Pass an explicit http:// prefix to allow plaintext HTTP, or point the URL at an HTTPS endpoint.`
|
|
@@ -4770,6 +6225,7 @@ async function resolveUrl(input5) {
|
|
|
4770
6225
|
var MAX_RETRIES = 3;
|
|
4771
6226
|
var MAX_REPROMPTS = 2;
|
|
4772
6227
|
var DEFAULT_HM_PORT2 = 9300;
|
|
6228
|
+
var BEST_EFFORT_PROBE_TIMEOUT_MS = 5e3;
|
|
4773
6229
|
function extractVersion(responseBody) {
|
|
4774
6230
|
try {
|
|
4775
6231
|
const outer = JSON.parse(responseBody);
|
|
@@ -4816,6 +6272,24 @@ async function verifyConnectivity(session2) {
|
|
|
4816
6272
|
function isCredentialError(errorMessage) {
|
|
4817
6273
|
return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
|
|
4818
6274
|
}
|
|
6275
|
+
async function connectBestEffort() {
|
|
6276
|
+
const url = process.env.KINETICA_URL;
|
|
6277
|
+
const user = process.env.KINETICA_USER;
|
|
6278
|
+
const pass = process.env.KINETICA_PASS;
|
|
6279
|
+
if (!url || !user || !pass) return void 0;
|
|
6280
|
+
try {
|
|
6281
|
+
const resolved = await resolveUrl(url, { nonInteractive: true });
|
|
6282
|
+
if (!resolved.ok) return void 0;
|
|
6283
|
+
const probe = createSession(resolved.url, user, pass, {
|
|
6284
|
+
timeoutMs: BEST_EFFORT_PROBE_TIMEOUT_MS
|
|
6285
|
+
});
|
|
6286
|
+
const kineticaVersion = await verifyConnectivity(probe);
|
|
6287
|
+
const session2 = createSession(resolved.url, user, pass);
|
|
6288
|
+
return { session: session2, kineticaVersion, degraded: false };
|
|
6289
|
+
} catch {
|
|
6290
|
+
return void 0;
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
4819
6293
|
async function connectWithRetry() {
|
|
4820
6294
|
const { credentials, prompted } = await collectCredentials();
|
|
4821
6295
|
const resolved = await resolveUrl(credentials.url);
|
|
@@ -4842,7 +6316,7 @@ async function connectWithRetry() {
|
|
|
4842
6316
|
console.error(import_picocolors13.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
|
|
4843
6317
|
if (isCredentialError(msg)) {
|
|
4844
6318
|
if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
|
|
4845
|
-
const shouldRetry = await (0,
|
|
6319
|
+
const shouldRetry = await (0, import_prompts10.confirm)({
|
|
4846
6320
|
message: "Credentials may be incorrect. Re-enter?",
|
|
4847
6321
|
default: true
|
|
4848
6322
|
});
|
|
@@ -4885,6 +6359,14 @@ async function connectWithRetry() {
|
|
|
4885
6359
|
// src/cli/index.ts
|
|
4886
6360
|
var verbose = false;
|
|
4887
6361
|
var session;
|
|
6362
|
+
function flagValue(args, name) {
|
|
6363
|
+
const prefix = `${name}=`;
|
|
6364
|
+
const arg = args.find((a) => a.startsWith(prefix));
|
|
6365
|
+
return arg === void 0 ? void 0 : arg.slice(prefix.length);
|
|
6366
|
+
}
|
|
6367
|
+
function chooseBundleSessionVersion(bundleVersion, liveVersion) {
|
|
6368
|
+
return bundleVersion ?? liveVersion;
|
|
6369
|
+
}
|
|
4888
6370
|
function printHelp() {
|
|
4889
6371
|
const lines = [
|
|
4890
6372
|
"",
|
|
@@ -4904,12 +6386,15 @@ function printHelp() {
|
|
|
4904
6386
|
" --login-org=UUID Target organization UUID for OAuth",
|
|
4905
6387
|
" --logout Log out from Anthropic account and exit",
|
|
4906
6388
|
` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
|
|
6389
|
+
" --max-budget=USD Per-session budget cap in USD (API-key billing only); default: 5.00",
|
|
6390
|
+
" --bundle=PATH Offline mode: diagnose from an extracted support bundle directory",
|
|
4907
6391
|
"",
|
|
4908
6392
|
" Environment variables:",
|
|
4909
|
-
" ANTHROPIC_API_KEY
|
|
4910
|
-
"
|
|
4911
|
-
"
|
|
4912
|
-
"
|
|
6393
|
+
" ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
|
|
6394
|
+
" ADMIN_AGENT_MAX_BUDGET Per-session budget cap in USD (overridden by --max-budget)",
|
|
6395
|
+
" KINETICA_URL Kinetica endpoint URL",
|
|
6396
|
+
" KINETICA_USER Admin username",
|
|
6397
|
+
" KINETICA_PASS Admin password",
|
|
4913
6398
|
""
|
|
4914
6399
|
];
|
|
4915
6400
|
process.stdout.write(lines.join("\n") + "\n");
|
|
@@ -4934,12 +6419,9 @@ async function main() {
|
|
|
4934
6419
|
return;
|
|
4935
6420
|
}
|
|
4936
6421
|
const forceLogin = args.includes("--login");
|
|
4937
|
-
const
|
|
4938
|
-
const
|
|
4939
|
-
const
|
|
4940
|
-
const loginOrgUUID = loginOrgArg?.split("=")[1];
|
|
4941
|
-
const modelArg = args.find((a) => a.startsWith("--model="));
|
|
4942
|
-
const modelValue = modelArg?.split("=")[1];
|
|
6422
|
+
const loginMethod = flagValue(args, "--login-method");
|
|
6423
|
+
const loginOrgUUID = flagValue(args, "--login-org");
|
|
6424
|
+
const modelValue = flagValue(args, "--model");
|
|
4943
6425
|
let model;
|
|
4944
6426
|
if (modelValue !== void 0) {
|
|
4945
6427
|
if (SUPPORTED_MODELS.includes(modelValue)) {
|
|
@@ -4954,13 +6436,39 @@ async function main() {
|
|
|
4954
6436
|
return;
|
|
4955
6437
|
}
|
|
4956
6438
|
}
|
|
6439
|
+
const budgetValue = flagValue(args, "--max-budget");
|
|
6440
|
+
let maxBudgetFlag;
|
|
6441
|
+
if (budgetValue !== void 0) {
|
|
6442
|
+
const parsed = Number(budgetValue);
|
|
6443
|
+
if (!isValidBudget(parsed)) {
|
|
6444
|
+
process.stderr.write(
|
|
6445
|
+
import_picocolors14.default.red(
|
|
6446
|
+
`Error: invalid --max-budget value "${budgetValue}". Use a positive number, e.g. --max-budget=10
|
|
6447
|
+
`
|
|
6448
|
+
)
|
|
6449
|
+
);
|
|
6450
|
+
process.exitCode = 1;
|
|
6451
|
+
return;
|
|
6452
|
+
}
|
|
6453
|
+
maxBudgetFlag = parsed;
|
|
6454
|
+
}
|
|
6455
|
+
const bundlePath = flagValue(args, "--bundle");
|
|
6456
|
+
if (bundlePath?.trim() === "") {
|
|
6457
|
+
process.stderr.write(
|
|
6458
|
+
import_picocolors14.default.red(
|
|
6459
|
+
"Error: --bundle requires a directory path, e.g. --bundle=/path/to/extracted-bundle\n"
|
|
6460
|
+
)
|
|
6461
|
+
);
|
|
6462
|
+
process.exitCode = 1;
|
|
6463
|
+
return;
|
|
6464
|
+
}
|
|
4957
6465
|
loadEnvFile();
|
|
4958
6466
|
printBanner();
|
|
4959
6467
|
if (model === void 0 && process.stdin.isTTY) {
|
|
4960
6468
|
model = await selectModel();
|
|
4961
6469
|
}
|
|
4962
6470
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4963
|
-
process.stderr.write(import_picocolors14.default.dim(`
|
|
6471
|
+
process.stderr.write(import_picocolors14.default.dim(`Model: ${effectiveModel}
|
|
4964
6472
|
`));
|
|
4965
6473
|
const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
|
|
4966
6474
|
if (authResult.method === "oauth") {
|
|
@@ -4970,9 +6478,54 @@ async function main() {
|
|
|
4970
6478
|
} else {
|
|
4971
6479
|
process.stderr.write(import_picocolors14.default.dim("Authenticated via API key\n"));
|
|
4972
6480
|
}
|
|
6481
|
+
const maxBudgetUsd = resolveMaxBudgetUsd(maxBudgetFlag);
|
|
6482
|
+
if (bundlePath !== void 0) {
|
|
6483
|
+
const result = await verifyBundle(bundlePath);
|
|
6484
|
+
if (!result.ok) {
|
|
6485
|
+
process.stderr.write(import_picocolors14.default.red(`Error: ${result.error}
|
|
6486
|
+
`));
|
|
6487
|
+
process.exitCode = 1;
|
|
6488
|
+
return;
|
|
6489
|
+
}
|
|
6490
|
+
if (result.missingExpected.length > 0) {
|
|
6491
|
+
process.stderr.write(
|
|
6492
|
+
import_picocolors14.default.yellow(
|
|
6493
|
+
`Warning: bundle is missing expected artifact(s): ${result.missingExpected.join(", ")}. Diagnosing with what is present.
|
|
6494
|
+
`
|
|
6495
|
+
)
|
|
6496
|
+
);
|
|
6497
|
+
}
|
|
6498
|
+
const live = await connectBestEffort();
|
|
6499
|
+
process.stderr.write(
|
|
6500
|
+
live ? import_picocolors14.default.dim("Live connection available \u2014 bundle + live verification enabled.\n") : import_picocolors14.default.dim("No reachable live connection \u2014 offline bundle analysis only.\n")
|
|
6501
|
+
);
|
|
6502
|
+
if (live?.kineticaVersion && result.kineticaVersion && live.kineticaVersion !== result.kineticaVersion) {
|
|
6503
|
+
process.stderr.write(
|
|
6504
|
+
import_picocolors14.default.yellow(
|
|
6505
|
+
`Warning: live cluster version (${live.kineticaVersion}) differs from the bundle's captured version (${result.kineticaVersion}). Reasoning over the bundle uses the captured version; the live cluster may have been upgraded since capture.
|
|
6506
|
+
`
|
|
6507
|
+
)
|
|
6508
|
+
);
|
|
6509
|
+
}
|
|
6510
|
+
await runAgent(
|
|
6511
|
+
live?.session,
|
|
6512
|
+
chooseBundleSessionVersion(result.kineticaVersion, live?.kineticaVersion),
|
|
6513
|
+
false,
|
|
6514
|
+
model,
|
|
6515
|
+
{
|
|
6516
|
+
authMethod: authResult.method,
|
|
6517
|
+
maxBudgetUsd,
|
|
6518
|
+
bundleSource: result.bundleSource
|
|
6519
|
+
}
|
|
6520
|
+
);
|
|
6521
|
+
return;
|
|
6522
|
+
}
|
|
4973
6523
|
const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
|
|
4974
6524
|
session = connectedSession;
|
|
4975
|
-
await runAgent(session, kineticaVersion, degraded, model
|
|
6525
|
+
await runAgent(session, kineticaVersion, degraded, model, {
|
|
6526
|
+
authMethod: authResult.method,
|
|
6527
|
+
maxBudgetUsd
|
|
6528
|
+
});
|
|
4976
6529
|
}
|
|
4977
6530
|
function getSession() {
|
|
4978
6531
|
return session;
|
|
@@ -4990,6 +6543,7 @@ if (process.env.NODE_ENV !== "test") {
|
|
|
4990
6543
|
}
|
|
4991
6544
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4992
6545
|
0 && (module.exports = {
|
|
6546
|
+
chooseBundleSessionVersion,
|
|
4993
6547
|
getSession,
|
|
4994
6548
|
main,
|
|
4995
6549
|
verbose
|