@kinetica/admin-agent 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin-agent.js
CHANGED
|
@@ -31,15 +31,16 @@ 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
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(cli_exports);
|
|
39
|
-
var
|
|
40
|
+
var import_picocolors15 = __toESM(require("picocolors"));
|
|
40
41
|
|
|
41
42
|
// src/cli/banner.ts
|
|
42
|
-
var
|
|
43
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
43
44
|
|
|
44
45
|
// src/cli/version.ts
|
|
45
46
|
var import_fs = require("fs");
|
|
@@ -61,6 +62,29 @@ function getVersion() {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// src/output/brand-colors.ts
|
|
66
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
67
|
+
var BRAND_PURPLE = [147, 51, 234];
|
|
68
|
+
var BRAND_PINK = [236, 72, 153];
|
|
69
|
+
var SOFTEN_TARGET = [210, 210, 210];
|
|
70
|
+
var SOFTEN_PURPLE = 0.55;
|
|
71
|
+
var SOFTEN_PINK = 0.45;
|
|
72
|
+
function soften([r, g, b], amount) {
|
|
73
|
+
const [tr, tg, tb] = SOFTEN_TARGET;
|
|
74
|
+
return [
|
|
75
|
+
Math.round(r + (tr - r) * amount),
|
|
76
|
+
Math.round(g + (tg - g) * amount),
|
|
77
|
+
Math.round(b + (tb - b) * amount)
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
var TEXT_PURPLE = soften(BRAND_PURPLE, SOFTEN_PURPLE);
|
|
81
|
+
var TEXT_PINK = soften(BRAND_PINK, SOFTEN_PINK);
|
|
82
|
+
function truecolor([r, g, b]) {
|
|
83
|
+
return (s) => import_picocolors.default.isColorSupported ? `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m` : s;
|
|
84
|
+
}
|
|
85
|
+
var purple = truecolor(TEXT_PURPLE);
|
|
86
|
+
var pink = truecolor(TEXT_PINK);
|
|
87
|
+
|
|
64
88
|
// src/cli/banner.ts
|
|
65
89
|
var LOGO = ` \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557
|
|
66
90
|
\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
@@ -84,14 +108,12 @@ function dimRgb(color, factor) {
|
|
|
84
108
|
}
|
|
85
109
|
var SHADOW_CHARS = /* @__PURE__ */ new Set(["\u2557", "\u2554", "\u2551", "\u255D", "\u255A", "\u2550"]);
|
|
86
110
|
var DIM_FACTOR = 0.4;
|
|
87
|
-
function gradientize(
|
|
88
|
-
const PURPLE = [147, 51, 234];
|
|
89
|
-
const HOT_PINK = [236, 72, 153];
|
|
111
|
+
function gradientize(text2) {
|
|
90
112
|
const RESET = "\x1B[0m";
|
|
91
|
-
const lines =
|
|
113
|
+
const lines = text2.split("\n");
|
|
92
114
|
const maxIdx = Math.max(lines.length - 1, 1);
|
|
93
115
|
return lines.map((line, i) => {
|
|
94
|
-
const bright = lerpRgb(
|
|
116
|
+
const bright = lerpRgb(BRAND_PURPLE, BRAND_PINK, i / maxIdx);
|
|
95
117
|
const dim = dimRgb(bright, DIM_FACTOR);
|
|
96
118
|
let result = "";
|
|
97
119
|
let currentMode = null;
|
|
@@ -109,20 +131,53 @@ function gradientize(text) {
|
|
|
109
131
|
}
|
|
110
132
|
function printBanner(model) {
|
|
111
133
|
const version = getVersion();
|
|
112
|
-
const subtitle = `admin-agent ${
|
|
134
|
+
const subtitle = `admin-agent ${import_picocolors2.default.dim(`v${version}`)}`;
|
|
113
135
|
const header = model ? `${subtitle}
|
|
114
|
-
${
|
|
136
|
+
${import_picocolors2.default.dim(`Model: ${model}`)}` : subtitle;
|
|
115
137
|
process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
|
|
116
138
|
return subtitle;
|
|
117
139
|
}
|
|
118
140
|
|
|
119
|
-
// src/
|
|
120
|
-
var
|
|
141
|
+
// src/output/themed-prompts.ts
|
|
142
|
+
var import_prompts = require("@inquirer/prompts");
|
|
143
|
+
|
|
144
|
+
// src/output/prompt-theme.ts
|
|
145
|
+
var PROMPT_THEME = {
|
|
146
|
+
style: {
|
|
147
|
+
answer: pink,
|
|
148
|
+
highlight: pink
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/output/themed-prompts.ts
|
|
153
|
+
function input(...args) {
|
|
154
|
+
const [config, ...rest] = args;
|
|
155
|
+
return (0, import_prompts.input)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
156
|
+
}
|
|
157
|
+
function confirm(...args) {
|
|
158
|
+
const [config, ...rest] = args;
|
|
159
|
+
return (0, import_prompts.confirm)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
160
|
+
}
|
|
161
|
+
function password(...args) {
|
|
162
|
+
const [config, ...rest] = args;
|
|
163
|
+
return (0, import_prompts.password)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
164
|
+
}
|
|
165
|
+
function select(...args) {
|
|
166
|
+
const [config, ...rest] = args;
|
|
167
|
+
return (0, import_prompts.select)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
168
|
+
}
|
|
169
|
+
function search(...args) {
|
|
170
|
+
const [config, ...rest] = args;
|
|
171
|
+
return (0, import_prompts.search)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
172
|
+
}
|
|
173
|
+
function checkbox(...args) {
|
|
174
|
+
const [config, ...rest] = args;
|
|
175
|
+
return (0, import_prompts.checkbox)({ theme: PROMPT_THEME, ...config }, ...rest);
|
|
176
|
+
}
|
|
121
177
|
|
|
122
178
|
// src/agent/run-agent.ts
|
|
123
|
-
var
|
|
124
|
-
var
|
|
125
|
-
var import_picocolors8 = __toESM(require("picocolors"));
|
|
179
|
+
var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
|
|
180
|
+
var import_picocolors9 = __toESM(require("picocolors"));
|
|
126
181
|
|
|
127
182
|
// src/agent/diagnostic-sql.ts
|
|
128
183
|
function has(columns, name) {
|
|
@@ -499,7 +554,7 @@ var BUILDER_REGISTRY = [
|
|
|
499
554
|
// src/tools/index.ts
|
|
500
555
|
var import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
|
|
501
556
|
var import_zod16 = require("zod");
|
|
502
|
-
var
|
|
557
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
503
558
|
|
|
504
559
|
// src/approval/registry.ts
|
|
505
560
|
var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
|
|
@@ -556,7 +611,7 @@ function formatTableArray(rows) {
|
|
|
556
611
|
const colWidths = headers.map(
|
|
557
612
|
(h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
|
|
558
613
|
);
|
|
559
|
-
const pad = (
|
|
614
|
+
const pad = (text2, col) => text2.padEnd(colWidths[col]);
|
|
560
615
|
const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
|
|
561
616
|
const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
|
|
562
617
|
const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
|
|
@@ -585,13 +640,13 @@ var DEFAULT_TRUNCATION = {
|
|
|
585
640
|
};
|
|
586
641
|
|
|
587
642
|
// src/output/truncate.ts
|
|
588
|
-
function truncateOutput(
|
|
589
|
-
if (
|
|
590
|
-
const lines =
|
|
643
|
+
function truncateOutput(text2, options = DEFAULT_TRUNCATION) {
|
|
644
|
+
if (text2 === "") return "";
|
|
645
|
+
const lines = text2.split("\n");
|
|
591
646
|
const { headLines, tailLines } = options;
|
|
592
647
|
const threshold = headLines + tailLines;
|
|
593
648
|
if (lines.length <= threshold) {
|
|
594
|
-
return
|
|
649
|
+
return text2;
|
|
595
650
|
}
|
|
596
651
|
const truncatedCount = lines.length - headLines - tailLines;
|
|
597
652
|
const head = lines.slice(0, headLines);
|
|
@@ -853,7 +908,7 @@ var GetSystemPropertiesSchema = import_zod.z.object({
|
|
|
853
908
|
category: import_zod.z.string().optional(),
|
|
854
909
|
key_pattern: import_zod.z.string().optional()
|
|
855
910
|
});
|
|
856
|
-
async function getSystemProperties(session2,
|
|
911
|
+
async function getSystemProperties(session2, input2) {
|
|
857
912
|
try {
|
|
858
913
|
const response = await session2.makeRequest("/show/system/properties", {
|
|
859
914
|
options: {}
|
|
@@ -883,7 +938,7 @@ async function getSystemProperties(session2, input5) {
|
|
|
883
938
|
const inner = parseDataStr(parsed.data_str, raw);
|
|
884
939
|
if (!inner.ok) return inner;
|
|
885
940
|
const propertyMap = inner.data?.property_map ?? {};
|
|
886
|
-
const filteredMap = applyFilters(propertyMap,
|
|
941
|
+
const filteredMap = applyFilters(propertyMap, input2);
|
|
887
942
|
return {
|
|
888
943
|
ok: true,
|
|
889
944
|
data: flatObjectToRows(filteredMap, "property", "value"),
|
|
@@ -899,8 +954,8 @@ async function getSystemProperties(session2, input5) {
|
|
|
899
954
|
};
|
|
900
955
|
}
|
|
901
956
|
}
|
|
902
|
-
function applyFilters(propertyMap,
|
|
903
|
-
const { category, key_pattern } =
|
|
957
|
+
function applyFilters(propertyMap, input2) {
|
|
958
|
+
const { category, key_pattern } = input2;
|
|
904
959
|
if (category === void 0 && key_pattern === void 0) {
|
|
905
960
|
return propertyMap;
|
|
906
961
|
}
|
|
@@ -1136,7 +1191,7 @@ var GetLogsSchema = import_zod2.z.object({
|
|
|
1136
1191
|
message: "duration and start_time/end_time are mutually exclusive -- use one or the other, not both"
|
|
1137
1192
|
}
|
|
1138
1193
|
);
|
|
1139
|
-
function buildStubResponse(
|
|
1194
|
+
function buildStubResponse(input2) {
|
|
1140
1195
|
return {
|
|
1141
1196
|
ok: true,
|
|
1142
1197
|
data: {
|
|
@@ -1144,39 +1199,39 @@ function buildStubResponse(input5) {
|
|
|
1144
1199
|
endpoint: "/admin/show/logs",
|
|
1145
1200
|
status: "stub",
|
|
1146
1201
|
requested_params: {
|
|
1147
|
-
source:
|
|
1148
|
-
min_severity:
|
|
1149
|
-
duration:
|
|
1150
|
-
start_time:
|
|
1151
|
-
end_time:
|
|
1152
|
-
node_id:
|
|
1153
|
-
limit:
|
|
1202
|
+
source: input2.source,
|
|
1203
|
+
min_severity: input2.min_severity,
|
|
1204
|
+
duration: input2.duration,
|
|
1205
|
+
start_time: input2.start_time,
|
|
1206
|
+
end_time: input2.end_time,
|
|
1207
|
+
node_id: input2.node_id,
|
|
1208
|
+
limit: input2.limit
|
|
1154
1209
|
}
|
|
1155
1210
|
}
|
|
1156
1211
|
};
|
|
1157
1212
|
}
|
|
1158
|
-
async function getLogs(session2,
|
|
1213
|
+
async function getLogs(session2, input2) {
|
|
1159
1214
|
const params = {
|
|
1160
|
-
source:
|
|
1161
|
-
severity:
|
|
1162
|
-
limit:
|
|
1215
|
+
source: input2.source,
|
|
1216
|
+
severity: input2.min_severity,
|
|
1217
|
+
limit: input2.limit
|
|
1163
1218
|
};
|
|
1164
|
-
if (
|
|
1165
|
-
params.duration =
|
|
1219
|
+
if (input2.duration !== void 0) {
|
|
1220
|
+
params.duration = input2.duration;
|
|
1166
1221
|
}
|
|
1167
|
-
if (
|
|
1168
|
-
params.start_time =
|
|
1222
|
+
if (input2.start_time !== void 0) {
|
|
1223
|
+
params.start_time = input2.start_time;
|
|
1169
1224
|
}
|
|
1170
|
-
if (
|
|
1171
|
-
params.end_time =
|
|
1225
|
+
if (input2.end_time !== void 0) {
|
|
1226
|
+
params.end_time = input2.end_time;
|
|
1172
1227
|
}
|
|
1173
|
-
if (
|
|
1174
|
-
params.node_id =
|
|
1228
|
+
if (input2.node_id !== void 0) {
|
|
1229
|
+
params.node_id = input2.node_id;
|
|
1175
1230
|
}
|
|
1176
1231
|
try {
|
|
1177
1232
|
const response = await session2.makeRequest("/admin/show/logs", params);
|
|
1178
1233
|
if (!response.ok) {
|
|
1179
|
-
return buildStubResponse(
|
|
1234
|
+
return buildStubResponse(input2);
|
|
1180
1235
|
}
|
|
1181
1236
|
const raw = await response.text();
|
|
1182
1237
|
try {
|
|
@@ -1186,10 +1241,10 @@ async function getLogs(session2, input5) {
|
|
|
1186
1241
|
data
|
|
1187
1242
|
};
|
|
1188
1243
|
} catch {
|
|
1189
|
-
return buildStubResponse(
|
|
1244
|
+
return buildStubResponse(input2);
|
|
1190
1245
|
}
|
|
1191
1246
|
} catch {
|
|
1192
|
-
return buildStubResponse(
|
|
1247
|
+
return buildStubResponse(input2);
|
|
1193
1248
|
}
|
|
1194
1249
|
}
|
|
1195
1250
|
|
|
@@ -1221,7 +1276,7 @@ var DEFAULT_SCRUB_PATTERNS = [
|
|
|
1221
1276
|
];
|
|
1222
1277
|
function scrubCredentials(content, patterns = DEFAULT_SCRUB_PATTERNS) {
|
|
1223
1278
|
const configRedacted = redactConfigSecrets(content);
|
|
1224
|
-
return patterns.reduce((
|
|
1279
|
+
return patterns.reduce((text2, pattern) => text2.replace(pattern, "[REDACTED]"), configRedacted);
|
|
1225
1280
|
}
|
|
1226
1281
|
|
|
1227
1282
|
// src/tools/rest/show-configuration.ts
|
|
@@ -1334,12 +1389,12 @@ var ResourceGroupsSchema = import_zod4.z.object({
|
|
|
1334
1389
|
names: import_zod4.z.array(import_zod4.z.string()).optional().default([""]),
|
|
1335
1390
|
show_tier_usage: import_zod4.z.boolean().optional()
|
|
1336
1391
|
});
|
|
1337
|
-
async function getResourceGroups(session2,
|
|
1392
|
+
async function getResourceGroups(session2, input2) {
|
|
1338
1393
|
try {
|
|
1339
1394
|
const response = await session2.makeRequest("/show/resourcegroups", {
|
|
1340
|
-
names:
|
|
1395
|
+
names: input2.names,
|
|
1341
1396
|
options: {
|
|
1342
|
-
show_tier_usage: String(
|
|
1397
|
+
show_tier_usage: String(input2.show_tier_usage ?? false),
|
|
1343
1398
|
show_default_values: "true",
|
|
1344
1399
|
show_default_group: "true"
|
|
1345
1400
|
}
|
|
@@ -1390,18 +1445,18 @@ var VerifyDbSchema = import_zod5.z.object({
|
|
|
1390
1445
|
verify_persist: import_zod5.z.boolean().optional(),
|
|
1391
1446
|
verify_rank0: import_zod5.z.boolean().optional()
|
|
1392
1447
|
});
|
|
1393
|
-
async function verifyDb(session2,
|
|
1448
|
+
async function verifyDb(session2, input2) {
|
|
1394
1449
|
const options = {
|
|
1395
1450
|
concurrent_safe: "true"
|
|
1396
1451
|
};
|
|
1397
|
-
if (
|
|
1398
|
-
options.verify_nulls = String(
|
|
1452
|
+
if (input2.verify_nulls !== void 0) {
|
|
1453
|
+
options.verify_nulls = String(input2.verify_nulls);
|
|
1399
1454
|
}
|
|
1400
|
-
if (
|
|
1401
|
-
options.verify_persist = String(
|
|
1455
|
+
if (input2.verify_persist !== void 0) {
|
|
1456
|
+
options.verify_persist = String(input2.verify_persist);
|
|
1402
1457
|
}
|
|
1403
|
-
if (
|
|
1404
|
-
options.verify_rank0 = String(
|
|
1458
|
+
if (input2.verify_rank0 !== void 0) {
|
|
1459
|
+
options.verify_rank0 = String(input2.verify_rank0);
|
|
1405
1460
|
}
|
|
1406
1461
|
try {
|
|
1407
1462
|
const response = await session2.makeRequest("/admin/verifydb", { options });
|
|
@@ -1454,10 +1509,10 @@ var import_zod6 = require("zod");
|
|
|
1454
1509
|
var ShowSecuritySchema = import_zod6.z.object({
|
|
1455
1510
|
names: import_zod6.z.array(import_zod6.z.string()).optional().default([""])
|
|
1456
1511
|
});
|
|
1457
|
-
async function showSecurity(session2,
|
|
1512
|
+
async function showSecurity(session2, input2) {
|
|
1458
1513
|
try {
|
|
1459
1514
|
const response = await session2.makeRequest("/show/security", {
|
|
1460
|
-
names:
|
|
1515
|
+
names: input2.names,
|
|
1461
1516
|
options: {}
|
|
1462
1517
|
});
|
|
1463
1518
|
if (!response.ok) {
|
|
@@ -1553,10 +1608,10 @@ var ShowTableSchema = import_zod7.z.object({
|
|
|
1553
1608
|
get_access_data: import_zod7.z.boolean().optional(),
|
|
1554
1609
|
get_column_info: import_zod7.z.boolean().optional()
|
|
1555
1610
|
});
|
|
1556
|
-
function resolveColumnInfoOption(
|
|
1557
|
-
if (
|
|
1558
|
-
if (
|
|
1559
|
-
return
|
|
1611
|
+
function resolveColumnInfoOption(input2) {
|
|
1612
|
+
if (input2.get_column_info === true) return "true";
|
|
1613
|
+
if (input2.get_column_info === false) return "false";
|
|
1614
|
+
return input2.table_name !== "" ? "true" : "false";
|
|
1560
1615
|
}
|
|
1561
1616
|
function parseColumnProperties(propertiesJson) {
|
|
1562
1617
|
try {
|
|
@@ -1622,19 +1677,19 @@ async function fetchIndexes(session2, tableName) {
|
|
|
1622
1677
|
return [];
|
|
1623
1678
|
}
|
|
1624
1679
|
}
|
|
1625
|
-
async function showTable(session2,
|
|
1680
|
+
async function showTable(session2, input2) {
|
|
1626
1681
|
const options = {
|
|
1627
|
-
get_sizes: String(
|
|
1682
|
+
get_sizes: String(input2.get_sizes ?? true),
|
|
1628
1683
|
show_children: "false",
|
|
1629
1684
|
no_error_if_not_exists: "true",
|
|
1630
|
-
get_column_info: resolveColumnInfoOption(
|
|
1685
|
+
get_column_info: resolveColumnInfoOption(input2)
|
|
1631
1686
|
};
|
|
1632
|
-
if (
|
|
1633
|
-
options.get_access_data = String(
|
|
1687
|
+
if (input2.get_access_data !== void 0) {
|
|
1688
|
+
options.get_access_data = String(input2.get_access_data);
|
|
1634
1689
|
}
|
|
1635
1690
|
try {
|
|
1636
1691
|
const response = await session2.makeRequest("/show/table", {
|
|
1637
|
-
table_name:
|
|
1692
|
+
table_name: input2.table_name,
|
|
1638
1693
|
options
|
|
1639
1694
|
});
|
|
1640
1695
|
if (!response.ok) {
|
|
@@ -1686,7 +1741,7 @@ async function showTable(session2, input5) {
|
|
|
1686
1741
|
properties: properties[0] ?? ""
|
|
1687
1742
|
};
|
|
1688
1743
|
const columns = buildColumnEntries(typeSchemas?.[0], typeSchemas ? properties[0] : void 0);
|
|
1689
|
-
const indexes = await fetchIndexes(session2,
|
|
1744
|
+
const indexes = await fetchIndexes(session2, input2.table_name);
|
|
1690
1745
|
return {
|
|
1691
1746
|
ok: true,
|
|
1692
1747
|
data: { table, columns, indexes }
|
|
@@ -1721,16 +1776,16 @@ var ResourceObjectsSchema = import_zod8.z.object({
|
|
|
1721
1776
|
order_by: import_zod8.z.string().optional(),
|
|
1722
1777
|
limit: import_zod8.z.number().int().min(1).max(1e4).optional().default(100)
|
|
1723
1778
|
});
|
|
1724
|
-
async function getResourceObjects(session2,
|
|
1779
|
+
async function getResourceObjects(session2, input2) {
|
|
1725
1780
|
const options = {
|
|
1726
|
-
table_names:
|
|
1727
|
-
limit: String(
|
|
1781
|
+
table_names: input2.table_names ?? "*",
|
|
1782
|
+
limit: String(input2.limit ?? 100)
|
|
1728
1783
|
};
|
|
1729
|
-
if (
|
|
1730
|
-
options.tiers =
|
|
1784
|
+
if (input2.tiers !== void 0) {
|
|
1785
|
+
options.tiers = input2.tiers;
|
|
1731
1786
|
}
|
|
1732
|
-
if (
|
|
1733
|
-
options.order_by =
|
|
1787
|
+
if (input2.order_by !== void 0) {
|
|
1788
|
+
options.order_by = input2.order_by;
|
|
1734
1789
|
}
|
|
1735
1790
|
try {
|
|
1736
1791
|
const response = await session2.makeRequest("/show/resource/objects", {
|
|
@@ -2134,8 +2189,8 @@ function computeVerification(requestedMap, afterState) {
|
|
|
2134
2189
|
}
|
|
2135
2190
|
return "confirmed";
|
|
2136
2191
|
}
|
|
2137
|
-
async function alterSystemProperties(session2,
|
|
2138
|
-
const requestedKeys = Object.keys(
|
|
2192
|
+
async function alterSystemProperties(session2, input2) {
|
|
2193
|
+
const requestedKeys = Object.keys(input2.property_updates_map);
|
|
2139
2194
|
const disallowed = findDisallowedProperties(requestedKeys);
|
|
2140
2195
|
if (disallowed.length > 0) {
|
|
2141
2196
|
return {
|
|
@@ -2150,7 +2205,7 @@ async function alterSystemProperties(session2, input5) {
|
|
|
2150
2205
|
let rawText;
|
|
2151
2206
|
try {
|
|
2152
2207
|
mutationResponse = await session2.makeRequest("/alter/system/properties", {
|
|
2153
|
-
property_updates_map:
|
|
2208
|
+
property_updates_map: input2.property_updates_map
|
|
2154
2209
|
});
|
|
2155
2210
|
rawText = await mutationResponse.text();
|
|
2156
2211
|
} catch (error) {
|
|
@@ -2212,7 +2267,7 @@ async function alterSystemProperties(session2, input5) {
|
|
|
2212
2267
|
afterState = Object.fromEntries(
|
|
2213
2268
|
requestedKeys.filter((key) => Object.prototype.hasOwnProperty.call(verifyPropertyMap, key)).map((key) => [key, verifyPropertyMap[key]])
|
|
2214
2269
|
);
|
|
2215
|
-
verification = computeVerification(
|
|
2270
|
+
verification = computeVerification(input2.property_updates_map, afterState);
|
|
2216
2271
|
}
|
|
2217
2272
|
}
|
|
2218
2273
|
} catch {
|
|
@@ -2356,29 +2411,29 @@ async function readShardState(session2) {
|
|
|
2356
2411
|
return {};
|
|
2357
2412
|
}
|
|
2358
2413
|
}
|
|
2359
|
-
async function adminRebalance(session2,
|
|
2414
|
+
async function adminRebalance(session2, input2) {
|
|
2360
2415
|
const beforeState = await readShardState(session2);
|
|
2361
2416
|
const options = {};
|
|
2362
|
-
if (
|
|
2363
|
-
options.rebalance_sharded_data = String(
|
|
2417
|
+
if (input2.rebalance_sharded_data !== void 0) {
|
|
2418
|
+
options.rebalance_sharded_data = String(input2.rebalance_sharded_data);
|
|
2364
2419
|
}
|
|
2365
|
-
if (
|
|
2366
|
-
options.rebalance_unsharded_data = String(
|
|
2420
|
+
if (input2.rebalance_unsharded_data !== void 0) {
|
|
2421
|
+
options.rebalance_unsharded_data = String(input2.rebalance_unsharded_data);
|
|
2367
2422
|
}
|
|
2368
|
-
if (
|
|
2369
|
-
options.table_includes =
|
|
2423
|
+
if (input2.table_includes !== void 0) {
|
|
2424
|
+
options.table_includes = input2.table_includes;
|
|
2370
2425
|
}
|
|
2371
|
-
if (
|
|
2372
|
-
options.table_excludes =
|
|
2426
|
+
if (input2.table_excludes !== void 0) {
|
|
2427
|
+
options.table_excludes = input2.table_excludes;
|
|
2373
2428
|
}
|
|
2374
|
-
if (
|
|
2375
|
-
options.aggressiveness = String(
|
|
2429
|
+
if (input2.aggressiveness !== void 0) {
|
|
2430
|
+
options.aggressiveness = String(input2.aggressiveness);
|
|
2376
2431
|
}
|
|
2377
|
-
if (
|
|
2378
|
-
options.compact_after_rebalance = String(
|
|
2432
|
+
if (input2.compact_after_rebalance !== void 0) {
|
|
2433
|
+
options.compact_after_rebalance = String(input2.compact_after_rebalance);
|
|
2379
2434
|
}
|
|
2380
|
-
if (
|
|
2381
|
-
options.compact_only = String(
|
|
2435
|
+
if (input2.compact_only !== void 0) {
|
|
2436
|
+
options.compact_only = String(input2.compact_only);
|
|
2382
2437
|
}
|
|
2383
2438
|
try {
|
|
2384
2439
|
const response = await session2.makeRequest("/admin/rebalance", { options });
|
|
@@ -2451,7 +2506,7 @@ async function readCurrentConfig(session2) {
|
|
|
2451
2506
|
return void 0;
|
|
2452
2507
|
}
|
|
2453
2508
|
}
|
|
2454
|
-
async function alterConfiguration(session2,
|
|
2509
|
+
async function alterConfiguration(session2, input2) {
|
|
2455
2510
|
if (!session2.makeRequestToPort) {
|
|
2456
2511
|
return {
|
|
2457
2512
|
ok: false,
|
|
@@ -2467,7 +2522,7 @@ async function alterConfiguration(session2, input5) {
|
|
|
2467
2522
|
let rawText;
|
|
2468
2523
|
try {
|
|
2469
2524
|
mutationResponse = await session2.makeRequestToPort(hmPort, "/admin/alter/configuration", {
|
|
2470
|
-
config_string:
|
|
2525
|
+
config_string: input2.config_string
|
|
2471
2526
|
});
|
|
2472
2527
|
rawText = await mutationResponse.text();
|
|
2473
2528
|
} catch (error) {
|
|
@@ -2522,28 +2577,26 @@ async function alterConfiguration(session2, input5) {
|
|
|
2522
2577
|
|
|
2523
2578
|
// src/tools/mutation/alter-table-columns.ts
|
|
2524
2579
|
var import_zod15 = require("zod");
|
|
2525
|
-
var
|
|
2526
|
-
var import_picocolors3 = __toESM(require("picocolors"));
|
|
2580
|
+
var import_picocolors4 = __toESM(require("picocolors"));
|
|
2527
2581
|
var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
|
|
2528
2582
|
|
|
2529
2583
|
// src/approval/checklist.ts
|
|
2530
|
-
var
|
|
2531
|
-
var
|
|
2532
|
-
var DIVIDER = import_picocolors2.default.dim("\u2500".repeat(60));
|
|
2584
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
2585
|
+
var DIVIDER = import_picocolors3.default.dim("\u2500".repeat(60));
|
|
2533
2586
|
function renderChecklist(header, summary, items) {
|
|
2534
2587
|
const lines = [
|
|
2535
2588
|
"",
|
|
2536
2589
|
DIVIDER,
|
|
2537
|
-
` ${
|
|
2590
|
+
` ${import_picocolors3.default.bold(import_picocolors3.default.yellow(header))}`,
|
|
2538
2591
|
"",
|
|
2539
|
-
` ${
|
|
2592
|
+
` ${import_picocolors3.default.dim("Summary:")} ${summary}`,
|
|
2540
2593
|
"",
|
|
2541
|
-
` ${
|
|
2594
|
+
` ${import_picocolors3.default.bold(`${items.length} proposed column change(s):`)}`,
|
|
2542
2595
|
""
|
|
2543
2596
|
];
|
|
2544
2597
|
for (let i = 0; i < items.length; i++) {
|
|
2545
2598
|
const item = items[i];
|
|
2546
|
-
lines.push(` ${
|
|
2599
|
+
lines.push(` ${import_picocolors3.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors3.default.dim(item.description)}`);
|
|
2547
2600
|
}
|
|
2548
2601
|
lines.push("", DIVIDER, "");
|
|
2549
2602
|
return lines.join("\n");
|
|
@@ -2552,7 +2605,7 @@ async function showChecklist(header, summary, items) {
|
|
|
2552
2605
|
const panel = renderChecklist(header, summary, items);
|
|
2553
2606
|
process.stderr.write(panel);
|
|
2554
2607
|
try {
|
|
2555
|
-
const selected = await
|
|
2608
|
+
const selected = await checkbox({
|
|
2556
2609
|
message: "Select columns to alter (space to toggle, enter to confirm):",
|
|
2557
2610
|
choices: items.map((item, i) => ({
|
|
2558
2611
|
value: i,
|
|
@@ -2587,12 +2640,12 @@ function buildAlterTableSql(tableName, columns) {
|
|
|
2587
2640
|
return `ALTER TABLE ${tableName}
|
|
2588
2641
|
${clauses.join(",\n")}`;
|
|
2589
2642
|
}
|
|
2590
|
-
var SQL_DIVIDER =
|
|
2643
|
+
var SQL_DIVIDER = import_picocolors4.default.dim("\u2500".repeat(60));
|
|
2591
2644
|
async function confirmSqlExecution(sql) {
|
|
2592
2645
|
const panel = [
|
|
2593
2646
|
"",
|
|
2594
2647
|
SQL_DIVIDER,
|
|
2595
|
-
` ${
|
|
2648
|
+
` ${import_picocolors4.default.bold(import_picocolors4.default.yellow("Generated SQL:"))}`,
|
|
2596
2649
|
"",
|
|
2597
2650
|
sql.split("\n").map((line) => ` ${line}`).join("\n"),
|
|
2598
2651
|
"",
|
|
@@ -2601,7 +2654,7 @@ async function confirmSqlExecution(sql) {
|
|
|
2601
2654
|
].join("\n");
|
|
2602
2655
|
process.stderr.write(panel);
|
|
2603
2656
|
try {
|
|
2604
|
-
const response = await
|
|
2657
|
+
const response = await input({ message: "Execute? (y/n):" });
|
|
2605
2658
|
return response.trim().toLowerCase() === "y";
|
|
2606
2659
|
} catch {
|
|
2607
2660
|
return false;
|
|
@@ -2714,7 +2767,7 @@ function fingerprint(value) {
|
|
|
2714
2767
|
}
|
|
2715
2768
|
function scrubCredentialPatterns(value) {
|
|
2716
2769
|
return CREDENTIAL_PATTERNS.reduce(
|
|
2717
|
-
(
|
|
2770
|
+
(text2, { regex, replacement }) => text2.replace(regex, replacement),
|
|
2718
2771
|
value
|
|
2719
2772
|
);
|
|
2720
2773
|
}
|
|
@@ -2739,8 +2792,8 @@ function redactNamedField(key, value) {
|
|
|
2739
2792
|
}
|
|
2740
2793
|
return redactValue(value);
|
|
2741
2794
|
}
|
|
2742
|
-
function redactAuditInput(
|
|
2743
|
-
return Object.fromEntries(Object.entries(
|
|
2795
|
+
function redactAuditInput(input2) {
|
|
2796
|
+
return Object.fromEntries(Object.entries(input2).map(([k, v]) => [k, redactNamedField(k, v)]));
|
|
2744
2797
|
}
|
|
2745
2798
|
|
|
2746
2799
|
// src/tools/index.ts
|
|
@@ -2769,24 +2822,22 @@ var DIAGNOSTIC_TOOL_NAMES = [
|
|
|
2769
2822
|
"kinetica_resource_objects",
|
|
2770
2823
|
"kinetica_host_manager_status"
|
|
2771
2824
|
];
|
|
2772
|
-
function createDiagnosticRegistry() {
|
|
2773
|
-
return DIAGNOSTIC_TOOL_NAMES.reduce(
|
|
2774
|
-
(registry, name) => registry.registerReadOnlyTool(name),
|
|
2775
|
-
createRegistry()
|
|
2776
|
-
);
|
|
2777
|
-
}
|
|
2778
2825
|
function applyOutputPipeline(result) {
|
|
2779
2826
|
const payload = result.ok ? result.data : result;
|
|
2780
|
-
|
|
2827
|
+
const body = formatOutput(payload);
|
|
2828
|
+
const withNote = result.ok && result.note ? `${result.note}
|
|
2829
|
+
|
|
2830
|
+
${body}` : body;
|
|
2831
|
+
return truncateOutput(withNote);
|
|
2781
2832
|
}
|
|
2782
|
-
function logMutationAudit(toolName, result,
|
|
2783
|
-
const statusLabel = result.ok ?
|
|
2784
|
-
const redacted = redactAuditInput(
|
|
2833
|
+
function logMutationAudit(toolName, result, input2) {
|
|
2834
|
+
const statusLabel = result.ok ? import_picocolors5.default.bold(import_picocolors5.default.green("EXECUTED")) : import_picocolors5.default.bold(import_picocolors5.default.red("FAILED"));
|
|
2835
|
+
const redacted = redactAuditInput(input2);
|
|
2785
2836
|
const inputSummary = Object.entries(redacted).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
2786
2837
|
const displayName = formatToolName(toolName);
|
|
2787
2838
|
process.stderr.write(
|
|
2788
|
-
` ${
|
|
2789
|
-
${
|
|
2839
|
+
` ${import_picocolors5.default.dim("MUTATION")} ${statusLabel} ${displayName}
|
|
2840
|
+
${import_picocolors5.default.dim(inputSummary)}
|
|
2790
2841
|
|
|
2791
2842
|
`
|
|
2792
2843
|
);
|
|
@@ -3191,6 +3242,26 @@ function buildEvidenceChecklist() {
|
|
|
3191
3242
|
].join("\n");
|
|
3192
3243
|
}
|
|
3193
3244
|
|
|
3245
|
+
// src/agent/prompt-sections.ts
|
|
3246
|
+
function buildFailurePatternsSection(playbooks) {
|
|
3247
|
+
if (!playbooks || playbooks.length === 0) return "";
|
|
3248
|
+
const entries = playbooks.map((p) => `**${p.title}:**
|
|
3249
|
+
|
|
3250
|
+
${p.body}`).join("\n\n");
|
|
3251
|
+
return `### Common Failure Patterns
|
|
3252
|
+
|
|
3253
|
+
${entries}`;
|
|
3254
|
+
}
|
|
3255
|
+
function buildReferenceSection(references) {
|
|
3256
|
+
if (!references || references.length === 0) return "";
|
|
3257
|
+
const entries = references.map((r) => `**${r.title}:**
|
|
3258
|
+
|
|
3259
|
+
${r.body}`).join("\n\n");
|
|
3260
|
+
return `### Reference Knowledge
|
|
3261
|
+
|
|
3262
|
+
${entries}`;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3194
3265
|
// src/agent/report-template.ts
|
|
3195
3266
|
var import_node_fs2 = require("fs");
|
|
3196
3267
|
var import_node_path2 = require("path");
|
|
@@ -3301,25 +3372,7 @@ ${sqls.join("\n\n")}
|
|
|
3301
3372
|
}
|
|
3302
3373
|
return result.trimEnd();
|
|
3303
3374
|
}
|
|
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) {
|
|
3375
|
+
function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded, bundleCapability, bundleReferences) {
|
|
3323
3376
|
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
3377
|
const t = "`";
|
|
3325
3378
|
const degradedSection = degraded ? `
|
|
@@ -3352,6 +3405,21 @@ function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, reference
|
|
|
3352
3405
|
- Do NOT attempt Round 4 (mutations) or Round 5 (verification) \u2014 the DB engine must be running first
|
|
3353
3406
|
|
|
3354
3407
|
` : "";
|
|
3408
|
+
const bundleSection = bundleCapability === void 0 ? "" : `
|
|
3409
|
+
---
|
|
3410
|
+
|
|
3411
|
+
## Support Bundle Capability
|
|
3412
|
+
|
|
3413
|
+
${bundleCapability === "attached" ? `A Kinetica support bundle (offline ${t}gpudb_sysinfo${t} snapshot) IS attached to this session. You now have **two complementary evidence sources**:
|
|
3414
|
+
- **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}.
|
|
3415
|
+
- **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.
|
|
3416
|
+
|
|
3417
|
+
**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.`}
|
|
3418
|
+
|
|
3419
|
+
**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.
|
|
3420
|
+
${bundleReferences && bundleReferences.length > 0 ? `
|
|
3421
|
+
${buildReferenceSection(bundleReferences)}
|
|
3422
|
+
` : ""}`;
|
|
3355
3423
|
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
3424
|
|
|
3357
3425
|
${versionSection}
|
|
@@ -3479,7 +3547,7 @@ ${t}kinetica_show_table${t} (e.g., ${t}is_shard_key${t}, ${t}is_primary_key${t},
|
|
|
3479
3547
|
${buildFailurePatternsSection(playbooks)}
|
|
3480
3548
|
|
|
3481
3549
|
${buildReferenceSection(references)}
|
|
3482
|
-
|
|
3550
|
+
${bundleSection}
|
|
3483
3551
|
---
|
|
3484
3552
|
|
|
3485
3553
|
## Analysis Instructions
|
|
@@ -3536,10 +3604,14 @@ Include specific, actionable remediation steps tied to your findings. Structure
|
|
|
3536
3604
|
|
|
3537
3605
|
## Post-Report Behavior
|
|
3538
3606
|
|
|
3539
|
-
1.
|
|
3540
|
-
2. After the report
|
|
3541
|
-
|
|
3542
|
-
|
|
3607
|
+
1. Present the finished report in your response so the operator can read it.
|
|
3608
|
+
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.
|
|
3609
|
+
- If the operator answers yes \u2192 call ${t}save_report${t} with the complete report markdown content.
|
|
3610
|
+
- If the operator answers no \u2192 do not save; acknowledge and continue.
|
|
3611
|
+
- **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.
|
|
3612
|
+
3. After saving (or after the operator declines), ask: "Would you like to investigate another issue, or end the session?"
|
|
3613
|
+
4. If the operator wants another investigation, start fresh with the same 5-round protocol.
|
|
3614
|
+
5. On session end: summarize all issues investigated and list the saved report file paths, then exit.
|
|
3543
3615
|
|
|
3544
3616
|
---
|
|
3545
3617
|
|
|
@@ -3642,9 +3714,8 @@ async function discoverCatalogSchemas(session2) {
|
|
|
3642
3714
|
var import_promises2 = require("fs/promises");
|
|
3643
3715
|
var import_node_path3 = require("path");
|
|
3644
3716
|
var import_node_fs3 = require("fs");
|
|
3645
|
-
async function
|
|
3717
|
+
async function loadReferencesFrom(dir) {
|
|
3646
3718
|
try {
|
|
3647
|
-
const dir = refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references");
|
|
3648
3719
|
if (!(0, import_node_fs3.existsSync)(dir)) return [];
|
|
3649
3720
|
const files = await (0, import_promises2.readdir)(dir);
|
|
3650
3721
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
@@ -3666,13 +3737,21 @@ async function loadReferences(refsDir) {
|
|
|
3666
3737
|
return [];
|
|
3667
3738
|
}
|
|
3668
3739
|
}
|
|
3740
|
+
function loadReferences(refsDir) {
|
|
3741
|
+
return loadReferencesFrom(refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references"));
|
|
3742
|
+
}
|
|
3743
|
+
function loadBundleReferences(refsDir) {
|
|
3744
|
+
return loadReferencesFrom(
|
|
3745
|
+
refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references", "bundle")
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3669
3748
|
|
|
3670
3749
|
// src/agent/prompt-budget.ts
|
|
3671
3750
|
var CHARS_PER_TOKEN = 4;
|
|
3672
3751
|
var DEFAULT_PROMPT_BUDGET_TOKENS = 2e4;
|
|
3673
|
-
function estimateTokens(
|
|
3674
|
-
if (!
|
|
3675
|
-
return Math.ceil(
|
|
3752
|
+
function estimateTokens(text2) {
|
|
3753
|
+
if (!text2) return 0;
|
|
3754
|
+
return Math.ceil(text2.length / CHARS_PER_TOKEN);
|
|
3676
3755
|
}
|
|
3677
3756
|
function checkPromptBudget(prompt, opts) {
|
|
3678
3757
|
const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
|
|
@@ -3712,14 +3791,14 @@ function isValidBudget(value) {
|
|
|
3712
3791
|
function estimateTurnCostUsd(usage, model) {
|
|
3713
3792
|
if (!usage) return 0;
|
|
3714
3793
|
const price = MODEL_PRICING[model];
|
|
3715
|
-
const
|
|
3794
|
+
const input2 = safeCount(usage.inputTokens);
|
|
3716
3795
|
const output = safeCount(usage.outputTokens);
|
|
3717
3796
|
const cacheRead = safeCount(usage.cacheReadInputTokens);
|
|
3718
3797
|
const cacheCreation = safeCount(usage.cacheCreationInputTokens);
|
|
3719
|
-
return (
|
|
3798
|
+
return (input2 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
|
|
3720
3799
|
}
|
|
3721
|
-
function resolveMaxBudgetUsd(
|
|
3722
|
-
if (isValidBudget(
|
|
3800
|
+
function resolveMaxBudgetUsd(flagValue2, env = process.env) {
|
|
3801
|
+
if (isValidBudget(flagValue2)) return flagValue2;
|
|
3723
3802
|
const raw = env[BUDGET_ENV_VAR];
|
|
3724
3803
|
if (raw !== void 0 && raw !== "") {
|
|
3725
3804
|
const parsed = Number(raw);
|
|
@@ -3766,7 +3845,7 @@ function formatTimestamp(date) {
|
|
|
3766
3845
|
function makeSaveReportTool() {
|
|
3767
3846
|
return (0, import_claude_agent_sdk3.tool)(
|
|
3768
3847
|
"save_report",
|
|
3769
|
-
"Save a diagnostic report to disk. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory.
|
|
3848
|
+
"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.",
|
|
3770
3849
|
{
|
|
3771
3850
|
content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
|
|
3772
3851
|
partial: import_zod17.z.boolean().optional().describe(
|
|
@@ -3790,179 +3869,1482 @@ function makeSaveReportTool() {
|
|
|
3790
3869
|
);
|
|
3791
3870
|
}
|
|
3792
3871
|
|
|
3793
|
-
// src/
|
|
3794
|
-
var
|
|
3872
|
+
// src/tools/bundle/index.ts
|
|
3873
|
+
var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
|
|
3795
3874
|
|
|
3796
|
-
// src/
|
|
3797
|
-
var
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
var
|
|
3801
|
-
|
|
3802
|
-
|
|
3875
|
+
// src/tools/bundle/list-files.ts
|
|
3876
|
+
var import_zod18 = require("zod");
|
|
3877
|
+
|
|
3878
|
+
// src/bundle/known-files.ts
|
|
3879
|
+
var KNOWN_BUNDLE_FILES = {
|
|
3880
|
+
// Host resources
|
|
3881
|
+
"cpu.txt": "CPU topology, NUMA, and interrupts (lscpu, numactl, /proc/cpuinfo, /proc/interrupts)",
|
|
3882
|
+
"mem.txt": "Memory usage, /proc/meminfo, and transparent-hugepage setting (free -m -t)",
|
|
3883
|
+
"disk.txt": "Filesystems, mounts, block devices, and disk stats (df, mount, lsblk, fdisk, /etc/fstab, /proc/diskstats)",
|
|
3884
|
+
"gpu.txt": "NVIDIA GPU inventory and state (nvidia-smi -L/-q, modinfo nvidia)",
|
|
3885
|
+
"net.txt": "Network interfaces, sockets, and DNS (hostname, ifconfig, netstat, /etc/resolv.conf)",
|
|
3886
|
+
// Processes
|
|
3887
|
+
"ps.txt": "Full process list (ps -auxww, ps -ejHlfww)",
|
|
3888
|
+
"gpudb-exe.txt": "Running gpudb processes (ps auxfwww | grep gpudb)",
|
|
3889
|
+
// Hardware / firmware
|
|
3890
|
+
"dmidecode.txt": "BIOS / DMI hardware inventory (dmidecode)",
|
|
3891
|
+
"lshw.txt": "Hardware listing (lshw -short -numeric)",
|
|
3892
|
+
"pci.txt": "PCI devices and I/O resources (lspci, /proc/ioports, /proc/iomem)",
|
|
3893
|
+
// Kernel / OS
|
|
3894
|
+
"dmesg.txt": "Kernel ring buffer \u2014 boot and runtime kernel messages (dmesg -T)",
|
|
3895
|
+
"dmesg-timestamp.txt": "Kernel ring buffer with human-readable timestamps",
|
|
3896
|
+
"sysctl.txt": "Kernel tunables (sysctl -a)",
|
|
3897
|
+
"sys.txt": "OS identity, uptime, ulimits, kernel cmdline, clocksource, and loaded modules (uname, ulimit, /proc/cmdline, lsmod)",
|
|
3898
|
+
"lsof.txt": "Open files and network sockets (lsof -n -P)",
|
|
3899
|
+
"lslocks.txt": "Held file locks (lslocks)",
|
|
3900
|
+
// Packages / linker / accounts
|
|
3901
|
+
"deb.txt": "Installed Debian packages and verification (dpkg -l, dpkg -V)",
|
|
3902
|
+
"rpm.txt": "Installed RPM packages (rpm -qa)",
|
|
3903
|
+
"ld.so.conf.txt": "Dynamic-linker library search paths (/etc/ld.so.conf)",
|
|
3904
|
+
"user.txt": "Users, groups, and the gpudb service account (whoami, id, /etc/passwd, /etc/group)",
|
|
3905
|
+
"sudoers.txt": "Sudo configuration (/etc/sudoers)",
|
|
3906
|
+
"etc_profile.txt": "Login shell profile (/etc/profile)",
|
|
3907
|
+
"etc_bashrc.txt": "System bashrc (/etc/bashrc)",
|
|
3908
|
+
"etc_host.txt": "Static hostname resolution (/etc/hosts)",
|
|
3909
|
+
// Kinetica-specific
|
|
3910
|
+
"gpudb.txt": "GPUdb version/build, binary md5 + ldd, and the captured gpudb.conf / gpudb_logger.conf ($GPUDB_EXE -v)",
|
|
3911
|
+
"gpudb_core_etc_gpudb.conf": "The live gpudb.conf at capture time (the database's main config)",
|
|
3912
|
+
"gpudb_core_etc_gpudb_logger.conf": "The logging configuration (gpudb_logger.conf)",
|
|
3913
|
+
"loki-info.txt": "Loki log-index stats: labels, series, and per-class volume (logcli)",
|
|
3914
|
+
"sql-queries.txt": "SQL query log extracted from Loki (logcli)",
|
|
3915
|
+
"tables.txt": "Table schemas and column types (gadmin --schema), when collected",
|
|
3916
|
+
"logfiles.txt": "Manifest: the log directories/files the collector enumerated",
|
|
3917
|
+
"errors.txt": "Collection commands that FAILED during capture (Evidence Gaps)",
|
|
3918
|
+
"proc-logs-erros.txt": "Per-process log-collection failures during capture (Evidence Gaps)"
|
|
3919
|
+
};
|
|
3920
|
+
var KIND_DESCRIPTIONS = {
|
|
3921
|
+
"core-log": "Per-rank rolling Kinetica core log (the primary incident narrative)",
|
|
3922
|
+
"component-log": "Component service log (sql-engine, httpd, reveal, tomcat, stats, \u2026)",
|
|
3923
|
+
"loki-tail": "Last-2h Loki tail for a service (small; searched only when no core logs exist)",
|
|
3924
|
+
"process-info": "Per-rank process snapshot: command line, PID, and environment (/proc/<pid>/environ)",
|
|
3925
|
+
config: "Kinetica configuration file",
|
|
3926
|
+
"version-info": "GPUdb version/build information",
|
|
3927
|
+
"collection-errors": "Collection commands that FAILED during capture (Evidence Gaps)",
|
|
3928
|
+
manifest: "Manifest of log directories/files the collector enumerated"
|
|
3929
|
+
};
|
|
3930
|
+
function basename(relPath) {
|
|
3931
|
+
const parts = relPath.split("/");
|
|
3932
|
+
return parts[parts.length - 1] ?? relPath;
|
|
3803
3933
|
}
|
|
3804
|
-
function
|
|
3805
|
-
|
|
3806
|
-
const action = `${formatLabel("Action")}${import_picocolors5.default.bold(formatToolName(toolName))}`;
|
|
3807
|
-
const paramEntries = Object.entries(toolInput);
|
|
3808
|
-
const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
|
|
3809
|
-
const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
3810
|
-
return ` ${import_picocolors5.default.dim(key)}: ${formatted}`;
|
|
3811
|
-
}).join("\n");
|
|
3812
|
-
const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
|
|
3813
|
-
const prompt = import_picocolors5.default.dim(
|
|
3814
|
-
`${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
|
|
3815
|
-
);
|
|
3816
|
-
const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
|
|
3817
|
-
const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
|
|
3818
|
-
(entry) => ` ${import_picocolors5.default.dim(entry.key)}: ${entry.current} ${import_picocolors5.default.yellow("->")} ${entry.proposed}`
|
|
3819
|
-
).join("\n") : null;
|
|
3820
|
-
const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
|
|
3821
|
-
const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
|
|
3822
|
-
const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
|
|
3823
|
-
if (beforeAfterSection !== null) {
|
|
3824
|
-
sections.push(beforeAfterSection, "");
|
|
3825
|
-
}
|
|
3826
|
-
if (reasoningSection !== null) {
|
|
3827
|
-
sections.push(reasoningSection, "");
|
|
3828
|
-
}
|
|
3829
|
-
sections.push(impactLine, "", prompt, DIVIDER2, "");
|
|
3830
|
-
return sections.join("\n");
|
|
3934
|
+
function describeBundleFile(entry) {
|
|
3935
|
+
return KNOWN_BUNDLE_FILES[basename(entry.relPath)] ?? KIND_DESCRIPTIONS[entry.kind] ?? "";
|
|
3831
3936
|
}
|
|
3832
3937
|
|
|
3833
|
-
// src/
|
|
3834
|
-
var
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
return {
|
|
3863
|
-
behavior: "deny",
|
|
3864
|
-
message: DENY_MESSAGE,
|
|
3865
|
-
toolUseID: options.toolUseID
|
|
3866
|
-
};
|
|
3867
|
-
}
|
|
3868
|
-
if (normalized === "explain") {
|
|
3869
|
-
const reasoning = options.decisionReason;
|
|
3870
|
-
if (reasoning) {
|
|
3871
|
-
console.error(`
|
|
3872
|
-
Agent reasoning: ${reasoning}
|
|
3873
|
-
`);
|
|
3874
|
-
} else {
|
|
3875
|
-
console.error(`
|
|
3876
|
-
${REASONING_FALLBACK}
|
|
3877
|
-
`);
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
|
-
} catch {
|
|
3881
|
-
return {
|
|
3882
|
-
behavior: "deny",
|
|
3883
|
-
message: DENY_MESSAGE,
|
|
3884
|
-
toolUseID: options.toolUseID
|
|
3885
|
-
};
|
|
3886
|
-
}
|
|
3938
|
+
// src/tools/bundle/list-files.ts
|
|
3939
|
+
var BundleListFilesSchema = import_zod18.z.object({
|
|
3940
|
+
kind: import_zod18.z.string().optional()
|
|
3941
|
+
});
|
|
3942
|
+
async function bundleListFiles(source, args = {}) {
|
|
3943
|
+
const all = source.listFiles();
|
|
3944
|
+
const filtered = args.kind ? all.filter((e) => e.kind === args.kind) : all;
|
|
3945
|
+
const { totalFiles, totalBytes, byKind, ranks, services } = source.inventory();
|
|
3946
|
+
const version = await source.detectVersion();
|
|
3947
|
+
const errors = await source.collectionErrors();
|
|
3948
|
+
const files = filtered.map((e) => ({
|
|
3949
|
+
file: e.relPath,
|
|
3950
|
+
kind: e.kind,
|
|
3951
|
+
rank: e.rank ?? "",
|
|
3952
|
+
size_kb: Math.round(e.sizeBytes / 1024),
|
|
3953
|
+
// What the file contains — so the agent can pick the right one without reading it.
|
|
3954
|
+
description: describeBundleFile(e)
|
|
3955
|
+
}));
|
|
3956
|
+
return {
|
|
3957
|
+
ok: true,
|
|
3958
|
+
data: {
|
|
3959
|
+
detected_version: version ?? "unknown",
|
|
3960
|
+
ranks_present: ranks.join(", ") || "none",
|
|
3961
|
+
services_present: services.join(", ") || "none",
|
|
3962
|
+
total_files: totalFiles,
|
|
3963
|
+
total_size_mb: Number((totalBytes / 1e6).toFixed(1)),
|
|
3964
|
+
counts_by_kind: byKind,
|
|
3965
|
+
failed_collections: errors.length,
|
|
3966
|
+
files
|
|
3887
3967
|
}
|
|
3888
3968
|
};
|
|
3889
3969
|
}
|
|
3890
3970
|
|
|
3891
|
-
// src/
|
|
3892
|
-
|
|
3893
|
-
|
|
3971
|
+
// src/tools/bundle/log-timeline.ts
|
|
3972
|
+
var import_zod19 = require("zod");
|
|
3973
|
+
var BundleLogTimelineSchema = import_zod19.z.object({
|
|
3974
|
+
min_severity: import_zod19.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
|
|
3975
|
+
granularity: import_zod19.z.enum(["day", "hour", "minute"]).optional(),
|
|
3976
|
+
rank: import_zod19.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
|
|
3977
|
+
host_manager: import_zod19.z.boolean().describe("Bucket the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
3978
|
+
component: import_zod19.z.string().optional(),
|
|
3979
|
+
include_components: import_zod19.z.boolean().optional()
|
|
3980
|
+
});
|
|
3981
|
+
async function bundleLogTimeline(source, args = {}) {
|
|
3982
|
+
const query3 = {
|
|
3983
|
+
...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
|
|
3984
|
+
...args.granularity !== void 0 ? { granularity: args.granularity } : {},
|
|
3985
|
+
...args.rank !== void 0 ? { rank: args.rank } : {},
|
|
3986
|
+
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
3987
|
+
...args.component !== void 0 ? { component: args.component } : {},
|
|
3988
|
+
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {}
|
|
3894
3989
|
};
|
|
3895
|
-
|
|
3896
|
-
|
|
3990
|
+
const result = await source.logTimeline(query3);
|
|
3991
|
+
const severities = [...new Set(result.buckets.flatMap((b) => Object.keys(b.counts)))];
|
|
3992
|
+
const order = ["FATAL", "ERROR", "UERR", "WARN", "INFO"];
|
|
3993
|
+
severities.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
3994
|
+
const rows = result.buckets.map((b) => {
|
|
3995
|
+
const row = { time_bucket: b.bucket };
|
|
3996
|
+
for (const sev of severities) row[sev] = b.counts[sev] ?? 0;
|
|
3997
|
+
row.total = b.total;
|
|
3998
|
+
return row;
|
|
3897
3999
|
});
|
|
3898
|
-
return
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
resolve2 = r;
|
|
3906
|
-
});
|
|
4000
|
+
return {
|
|
4001
|
+
ok: true,
|
|
4002
|
+
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).`,
|
|
4003
|
+
data: {
|
|
4004
|
+
lines_scanned: result.linesScanned,
|
|
4005
|
+
files_scanned: result.filesScanned.join(", ") || "none",
|
|
4006
|
+
buckets: rows
|
|
3907
4007
|
}
|
|
3908
|
-
}
|
|
4008
|
+
};
|
|
3909
4009
|
}
|
|
3910
4010
|
|
|
3911
|
-
// src/
|
|
3912
|
-
var
|
|
3913
|
-
var
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4011
|
+
// src/tools/bundle/search-logs.ts
|
|
4012
|
+
var import_zod20 = require("zod");
|
|
4013
|
+
var BundleSearchLogsSchema = import_zod20.z.object({
|
|
4014
|
+
regex: import_zod20.z.string().optional(),
|
|
4015
|
+
min_severity: import_zod20.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
|
|
4016
|
+
from_ts: import_zod20.z.string().optional(),
|
|
4017
|
+
to_ts: import_zod20.z.string().optional(),
|
|
4018
|
+
rank: import_zod20.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
|
|
4019
|
+
host_manager: import_zod20.z.boolean().describe("Search the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
4020
|
+
component: import_zod20.z.string().optional(),
|
|
4021
|
+
include_components: import_zod20.z.boolean().optional(),
|
|
4022
|
+
max_matches: import_zod20.z.number().int().min(1).max(1e3).optional()
|
|
4023
|
+
});
|
|
4024
|
+
async function bundleSearchLogs(source, args = {}) {
|
|
4025
|
+
const query3 = {
|
|
4026
|
+
...args.regex !== void 0 ? { regex: args.regex } : {},
|
|
4027
|
+
...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
|
|
4028
|
+
...args.from_ts !== void 0 ? { fromTs: args.from_ts } : {},
|
|
4029
|
+
...args.to_ts !== void 0 ? { toTs: args.to_ts } : {},
|
|
4030
|
+
...args.rank !== void 0 ? { rank: args.rank } : {},
|
|
4031
|
+
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
4032
|
+
...args.component !== void 0 ? { component: args.component } : {},
|
|
4033
|
+
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {},
|
|
4034
|
+
...args.max_matches !== void 0 ? { maxMatches: args.max_matches } : {}
|
|
4035
|
+
};
|
|
4036
|
+
const result = await source.searchLogs(query3);
|
|
4037
|
+
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).`;
|
|
4038
|
+
return {
|
|
4039
|
+
ok: true,
|
|
4040
|
+
note,
|
|
4041
|
+
data: {
|
|
4042
|
+
total_matched: result.totalMatched,
|
|
4043
|
+
lines_scanned: result.linesScanned,
|
|
4044
|
+
files_scanned: result.filesScanned.join(", ") || "none",
|
|
4045
|
+
capped: result.capped,
|
|
4046
|
+
matches: result.matches.map((m) => ({
|
|
4047
|
+
file: m.file,
|
|
4048
|
+
line: m.lineNumber,
|
|
4049
|
+
timestamp: m.timestamp ?? "",
|
|
4050
|
+
severity: m.severity ?? "",
|
|
4051
|
+
rank: m.rank ?? "",
|
|
4052
|
+
message: m.message
|
|
4053
|
+
}))
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
// src/tools/bundle/read-config.ts
|
|
4059
|
+
var import_zod21 = require("zod");
|
|
4060
|
+
var BundleReadConfigSchema = import_zod21.z.object({
|
|
4061
|
+
section: import_zod21.z.string().optional(),
|
|
4062
|
+
key: import_zod21.z.string().optional()
|
|
4063
|
+
});
|
|
4064
|
+
async function bundleReadConfig(source, args = {}) {
|
|
4065
|
+
const result = await source.readConfig({
|
|
4066
|
+
...args.section !== void 0 ? { section: args.section } : {},
|
|
4067
|
+
...args.key !== void 0 ? { key: args.key } : {}
|
|
4068
|
+
});
|
|
4069
|
+
if ("error" in result) {
|
|
4070
|
+
return { ok: false, status: 0, error: result.error, raw: "" };
|
|
3919
4071
|
}
|
|
3920
|
-
if (
|
|
3921
|
-
|
|
4072
|
+
if (result.entries.length === 0 && args.section !== void 0) {
|
|
4073
|
+
const all = await source.readConfig(args.key !== void 0 ? { key: args.key } : {});
|
|
4074
|
+
const sections = "error" in all ? [] : [...new Set(all.entries.map((e) => e.section))].sort();
|
|
4075
|
+
const sectionList = sections.map((s) => s === "" ? "(flat/top-level)" : s).join(", ");
|
|
4076
|
+
return {
|
|
4077
|
+
ok: true,
|
|
4078
|
+
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)"}.`,
|
|
4079
|
+
data: { section_not_found: args.section, available_sections: sections }
|
|
4080
|
+
};
|
|
3922
4081
|
}
|
|
3923
|
-
return
|
|
4082
|
+
return {
|
|
4083
|
+
ok: true,
|
|
4084
|
+
note: `${result.entries.length} entr(y/ies) from ${result.file}.`,
|
|
4085
|
+
data: result.entries.map((e) => ({ section: e.section, key: e.key, value: e.value }))
|
|
4086
|
+
};
|
|
3924
4087
|
}
|
|
3925
4088
|
|
|
3926
|
-
// src/
|
|
3927
|
-
var
|
|
3928
|
-
var
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
}
|
|
3935
|
-
|
|
3936
|
-
return
|
|
4089
|
+
// src/tools/bundle/read-sysinfo.ts
|
|
4090
|
+
var import_zod22 = require("zod");
|
|
4091
|
+
var BundleReadSysinfoSchema = import_zod22.z.object({
|
|
4092
|
+
name: import_zod22.z.string().min(1)
|
|
4093
|
+
});
|
|
4094
|
+
async function bundleReadSysinfo(source, args) {
|
|
4095
|
+
const result = await source.readSysinfo(args.name);
|
|
4096
|
+
if ("error" in result) {
|
|
4097
|
+
return { ok: false, status: 0, error: result.error, raw: "" };
|
|
4098
|
+
}
|
|
4099
|
+
return {
|
|
4100
|
+
ok: true,
|
|
4101
|
+
data: {
|
|
4102
|
+
...result.header !== void 0 ? { source_file: result.header } : {},
|
|
4103
|
+
blocks: result.blocks.map((b) => ({
|
|
4104
|
+
command: b.command,
|
|
4105
|
+
...b.exitCode !== void 0 ? { exit_code: b.exitCode } : {},
|
|
4106
|
+
output: b.output
|
|
4107
|
+
}))
|
|
4108
|
+
}
|
|
4109
|
+
};
|
|
3937
4110
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
4111
|
+
|
|
4112
|
+
// src/tools/bundle/load-bundle.ts
|
|
4113
|
+
var import_zod23 = require("zod");
|
|
4114
|
+
|
|
4115
|
+
// src/bundle/verify-bundle.ts
|
|
4116
|
+
var import_promises6 = require("fs/promises");
|
|
4117
|
+
|
|
4118
|
+
// src/bundle/BundleSource.ts
|
|
4119
|
+
var import_promises5 = require("fs/promises");
|
|
4120
|
+
var import_node_path6 = require("path");
|
|
4121
|
+
|
|
4122
|
+
// src/bundle/sysinfo-block.ts
|
|
4123
|
+
var SEPARATOR_RE = /^-{3,}$/;
|
|
4124
|
+
var EXEC_CMD_RE = /^EXEC_CMD:\s?(.*)$/;
|
|
4125
|
+
var EXEC_END_RE = /^EXEC_END with exit code (\d+)\s*:?\s*(.*)$/;
|
|
4126
|
+
var SHOWING_RE = /^### Showing whole log file\s*:/;
|
|
4127
|
+
function trimBlankEdges(lines) {
|
|
4128
|
+
let start = 0;
|
|
4129
|
+
let end = lines.length;
|
|
4130
|
+
while (start < end && lines[start].trim() === "") start++;
|
|
4131
|
+
while (end > start && lines[end - 1].trim() === "") end--;
|
|
4132
|
+
return lines.slice(start, end).join("\n");
|
|
4133
|
+
}
|
|
4134
|
+
function parseSysinfo(content) {
|
|
4135
|
+
const lines = content.split("\n");
|
|
4136
|
+
let header;
|
|
4137
|
+
const blocks = [];
|
|
4138
|
+
let current;
|
|
4139
|
+
let sawCommand = false;
|
|
4140
|
+
const closeBlock = (exitCode, exitMessage) => {
|
|
4141
|
+
if (!current) return;
|
|
4142
|
+
blocks.push({
|
|
4143
|
+
command: current.command,
|
|
4144
|
+
output: trimBlankEdges(current.output),
|
|
4145
|
+
...exitCode !== void 0 ? { exitCode } : {},
|
|
4146
|
+
...exitMessage !== void 0 && exitMessage !== "" ? { exitMessage } : {}
|
|
4147
|
+
});
|
|
4148
|
+
current = void 0;
|
|
4149
|
+
};
|
|
4150
|
+
for (const line of lines) {
|
|
4151
|
+
if (SEPARATOR_RE.test(line)) continue;
|
|
4152
|
+
const cmdMatch = EXEC_CMD_RE.exec(line);
|
|
4153
|
+
if (cmdMatch) {
|
|
4154
|
+
closeBlock();
|
|
4155
|
+
current = { command: cmdMatch[1].trim(), output: [] };
|
|
4156
|
+
sawCommand = true;
|
|
4157
|
+
continue;
|
|
4158
|
+
}
|
|
4159
|
+
const endMatch = EXEC_END_RE.exec(line);
|
|
4160
|
+
if (endMatch && current) {
|
|
4161
|
+
closeBlock(Number(endMatch[1]), endMatch[2].trim());
|
|
4162
|
+
continue;
|
|
4163
|
+
}
|
|
4164
|
+
if (current) {
|
|
4165
|
+
if (SHOWING_RE.test(line)) continue;
|
|
4166
|
+
current.output.push(line);
|
|
4167
|
+
continue;
|
|
4168
|
+
}
|
|
4169
|
+
if (header === void 0 && !sawCommand && line.trim() !== "") {
|
|
4170
|
+
header = line.trim();
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
closeBlock();
|
|
4174
|
+
return { ...header !== void 0 ? { header } : {}, blocks };
|
|
3940
4175
|
}
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
4176
|
+
|
|
4177
|
+
// src/bundle/parse-ini.ts
|
|
4178
|
+
var SECTION_RE = /^\[(.+)\]$/;
|
|
4179
|
+
function parseIni(content) {
|
|
4180
|
+
const entries = [];
|
|
4181
|
+
let section = "";
|
|
4182
|
+
for (const rawLine of content.split("\n")) {
|
|
4183
|
+
const line = rawLine.trim();
|
|
4184
|
+
if (line === "" || line.startsWith("#") || line.startsWith(";")) continue;
|
|
4185
|
+
const sectionMatch = SECTION_RE.exec(line);
|
|
4186
|
+
if (sectionMatch) {
|
|
4187
|
+
section = sectionMatch[1].trim();
|
|
4188
|
+
continue;
|
|
3948
4189
|
}
|
|
3949
|
-
|
|
4190
|
+
const eq = line.indexOf("=");
|
|
4191
|
+
if (eq === -1) continue;
|
|
4192
|
+
const key = line.slice(0, eq).trim();
|
|
4193
|
+
const value = line.slice(eq + 1).trim();
|
|
4194
|
+
if (key) entries.push({ section, key, value });
|
|
4195
|
+
}
|
|
4196
|
+
return entries;
|
|
4197
|
+
}
|
|
4198
|
+
function filterIni(entries, opts = {}) {
|
|
4199
|
+
const section = opts.section?.toLowerCase();
|
|
4200
|
+
const key = opts.key?.toLowerCase();
|
|
4201
|
+
return entries.filter((e) => {
|
|
4202
|
+
if (section !== void 0 && e.section.toLowerCase() !== section) return false;
|
|
4203
|
+
if (key !== void 0 && !e.key.toLowerCase().includes(key)) return false;
|
|
4204
|
+
return true;
|
|
3950
4205
|
});
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
// src/bundle/log-search.ts
|
|
4209
|
+
var import_node_fs4 = require("fs");
|
|
4210
|
+
var import_node_readline = require("readline");
|
|
4211
|
+
|
|
4212
|
+
// src/bundle/unwrap-loki-jsonl.ts
|
|
4213
|
+
var LEADING_TS_RE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+)\s+/;
|
|
4214
|
+
var HEADER_BODY_SEP = " : ";
|
|
4215
|
+
function unwrapLokiJsonl(line) {
|
|
4216
|
+
let i = 0;
|
|
4217
|
+
while (i < line.length && line.charCodeAt(i) <= 32) i++;
|
|
4218
|
+
if (line.charCodeAt(i) !== 123) return void 0;
|
|
4219
|
+
let obj;
|
|
4220
|
+
try {
|
|
4221
|
+
obj = JSON.parse(line);
|
|
4222
|
+
} catch {
|
|
4223
|
+
return void 0;
|
|
4224
|
+
}
|
|
4225
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
4226
|
+
const inner = obj.line;
|
|
4227
|
+
if (typeof inner !== "string") return void 0;
|
|
4228
|
+
const tsMatch = LEADING_TS_RE.exec(inner);
|
|
4229
|
+
const sepIdx = inner.indexOf(HEADER_BODY_SEP);
|
|
4230
|
+
if (tsMatch && sepIdx !== -1) {
|
|
4231
|
+
const ts = tsMatch[1];
|
|
4232
|
+
const body = inner.slice(sepIdx + HEADER_BODY_SEP.length).trim();
|
|
4233
|
+
return `${ts} ${body}`;
|
|
4234
|
+
}
|
|
4235
|
+
return inner;
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/bundle/parse-log-line.ts
|
|
4239
|
+
var PREFIX_RE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+)\s+([A-Z]+)\s+\(([^)]*)\)\s*(.*)$/;
|
|
4240
|
+
var CORE_TAIL_RE = /^(\S+)\s+(\S+:\d+)\s+-\s+(.*)$/;
|
|
4241
|
+
var RANK_RE = /^(r\d+)\b/;
|
|
4242
|
+
var SEVERITY_RANK = {
|
|
4243
|
+
TRACE: 0,
|
|
4244
|
+
DEBUG: 1,
|
|
4245
|
+
INFO: 2,
|
|
4246
|
+
WARN: 3,
|
|
4247
|
+
UERR: 4,
|
|
4248
|
+
ERROR: 5,
|
|
4249
|
+
FATAL: 6
|
|
4250
|
+
};
|
|
4251
|
+
function severityRank(severity) {
|
|
4252
|
+
if (severity === void 0) return -1;
|
|
4253
|
+
return SEVERITY_RANK[severity] ?? -1;
|
|
4254
|
+
}
|
|
4255
|
+
function parseLogLine(line) {
|
|
4256
|
+
const effective = unwrapLokiJsonl(line) ?? line;
|
|
4257
|
+
const match = PREFIX_RE.exec(effective);
|
|
4258
|
+
if (!match) {
|
|
4259
|
+
return { message: effective, raw: line };
|
|
4260
|
+
}
|
|
4261
|
+
const [, timestamp, severity, paren, rest] = match;
|
|
4262
|
+
const parts = paren.split(",");
|
|
4263
|
+
const pid = parts[0]?.trim() || void 0;
|
|
4264
|
+
const tid = parts[1]?.trim() || void 0;
|
|
4265
|
+
const context = parts.slice(2).join(",").trim() || void 0;
|
|
4266
|
+
const rank = context ? RANK_RE.exec(context)?.[1] ?? void 0 : void 0;
|
|
4267
|
+
const coreTail = CORE_TAIL_RE.exec(rest);
|
|
4268
|
+
if (coreTail) {
|
|
4269
|
+
const [, host, source, message] = coreTail;
|
|
4270
|
+
return { timestamp, severity, pid, tid, context, rank, host, source, message, raw: line };
|
|
4271
|
+
}
|
|
4272
|
+
return { timestamp, severity, pid, tid, context, rank, message: rest, raw: line };
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
// src/bundle/log-search.ts
|
|
4276
|
+
var DEFAULT_MAX_MATCHES = 200;
|
|
4277
|
+
var REGEX_SCAN_MAX = 8192;
|
|
4278
|
+
var GRANULARITY_LEN = {
|
|
4279
|
+
day: 10,
|
|
4280
|
+
// "2026-06-11"
|
|
4281
|
+
hour: 13,
|
|
4282
|
+
// "2026-06-11 15"
|
|
4283
|
+
minute: 16
|
|
4284
|
+
// "2026-06-11 15:18"
|
|
4285
|
+
};
|
|
4286
|
+
function compileRegex(query3) {
|
|
4287
|
+
if (query3.regex === void 0) return void 0;
|
|
4288
|
+
return new RegExp(query3.regex, query3.caseSensitive ? void 0 : "i");
|
|
4289
|
+
}
|
|
4290
|
+
var TS_FLOOR = "0000-01-01 00:00:00.000";
|
|
4291
|
+
var TS_CEIL = "9999-12-31 23:59:59.999";
|
|
4292
|
+
var SAFE_PREFIX_LENS = [4, 7, 10, 13, 16, 19];
|
|
4293
|
+
function alignPrefixLen(len) {
|
|
4294
|
+
let aligned = 0;
|
|
4295
|
+
for (const n of SAFE_PREFIX_LENS) if (n <= len) aligned = n;
|
|
4296
|
+
return aligned;
|
|
4297
|
+
}
|
|
4298
|
+
function floorTimestamp(ts) {
|
|
4299
|
+
if (ts.length >= TS_FLOOR.length) return ts;
|
|
4300
|
+
const len = alignPrefixLen(ts.length);
|
|
4301
|
+
return ts.slice(0, len) + TS_FLOOR.slice(len);
|
|
4302
|
+
}
|
|
4303
|
+
function ceilTimestamp(ts) {
|
|
4304
|
+
if (ts.length >= TS_CEIL.length) return ts;
|
|
4305
|
+
const len = alignPrefixLen(ts.length);
|
|
4306
|
+
return ts.slice(0, len) + TS_CEIL.slice(len);
|
|
4307
|
+
}
|
|
4308
|
+
function matchesFilters(parsed, query3, regex, minRank) {
|
|
4309
|
+
if (regex && !regex.test(parsed.raw.slice(0, REGEX_SCAN_MAX))) return false;
|
|
4310
|
+
if (query3.minSeverity !== void 0 && severityRank(parsed.severity) < minRank) return false;
|
|
4311
|
+
if (query3.rank !== void 0 && parsed.rank !== query3.rank) return false;
|
|
4312
|
+
if (query3.fromTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp < query3.fromTs))
|
|
4313
|
+
return false;
|
|
4314
|
+
if (query3.toTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp > query3.toTs))
|
|
4315
|
+
return false;
|
|
4316
|
+
return true;
|
|
4317
|
+
}
|
|
4318
|
+
async function searchLogFile(filePath, query3) {
|
|
4319
|
+
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4320
|
+
const minRank = query3.minSeverity !== void 0 ? severityRank(query3.minSeverity) : -Infinity;
|
|
4321
|
+
let regex;
|
|
4322
|
+
try {
|
|
4323
|
+
regex = compileRegex(query3);
|
|
4324
|
+
} catch (err) {
|
|
4325
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4326
|
+
return {
|
|
4327
|
+
matches: [],
|
|
4328
|
+
totalMatched: 0,
|
|
4329
|
+
linesScanned: 0,
|
|
4330
|
+
capped: false,
|
|
4331
|
+
error: `invalid regex: ${message}`
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
const boundedQuery = {
|
|
4335
|
+
...query3,
|
|
4336
|
+
...query3.fromTs !== void 0 ? { fromTs: floorTimestamp(query3.fromTs) } : {},
|
|
4337
|
+
...query3.toTs !== void 0 ? { toTs: ceilTimestamp(query3.toTs) } : {}
|
|
4338
|
+
};
|
|
4339
|
+
const matches = [];
|
|
4340
|
+
let totalMatched = 0;
|
|
4341
|
+
let linesScanned = 0;
|
|
4342
|
+
try {
|
|
4343
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4344
|
+
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
4345
|
+
crlfDelay: Infinity
|
|
4346
|
+
});
|
|
4347
|
+
for await (const line of rl) {
|
|
4348
|
+
linesScanned++;
|
|
4349
|
+
const parsed = parseLogLine(line);
|
|
4350
|
+
if (!matchesFilters(parsed, boundedQuery, regex, minRank)) continue;
|
|
4351
|
+
totalMatched++;
|
|
4352
|
+
if (matches.length < maxMatches) {
|
|
4353
|
+
matches.push({
|
|
4354
|
+
lineNumber: linesScanned,
|
|
4355
|
+
...parsed.timestamp !== void 0 ? { timestamp: parsed.timestamp } : {},
|
|
4356
|
+
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4357
|
+
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4358
|
+
message: parsed.message,
|
|
4359
|
+
raw: parsed.raw
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
} catch (err) {
|
|
4364
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4365
|
+
return {
|
|
4366
|
+
matches,
|
|
4367
|
+
totalMatched,
|
|
4368
|
+
linesScanned,
|
|
4369
|
+
capped: totalMatched > matches.length,
|
|
4370
|
+
error: message
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
return { matches, totalMatched, linesScanned, capped: totalMatched > matches.length };
|
|
4374
|
+
}
|
|
4375
|
+
async function aggregateTimeline(filePath, query3 = {}) {
|
|
4376
|
+
const granularity = query3.granularity ?? "hour";
|
|
4377
|
+
const prefixLen = GRANULARITY_LEN[granularity];
|
|
4378
|
+
const minRank = severityRank(query3.minSeverity ?? "WARN");
|
|
4379
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
4380
|
+
let linesScanned = 0;
|
|
4381
|
+
let totalCounted = 0;
|
|
4382
|
+
try {
|
|
4383
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
4384
|
+
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
4385
|
+
crlfDelay: Infinity
|
|
4386
|
+
});
|
|
4387
|
+
for await (const line of rl) {
|
|
4388
|
+
linesScanned++;
|
|
4389
|
+
const parsed = parseLogLine(line);
|
|
4390
|
+
if (parsed.timestamp === void 0 || parsed.severity === void 0) continue;
|
|
4391
|
+
if (severityRank(parsed.severity) < minRank) continue;
|
|
4392
|
+
if (query3.rank !== void 0 && parsed.rank !== query3.rank) continue;
|
|
4393
|
+
const key = parsed.timestamp.slice(0, prefixLen);
|
|
4394
|
+
const bucket = buckets.get(key) ?? {};
|
|
4395
|
+
bucket[parsed.severity] = (bucket[parsed.severity] ?? 0) + 1;
|
|
4396
|
+
buckets.set(key, bucket);
|
|
4397
|
+
totalCounted++;
|
|
4398
|
+
}
|
|
4399
|
+
} catch (err) {
|
|
4400
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4401
|
+
return { buckets: [], linesScanned, totalCounted, error: message };
|
|
4402
|
+
}
|
|
4403
|
+
const result = [];
|
|
4404
|
+
for (const [bucket, counts] of buckets) {
|
|
4405
|
+
const total = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
4406
|
+
result.push({ bucket, counts, total });
|
|
4407
|
+
}
|
|
4408
|
+
return { buckets: result, linesScanned, totalCounted };
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// src/bundle/bundle-index.ts
|
|
4412
|
+
var import_promises4 = require("fs/promises");
|
|
4413
|
+
var import_node_path5 = require("path");
|
|
4414
|
+
|
|
4415
|
+
// src/bundle/classify-file.ts
|
|
4416
|
+
var ROLLING_ID_RE = /core-gpudb-rolling-(r\d+|hm)\.log(?:\.\d+)?$/;
|
|
4417
|
+
var EXE_ID_RE = /gpudb-exe-(r\d+|hm)-/;
|
|
4418
|
+
var HOST_RE = /\b(node\w+)\b/;
|
|
4419
|
+
var LOG_RE = /\.log(?:\.\d+)?$/;
|
|
4420
|
+
var LOKI_RANK_RE = /^rank(\d+)\.log$/;
|
|
4421
|
+
var LOKI_HM_BASE = "hostmanager.log";
|
|
4422
|
+
function rankOrService(id) {
|
|
4423
|
+
return id === "hm" ? { service: "host-manager" } : { rank: id };
|
|
4424
|
+
}
|
|
4425
|
+
function basename2(relPath) {
|
|
4426
|
+
const parts = relPath.split("/");
|
|
4427
|
+
return parts[parts.length - 1] ?? relPath;
|
|
4428
|
+
}
|
|
4429
|
+
function dirOf(relPath) {
|
|
4430
|
+
const parts = relPath.split("/");
|
|
4431
|
+
return parts.length > 1 ? parts[parts.length - 2] : "";
|
|
4432
|
+
}
|
|
4433
|
+
function inferHost(relPath) {
|
|
4434
|
+
return HOST_RE.exec(relPath)?.[1] ?? void 0;
|
|
4435
|
+
}
|
|
4436
|
+
function componentName(base) {
|
|
4437
|
+
return base.replace(/\.\d+$/, "").replace(/(\.log)+$/, "").replace(/^core-gpudb-/, "").replace(/^gpudb-/, "").replace(/-node\w+$/, "");
|
|
4438
|
+
}
|
|
4439
|
+
function classifyFile(relPath) {
|
|
4440
|
+
const base = basename2(relPath);
|
|
4441
|
+
const dir = dirOf(relPath);
|
|
4442
|
+
const host = inferHost(relPath);
|
|
4443
|
+
if (base.endsWith(".conf")) {
|
|
4444
|
+
return { kind: "config", ...host ? { host } : {} };
|
|
4445
|
+
}
|
|
4446
|
+
if (base === "logfiles.txt") {
|
|
4447
|
+
return { kind: "manifest", ...host ? { host } : {} };
|
|
4448
|
+
}
|
|
4449
|
+
if (base === "errors.txt" || base.endsWith("erros.txt")) {
|
|
4450
|
+
return { kind: "collection-errors", ...host ? { host } : {} };
|
|
4451
|
+
}
|
|
4452
|
+
if (base === "gpudb.txt") {
|
|
4453
|
+
return { kind: "version-info", ...host ? { host } : {} };
|
|
4454
|
+
}
|
|
4455
|
+
const exeId = EXE_ID_RE.exec(base);
|
|
4456
|
+
if (exeId) {
|
|
4457
|
+
return { kind: "process-info", ...rankOrService(exeId[1]), ...host ? { host } : {} };
|
|
4458
|
+
}
|
|
4459
|
+
if (LOG_RE.test(base)) {
|
|
4460
|
+
const rolling = ROLLING_ID_RE.exec(base);
|
|
4461
|
+
if (rolling) {
|
|
4462
|
+
return { kind: "core-log", ...rankOrService(rolling[1]), ...host ? { host } : {} };
|
|
4463
|
+
}
|
|
4464
|
+
if (dir === "logs") {
|
|
4465
|
+
const lokiRank = LOKI_RANK_RE.exec(base);
|
|
4466
|
+
const lokiId = lokiRank ? `r${lokiRank[1]}` : base === LOKI_HM_BASE ? "hm" : void 0;
|
|
4467
|
+
if (lokiId !== void 0) {
|
|
4468
|
+
return { kind: "loki-tail", ...rankOrService(lokiId), ...host ? { host } : {} };
|
|
4469
|
+
}
|
|
4470
|
+
return { kind: "loki-tail", component: componentName(base), ...host ? { host } : {} };
|
|
4471
|
+
}
|
|
4472
|
+
return { kind: "component-log", component: componentName(base), ...host ? { host } : {} };
|
|
4473
|
+
}
|
|
4474
|
+
if (base.endsWith(".txt")) {
|
|
4475
|
+
return { kind: "os-diag", ...host ? { host } : {} };
|
|
4476
|
+
}
|
|
4477
|
+
return { kind: "unknown", ...host ? { host } : {} };
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
// src/bundle/bundle-index.ts
|
|
4481
|
+
async function buildIndex(rootDir) {
|
|
4482
|
+
let relPaths;
|
|
4483
|
+
try {
|
|
4484
|
+
relPaths = await (0, import_promises4.readdir)(rootDir, { recursive: true });
|
|
4485
|
+
} catch {
|
|
4486
|
+
return [];
|
|
4487
|
+
}
|
|
4488
|
+
const settled = await Promise.all(
|
|
4489
|
+
relPaths.map(async (rel) => {
|
|
4490
|
+
const relPath = rel.split("\\").join("/");
|
|
4491
|
+
const absPath = (0, import_node_path5.join)(rootDir, rel);
|
|
4492
|
+
try {
|
|
4493
|
+
const s = await (0, import_promises4.lstat)(absPath);
|
|
4494
|
+
if (s.isSymbolicLink() || !s.isFile()) return null;
|
|
4495
|
+
const c = classifyFile(relPath);
|
|
4496
|
+
return {
|
|
4497
|
+
relPath,
|
|
4498
|
+
absPath,
|
|
4499
|
+
kind: c.kind,
|
|
4500
|
+
...c.rank !== void 0 ? { rank: c.rank } : {},
|
|
4501
|
+
...c.service !== void 0 ? { service: c.service } : {},
|
|
4502
|
+
...c.host !== void 0 ? { host: c.host } : {},
|
|
4503
|
+
...c.component !== void 0 ? { component: c.component } : {},
|
|
4504
|
+
sizeBytes: s.size
|
|
4505
|
+
};
|
|
4506
|
+
} catch {
|
|
4507
|
+
return null;
|
|
4508
|
+
}
|
|
4509
|
+
})
|
|
4510
|
+
);
|
|
4511
|
+
return settled.filter((e) => e !== null).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
// src/bundle/BundleSource.ts
|
|
4515
|
+
var GPUDB_VERSION_RE = /GPUdb version\s*:\s*(\S+)/;
|
|
4516
|
+
function selectLogFiles(index, opts) {
|
|
4517
|
+
if (opts.component !== void 0) {
|
|
4518
|
+
return index.filter(
|
|
4519
|
+
(e) => (e.kind === "component-log" || e.kind === "loki-tail") && e.component === opts.component
|
|
4520
|
+
);
|
|
4521
|
+
}
|
|
4522
|
+
if (opts.hostManager) {
|
|
4523
|
+
const hmCore = index.filter((e) => e.kind === "core-log" && e.service === "host-manager");
|
|
4524
|
+
if (hmCore.length > 0) return hmCore;
|
|
4525
|
+
return index.filter((e) => e.kind === "loki-tail" && e.service === "host-manager");
|
|
4526
|
+
}
|
|
4527
|
+
const matchesRank = (e) => opts.rank === void 0 || e.rank === opts.rank;
|
|
4528
|
+
const coreLogs = index.filter((e) => e.kind === "core-log" && matchesRank(e));
|
|
4529
|
+
const ranksWithCore = new Set(
|
|
4530
|
+
coreLogs.map((e) => e.rank).filter((r) => r !== void 0)
|
|
4531
|
+
);
|
|
4532
|
+
const supplementalTails = index.filter(
|
|
4533
|
+
(e) => e.kind === "loki-tail" && e.rank !== void 0 && matchesRank(e) && !ranksWithCore.has(e.rank)
|
|
4534
|
+
);
|
|
4535
|
+
const rankBearing = [...coreLogs, ...supplementalTails];
|
|
4536
|
+
const core = rankBearing.length > 0 ? rankBearing : index.filter((e) => e.kind === "loki-tail" && matchesRank(e));
|
|
4537
|
+
if (opts.includeComponents) {
|
|
4538
|
+
return [...core, ...index.filter((e) => e.kind === "component-log")];
|
|
4539
|
+
}
|
|
4540
|
+
return core;
|
|
4541
|
+
}
|
|
4542
|
+
function toLineQuery(q) {
|
|
4543
|
+
return {
|
|
4544
|
+
...q.regex !== void 0 ? { regex: q.regex } : {},
|
|
4545
|
+
...q.caseSensitive !== void 0 ? { caseSensitive: q.caseSensitive } : {},
|
|
4546
|
+
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4547
|
+
...q.fromTs !== void 0 ? { fromTs: q.fromTs } : {},
|
|
4548
|
+
...q.toTs !== void 0 ? { toTs: q.toTs } : {},
|
|
4549
|
+
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {}
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
function toTimelineLineQuery(q) {
|
|
4553
|
+
return {
|
|
4554
|
+
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4555
|
+
...q.granularity !== void 0 ? { granularity: q.granularity } : {}
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
async function createBundleSource(rootDir) {
|
|
4559
|
+
const root = (0, import_node_path6.resolve)(rootDir);
|
|
4560
|
+
const index = await buildIndex(root);
|
|
4561
|
+
const resolve3 = (relPath) => {
|
|
4562
|
+
const abs = (0, import_node_path6.resolve)(root, relPath);
|
|
4563
|
+
if (abs !== root && !abs.startsWith(root + import_node_path6.sep)) return void 0;
|
|
4564
|
+
return abs;
|
|
4565
|
+
};
|
|
4566
|
+
const findByKind = (kind) => index.find((e) => e.kind === kind);
|
|
4567
|
+
const inventoryValue = (() => {
|
|
4568
|
+
const byKind = {};
|
|
4569
|
+
const rankSet = /* @__PURE__ */ new Set();
|
|
4570
|
+
const serviceSet = /* @__PURE__ */ new Set();
|
|
4571
|
+
let totalBytes = 0;
|
|
4572
|
+
for (const e of index) {
|
|
4573
|
+
byKind[e.kind] = (byKind[e.kind] ?? 0) + 1;
|
|
4574
|
+
totalBytes += e.sizeBytes;
|
|
4575
|
+
if (e.rank) rankSet.add(e.rank);
|
|
4576
|
+
if (e.service) serviceSet.add(e.service);
|
|
4577
|
+
}
|
|
4578
|
+
return {
|
|
4579
|
+
totalFiles: index.length,
|
|
4580
|
+
totalBytes,
|
|
4581
|
+
byKind,
|
|
4582
|
+
ranks: [...rankSet].sort(),
|
|
4583
|
+
services: [...serviceSet].sort()
|
|
4584
|
+
};
|
|
4585
|
+
})();
|
|
4586
|
+
const detectVersion = async () => {
|
|
4587
|
+
const versionFile = findByKind("version-info");
|
|
4588
|
+
if (versionFile) {
|
|
4589
|
+
try {
|
|
4590
|
+
const parsed = parseSysinfo(await (0, import_promises5.readFile)(versionFile.absPath, "utf-8"));
|
|
4591
|
+
for (const block of parsed.blocks) {
|
|
4592
|
+
const m = GPUDB_VERSION_RE.exec(block.output);
|
|
4593
|
+
if (m) return m[1];
|
|
4594
|
+
}
|
|
4595
|
+
} catch {
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
const configFile = findByKind("config");
|
|
4599
|
+
if (configFile) {
|
|
4600
|
+
try {
|
|
4601
|
+
const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
|
|
4602
|
+
return entries.find((e) => e.key === "file_version")?.value;
|
|
4603
|
+
} catch {
|
|
4604
|
+
return void 0;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
return void 0;
|
|
4608
|
+
};
|
|
4609
|
+
const readConfig = async (opts = {}) => {
|
|
4610
|
+
const configFile = index.find((e) => e.kind === "config" && e.relPath.endsWith("gpudb.conf")) ?? findByKind("config");
|
|
4611
|
+
if (!configFile) return { error: "no gpudb.conf found in bundle" };
|
|
4612
|
+
try {
|
|
4613
|
+
const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
|
|
4614
|
+
return { entries: filterIni(entries, opts), file: configFile.relPath };
|
|
4615
|
+
} catch (err) {
|
|
4616
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
4617
|
+
}
|
|
4618
|
+
};
|
|
4619
|
+
const readSysinfo = async (name) => {
|
|
4620
|
+
const entry = index.find(
|
|
4621
|
+
(e) => e.relPath === name || e.relPath.endsWith("/" + name) || e.relPath.split("/").pop() === name
|
|
4622
|
+
);
|
|
4623
|
+
if (!entry) return { error: `no bundle file named "${name}"` };
|
|
4624
|
+
const abs = resolve3(entry.relPath);
|
|
4625
|
+
if (!abs) return { error: `path "${name}" escapes the bundle root` };
|
|
4626
|
+
try {
|
|
4627
|
+
return parseSysinfo(await (0, import_promises5.readFile)(abs, "utf-8"));
|
|
4628
|
+
} catch (err) {
|
|
4629
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
4630
|
+
}
|
|
4631
|
+
};
|
|
4632
|
+
const searchLogs = async (query3) => {
|
|
4633
|
+
const files = selectLogFiles(index, query3);
|
|
4634
|
+
const lineQuery = toLineQuery(query3);
|
|
4635
|
+
const matches = [];
|
|
4636
|
+
const filesScanned = [];
|
|
4637
|
+
let totalMatched = 0;
|
|
4638
|
+
let linesScanned = 0;
|
|
4639
|
+
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4640
|
+
for (const file of files) {
|
|
4641
|
+
const remaining = Math.max(0, maxMatches - matches.length);
|
|
4642
|
+
const r = await searchLogFile(file.absPath, { ...lineQuery, maxMatches: remaining });
|
|
4643
|
+
filesScanned.push(file.relPath);
|
|
4644
|
+
totalMatched += r.totalMatched;
|
|
4645
|
+
linesScanned += r.linesScanned;
|
|
4646
|
+
for (const m of r.matches) matches.push({ ...m, file: file.relPath });
|
|
4647
|
+
}
|
|
4648
|
+
return {
|
|
4649
|
+
matches,
|
|
4650
|
+
totalMatched,
|
|
4651
|
+
linesScanned,
|
|
4652
|
+
filesScanned,
|
|
4653
|
+
capped: totalMatched > matches.length
|
|
4654
|
+
};
|
|
4655
|
+
};
|
|
4656
|
+
const logTimeline = async (query3) => {
|
|
4657
|
+
const files = selectLogFiles(index, query3);
|
|
4658
|
+
const lineQuery = toTimelineLineQuery(query3);
|
|
4659
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4660
|
+
const filesScanned = [];
|
|
4661
|
+
let linesScanned = 0;
|
|
4662
|
+
let totalCounted = 0;
|
|
4663
|
+
for (const file of files) {
|
|
4664
|
+
const r = await aggregateTimeline(file.absPath, lineQuery);
|
|
4665
|
+
filesScanned.push(file.relPath);
|
|
4666
|
+
linesScanned += r.linesScanned;
|
|
4667
|
+
totalCounted += r.totalCounted;
|
|
4668
|
+
for (const b of r.buckets) {
|
|
4669
|
+
const existing = merged.get(b.bucket) ?? {};
|
|
4670
|
+
for (const [sev, n] of Object.entries(b.counts)) existing[sev] = (existing[sev] ?? 0) + n;
|
|
4671
|
+
merged.set(b.bucket, existing);
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
const buckets = [...merged.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([bucket, counts]) => ({
|
|
4675
|
+
bucket,
|
|
4676
|
+
counts,
|
|
4677
|
+
total: Object.values(counts).reduce((x, y) => x + y, 0)
|
|
4678
|
+
}));
|
|
4679
|
+
return { buckets, linesScanned, totalCounted, filesScanned };
|
|
4680
|
+
};
|
|
4681
|
+
const collectionErrors = async () => {
|
|
4682
|
+
const files = index.filter((e) => e.kind === "collection-errors");
|
|
4683
|
+
const lines = [];
|
|
4684
|
+
for (const file of files) {
|
|
4685
|
+
try {
|
|
4686
|
+
const content = await (0, import_promises5.readFile)(file.absPath, "utf-8");
|
|
4687
|
+
for (const line of content.split("\n")) {
|
|
4688
|
+
const trimmed = line.trim();
|
|
4689
|
+
if (trimmed !== "" && !/^-{3,}$/.test(trimmed)) lines.push(trimmed);
|
|
4690
|
+
}
|
|
4691
|
+
} catch {
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
return lines;
|
|
4695
|
+
};
|
|
4696
|
+
return {
|
|
4697
|
+
root,
|
|
4698
|
+
listFiles: () => index,
|
|
4699
|
+
inventory: () => inventoryValue,
|
|
4700
|
+
resolve: resolve3,
|
|
4701
|
+
detectVersion,
|
|
4702
|
+
readConfig,
|
|
4703
|
+
readSysinfo,
|
|
4704
|
+
searchLogs,
|
|
4705
|
+
logTimeline,
|
|
4706
|
+
collectionErrors
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// src/bundle/verify-bundle.ts
|
|
4711
|
+
var ARCHIVE_RE = /\.(tgz|tar\.gz|tar|gz|zip)$/i;
|
|
4712
|
+
var EXPECTED_KINDS = ["config", "core-log"];
|
|
4713
|
+
async function verifyBundle(bundlePath) {
|
|
4714
|
+
let info;
|
|
4715
|
+
try {
|
|
4716
|
+
info = await (0, import_promises6.stat)(bundlePath);
|
|
4717
|
+
} catch {
|
|
4718
|
+
return { ok: false, error: `bundle path does not exist: ${bundlePath}` };
|
|
4719
|
+
}
|
|
4720
|
+
if (!info.isDirectory()) {
|
|
4721
|
+
if (ARCHIVE_RE.test(bundlePath)) {
|
|
4722
|
+
return {
|
|
4723
|
+
ok: false,
|
|
4724
|
+
error: `bundle mode expects an extracted directory, not an archive. Run \`tar xzf ${bundlePath}\` and pass the resulting directory.`
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4727
|
+
return { ok: false, error: `bundle path is not a directory: ${bundlePath}` };
|
|
4728
|
+
}
|
|
4729
|
+
const bundleSource = await createBundleSource(bundlePath);
|
|
4730
|
+
const inventory = bundleSource.inventory();
|
|
4731
|
+
if (inventory.totalFiles === 0) {
|
|
4732
|
+
return { ok: false, error: `no readable files found in bundle directory: ${bundlePath}` };
|
|
4733
|
+
}
|
|
4734
|
+
const missingExpected = EXPECTED_KINDS.filter((k) => (inventory.byKind[k] ?? 0) === 0);
|
|
4735
|
+
const kineticaVersion = await bundleSource.detectVersion();
|
|
4736
|
+
return {
|
|
4737
|
+
ok: true,
|
|
4738
|
+
bundleSource,
|
|
4739
|
+
...kineticaVersion !== void 0 ? { kineticaVersion } : {},
|
|
4740
|
+
inventory,
|
|
4741
|
+
missingExpected
|
|
4742
|
+
};
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
// src/tools/bundle/load-bundle.ts
|
|
4746
|
+
var BundleLoadSchema = import_zod23.z.object({
|
|
4747
|
+
path: import_zod23.z.string().min(1).optional()
|
|
4748
|
+
});
|
|
4749
|
+
async function bundleLoad(holder, args, promptForPath, confirmPath) {
|
|
4750
|
+
let path2;
|
|
4751
|
+
if (args.path !== void 0) {
|
|
4752
|
+
if (confirmPath && !await confirmPath(args.path)) {
|
|
4753
|
+
return {
|
|
4754
|
+
ok: false,
|
|
4755
|
+
status: 0,
|
|
4756
|
+
error: `Operator declined to load a bundle from "${args.path}".`,
|
|
4757
|
+
raw: args.path
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
path2 = args.path;
|
|
4761
|
+
} else {
|
|
4762
|
+
path2 = promptForPath ? await promptForPath() : void 0;
|
|
4763
|
+
}
|
|
4764
|
+
if (!path2) {
|
|
4765
|
+
return {
|
|
4766
|
+
ok: false,
|
|
4767
|
+
status: 0,
|
|
4768
|
+
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`.",
|
|
4769
|
+
raw: ""
|
|
4770
|
+
};
|
|
4771
|
+
}
|
|
4772
|
+
const result = await verifyBundle(path2);
|
|
4773
|
+
if (!result.ok) {
|
|
4774
|
+
return { ok: false, status: 0, error: result.error, raw: path2 };
|
|
4775
|
+
}
|
|
4776
|
+
holder.set(result.bundleSource);
|
|
4777
|
+
const missingNote = result.missingExpected.length > 0 ? ` Missing expected artifact(s): ${result.missingExpected.join(", ")} (treat as Evidence Gaps).` : "";
|
|
4778
|
+
return {
|
|
4779
|
+
ok: true,
|
|
4780
|
+
// Loading a bundle is SETUP, not an investigation. Do not auto-proceed — the
|
|
4781
|
+
// operator hasn't said what they want yet. End the turn and ask.
|
|
4782
|
+
note: `Bundle attached. Do NOT start investigating yet \u2014 ask the operator what they want to investigate, then proceed.${missingNote}`,
|
|
4783
|
+
data: {
|
|
4784
|
+
loaded: true,
|
|
4785
|
+
path: path2,
|
|
4786
|
+
detected_version: result.kineticaVersion ?? "unknown",
|
|
4787
|
+
total_files: result.inventory.totalFiles,
|
|
4788
|
+
ranks_present: result.inventory.ranks.join(", ") || "none",
|
|
4789
|
+
counts_by_kind: result.inventory.byKind,
|
|
4790
|
+
missing_expected: result.missingExpected.join(", ") || "none"
|
|
4791
|
+
}
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
// src/tools/bundle/index.ts
|
|
4796
|
+
var BUNDLE_TOOL_NAMES = [
|
|
4797
|
+
"kinetica_load_bundle",
|
|
4798
|
+
"kinetica_bundle_list_files",
|
|
4799
|
+
"kinetica_bundle_log_timeline",
|
|
4800
|
+
"kinetica_bundle_search_logs",
|
|
4801
|
+
"kinetica_bundle_read_config",
|
|
4802
|
+
"kinetica_bundle_read_sysinfo"
|
|
4803
|
+
];
|
|
4804
|
+
var text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
4805
|
+
function notLoaded() {
|
|
4806
|
+
return {
|
|
4807
|
+
ok: false,
|
|
4808
|
+
status: 0,
|
|
4809
|
+
error: "No support bundle is loaded. Ask the operator for the extracted bundle directory path and call kinetica_load_bundle first.",
|
|
4810
|
+
raw: ""
|
|
4811
|
+
};
|
|
4812
|
+
}
|
|
4813
|
+
async function withSource(holder, fn) {
|
|
4814
|
+
const source = holder.get();
|
|
4815
|
+
if (!source) return applyOutputPipeline(notLoaded());
|
|
4816
|
+
return applyOutputPipeline(await fn(source));
|
|
4817
|
+
}
|
|
4818
|
+
function makeLoadBundleTool(holder, deps) {
|
|
4819
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4820
|
+
"kinetica_load_bundle",
|
|
4821
|
+
"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.)",
|
|
4822
|
+
BundleLoadSchema.shape,
|
|
4823
|
+
async (args) => text(
|
|
4824
|
+
applyOutputPipeline(await bundleLoad(holder, args, deps?.promptForPath, deps?.confirmPath))
|
|
4825
|
+
),
|
|
4826
|
+
{ annotations: { readOnly: true } }
|
|
4827
|
+
);
|
|
4828
|
+
}
|
|
4829
|
+
function makeListFilesTool(holder) {
|
|
4830
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4831
|
+
"kinetica_bundle_list_files",
|
|
4832
|
+
"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).",
|
|
4833
|
+
BundleListFilesSchema.shape,
|
|
4834
|
+
async (args) => text(await withSource(holder, (s) => bundleListFiles(s, args))),
|
|
4835
|
+
{ annotations: { readOnly: true } }
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
function makeLogTimelineTool(holder) {
|
|
4839
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4840
|
+
"kinetica_bundle_log_timeline",
|
|
4841
|
+
"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.",
|
|
4842
|
+
BundleLogTimelineSchema.shape,
|
|
4843
|
+
async (args) => text(await withSource(holder, (s) => bundleLogTimeline(s, args))),
|
|
4844
|
+
{ annotations: { readOnly: true } }
|
|
4845
|
+
);
|
|
4846
|
+
}
|
|
4847
|
+
function makeSearchLogsTool(holder) {
|
|
4848
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4849
|
+
"kinetica_bundle_search_logs",
|
|
4850
|
+
"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.",
|
|
4851
|
+
BundleSearchLogsSchema.shape,
|
|
4852
|
+
async (args) => text(await withSource(holder, (s) => bundleSearchLogs(s, args))),
|
|
4853
|
+
{ annotations: { readOnly: true } }
|
|
4854
|
+
);
|
|
4855
|
+
}
|
|
4856
|
+
function makeReadConfigTool(holder) {
|
|
4857
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4858
|
+
"kinetica_bundle_read_config",
|
|
4859
|
+
"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.",
|
|
4860
|
+
BundleReadConfigSchema.shape,
|
|
4861
|
+
async (args) => text(await withSource(holder, (s) => bundleReadConfig(s, args))),
|
|
4862
|
+
{ annotations: { readOnly: true } }
|
|
4863
|
+
);
|
|
4864
|
+
}
|
|
4865
|
+
function makeReadSysinfoTool(holder) {
|
|
4866
|
+
return (0, import_claude_agent_sdk4.tool)(
|
|
4867
|
+
"kinetica_bundle_read_sysinfo",
|
|
4868
|
+
"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.",
|
|
4869
|
+
BundleReadSysinfoSchema.shape,
|
|
4870
|
+
async (args) => text(await withSource(holder, (s) => bundleReadSysinfo(s, args))),
|
|
4871
|
+
{ annotations: { readOnly: true } }
|
|
4872
|
+
);
|
|
4873
|
+
}
|
|
4874
|
+
function makeBundleTools(holder, deps) {
|
|
4875
|
+
return [
|
|
4876
|
+
makeLoadBundleTool(holder, deps),
|
|
4877
|
+
makeListFilesTool(holder),
|
|
4878
|
+
makeLogTimelineTool(holder),
|
|
4879
|
+
makeSearchLogsTool(holder),
|
|
4880
|
+
makeReadConfigTool(holder),
|
|
4881
|
+
makeReadSysinfoTool(holder)
|
|
4882
|
+
];
|
|
4883
|
+
}
|
|
4884
|
+
function createBundleRegistry() {
|
|
4885
|
+
return BUNDLE_TOOL_NAMES.reduce(
|
|
4886
|
+
(registry, name) => registry.registerReadOnlyTool(name),
|
|
4887
|
+
createRegistry()
|
|
4888
|
+
);
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
// src/tools/bundle/catalog.ts
|
|
4892
|
+
var BUNDLE_TOOL_CATALOG = {
|
|
4893
|
+
kinetica_load_bundle: {
|
|
4894
|
+
reveals: "Attaches an extracted support bundle (directory path) for offline analysis",
|
|
4895
|
+
whenToUse: "When the operator wants to analyze a support bundle \u2014 ask for the path, then load"
|
|
4896
|
+
},
|
|
4897
|
+
kinetica_bundle_list_files: {
|
|
4898
|
+
reveals: "Bundle inventory: detected version, ranks, file kinds/sizes, failed collections",
|
|
4899
|
+
whenToUse: "First action of every bundle investigation (orientation)"
|
|
4900
|
+
},
|
|
4901
|
+
kinetica_bundle_log_timeline: {
|
|
4902
|
+
reveals: "WARN+ log lines bucketed by time + severity across ranks (incident shape)",
|
|
4903
|
+
whenToUse: "Right after list_files \u2014 find WHEN errors spiked before drilling in"
|
|
4904
|
+
},
|
|
4905
|
+
kinetica_bundle_search_logs: {
|
|
4906
|
+
reveals: "Matching log lines by regex/severity/time-window/rank (bounded, streamed)",
|
|
4907
|
+
whenToUse: "Drill into a time window or error pattern surfaced by the timeline"
|
|
4908
|
+
},
|
|
4909
|
+
kinetica_bundle_read_config: {
|
|
4910
|
+
reveals: "gpudb.conf entries (the real on-disk config), filterable by section/key",
|
|
4911
|
+
whenToUse: "Config drift, misconfiguration, parameter verification"
|
|
4912
|
+
},
|
|
4913
|
+
kinetica_bundle_read_sysinfo: {
|
|
4914
|
+
reveals: "OS-diag / process / version command blocks (mem, cpu, disk, gpu, ps, gpudb.txt)",
|
|
4915
|
+
whenToUse: "Host-level facts: memory pressure, GPU presence, disk, THP, process args"
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4918
|
+
function buildBundleEvidenceChecklist() {
|
|
4919
|
+
const rows = BUNDLE_TOOL_NAMES.map((name) => {
|
|
4920
|
+
const entry = BUNDLE_TOOL_CATALOG[name];
|
|
4921
|
+
return `| ${name} | ${entry.reveals} | ${entry.whenToUse} |`;
|
|
4922
|
+
});
|
|
4923
|
+
return [
|
|
4924
|
+
"| Tool | What it reveals | When to use |",
|
|
4925
|
+
"|------|----------------|-------------|",
|
|
4926
|
+
...rows
|
|
4927
|
+
].join("\n");
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/agent/bundle-system-prompt.ts
|
|
4931
|
+
function buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences) {
|
|
4932
|
+
const t = "`";
|
|
4933
|
+
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.";
|
|
4934
|
+
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.
|
|
4935
|
+
|
|
4936
|
+
${versionSection}
|
|
4937
|
+
|
|
4938
|
+
---
|
|
4939
|
+
|
|
4940
|
+
## OFFLINE BUNDLE MODE \u2014 What This Means
|
|
4941
|
+
|
|
4942
|
+
**You are reading frozen, point-in-time evidence \u2014 not a live system.**
|
|
4943
|
+
|
|
4944
|
+
- 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.
|
|
4945
|
+
- 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.
|
|
4946
|
+
- 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.
|
|
4947
|
+
- **Do not assume cluster-wide clock synchronization.** Correlate events by message content as well as timestamp; note when timing is ambiguous.
|
|
4948
|
+
- 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.
|
|
4949
|
+
|
|
4950
|
+
---
|
|
4951
|
+
|
|
4952
|
+
## Investigation Protocol (read-only)
|
|
4953
|
+
|
|
4954
|
+
### Pre-Investigation: Announce Your Plan
|
|
4955
|
+
|
|
4956
|
+
Before gathering evidence, announce a brief 2-3 line plan: restate the issue, list the first tools you'll use, then begin immediately.
|
|
4957
|
+
|
|
4958
|
+
### Round 1 \u2014 Orient
|
|
4959
|
+
|
|
4960
|
+
- ${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.
|
|
4961
|
+
- ${t}kinetica_bundle_log_timeline${t} (min_severity: WARN) \u2014 get the incident shape: when did WARN/ERROR/FATAL spike, and on which rank?
|
|
4962
|
+
|
|
4963
|
+
### Round 2 \u2014 Drill Down
|
|
4964
|
+
|
|
4965
|
+
Based on the timeline, narrow in:
|
|
4966
|
+
- ${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.
|
|
4967
|
+
- ${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).
|
|
4968
|
+
|
|
4969
|
+
### Round 3 \u2014 Confirm
|
|
4970
|
+
|
|
4971
|
+
- ${t}kinetica_bundle_read_config${t} \u2014 check gpudb.conf for misconfiguration / config-drift relevant to your hypothesis (tier limits, thread pools, ports, HA).
|
|
4972
|
+
- Re-search logs to confirm the root-cause sequence.
|
|
4973
|
+
|
|
4974
|
+
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.
|
|
4975
|
+
|
|
4976
|
+
### Parallel Tool Calls
|
|
4977
|
+
|
|
4978
|
+
Issue independent reads together where possible (e.g. timeline + list_files, or a log search alongside a sysinfo read).
|
|
4979
|
+
|
|
4980
|
+
---
|
|
4981
|
+
|
|
4982
|
+
## Evidence Checklist \u2014 Bundle Tools
|
|
4983
|
+
|
|
4984
|
+
${buildBundleEvidenceChecklist()}
|
|
4985
|
+
|
|
4986
|
+
---
|
|
4987
|
+
|
|
4988
|
+
${buildFailurePatternsSection(playbooks)}
|
|
4989
|
+
|
|
4990
|
+
${buildReferenceSection([...bundleReferences ?? [], ...references ?? []])}
|
|
4991
|
+
|
|
4992
|
+
---
|
|
4993
|
+
|
|
4994
|
+
## Analysis Instructions
|
|
4995
|
+
|
|
4996
|
+
### Commit to the Best Hypothesis
|
|
4997
|
+
|
|
4998
|
+
After gathering evidence, name specific root causes \u2014 no generic hedging.
|
|
4999
|
+
|
|
5000
|
+
**DO:**
|
|
5001
|
+
- "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."
|
|
5002
|
+
- "If uncertain, rank top 2-3 hypotheses by likelihood: Primary (70%): X; Secondary (25%): Y."
|
|
5003
|
+
|
|
5004
|
+
**DO NOT:**
|
|
5005
|
+
- "There could be various reasons..." / "Further investigation may be needed..."
|
|
5006
|
+
|
|
5007
|
+
### Tie Evidence to Conclusions
|
|
5008
|
+
|
|
5009
|
+
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."
|
|
5010
|
+
|
|
5011
|
+
### Evidence Gap Handling
|
|
5012
|
+
|
|
5013
|
+
Note gaps and continue \u2014 never halt on a missing artifact:
|
|
5014
|
+
- "Host memory at crash: unavailable (mem.txt is a point-in-time snapshot taken during collection, not at crash time)."
|
|
5015
|
+
- "GPU metrics: unavailable (nvidia-smi collection FAILED \u2014 CPU-only host)."
|
|
5016
|
+
|
|
5017
|
+
---
|
|
5018
|
+
|
|
5019
|
+
## Fix Instructions
|
|
5020
|
+
|
|
5021
|
+
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:
|
|
5022
|
+
|
|
5023
|
+
1. Immediate manual actions the operator should take on the live system
|
|
5024
|
+
2. Configuration changes to prevent recurrence (cite the gpudb.conf key + value)
|
|
5025
|
+
3. Monitoring/alerting improvements
|
|
5026
|
+
4. What to capture or verify on the live system to close remaining Evidence Gaps
|
|
5027
|
+
|
|
5028
|
+
---
|
|
5029
|
+
|
|
5030
|
+
## Post-Report Behavior
|
|
5031
|
+
|
|
5032
|
+
1. Present the finished report in your response so the operator can read it.
|
|
5033
|
+
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.)
|
|
5034
|
+
3. After saving (or after the operator declines), ask: "Would you like to investigate another issue in this bundle, or end the session?"
|
|
5035
|
+
4. On session end: summarize issues investigated and list saved report paths, then exit.
|
|
5036
|
+
|
|
5037
|
+
---
|
|
5038
|
+
|
|
5039
|
+
## Budget & Length Awareness
|
|
5040
|
+
|
|
5041
|
+
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.
|
|
5042
|
+
|
|
5043
|
+
---
|
|
5044
|
+
|
|
5045
|
+
## Output Formatting
|
|
5046
|
+
|
|
5047
|
+
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.
|
|
5048
|
+
|
|
5049
|
+
---
|
|
5050
|
+
|
|
5051
|
+
## REPORT TEMPLATE
|
|
5052
|
+
|
|
5053
|
+
At the end of each investigation, generate a structured markdown report using this EXACT template and section order:
|
|
5054
|
+
|
|
5055
|
+
\`\`\`markdown
|
|
5056
|
+
` + REPORT_TEMPLATE + `\`\`\`
|
|
5057
|
+
|
|
5058
|
+
**CRITICAL:** Use this exact section order. The metadata table comes first. Summary before Remediation. Evidence Collected before Evidence Gaps.
|
|
5059
|
+
|
|
5060
|
+
**Bundle-mode report notes:**
|
|
5061
|
+
- In the metadata, make clear this diagnosis is from an offline support bundle (note the node and detected version).
|
|
5062
|
+
- "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.
|
|
5063
|
+
- "Evidence Collected" \u2014 cite specific files, timestamps, and log lines (no raw dumps; the 3-10 most relevant findings).
|
|
5064
|
+
`;
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
// src/bundle/bundle-holder.ts
|
|
5068
|
+
function createBundleHolder(initial) {
|
|
5069
|
+
let current = initial;
|
|
5070
|
+
return {
|
|
5071
|
+
get: () => current,
|
|
5072
|
+
set: (source) => {
|
|
5073
|
+
current = source;
|
|
5074
|
+
},
|
|
5075
|
+
isLoaded: () => current !== void 0
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
5078
|
+
|
|
5079
|
+
// src/cli/pick-bundle-path.ts
|
|
5080
|
+
var import_promises7 = require("fs/promises");
|
|
5081
|
+
var import_node_path7 = require("path");
|
|
5082
|
+
function isPermissionError(err) {
|
|
5083
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return false;
|
|
5084
|
+
const code = err.code;
|
|
5085
|
+
return code === "EACCES" || code === "EPERM";
|
|
5086
|
+
}
|
|
5087
|
+
async function listDirectoryCandidates(term) {
|
|
5088
|
+
const input2 = term.trim() === "" ? "." : term;
|
|
5089
|
+
const endsWithSep = input2.endsWith("/");
|
|
5090
|
+
const baseDir = endsWithSep ? input2 : (0, import_node_path7.dirname)(input2) || ".";
|
|
5091
|
+
const prefix = endsWithSep ? "" : (0, import_node_path7.basename)(input2);
|
|
5092
|
+
const resolved = (0, import_node_path7.resolve)(baseDir);
|
|
5093
|
+
let entries;
|
|
5094
|
+
try {
|
|
5095
|
+
entries = await (0, import_promises7.readdir)(resolved, { withFileTypes: true });
|
|
5096
|
+
} catch (err) {
|
|
5097
|
+
if (isPermissionError(err)) return { kind: "denied", dir: resolved };
|
|
5098
|
+
return { kind: "ok", candidates: [] };
|
|
5099
|
+
}
|
|
5100
|
+
const candidates = entries.filter((e) => e.isDirectory() && e.name.startsWith(prefix)).map((e) => {
|
|
5101
|
+
const value = (0, import_node_path7.join)(baseDir, e.name);
|
|
5102
|
+
return { name: `${value}/`, value };
|
|
5103
|
+
}).sort((a, b) => a.value.localeCompare(b.value));
|
|
5104
|
+
return { kind: "ok", candidates };
|
|
5105
|
+
}
|
|
5106
|
+
function listingToChoices(listing) {
|
|
5107
|
+
if (listing.kind === "denied") {
|
|
5108
|
+
return [
|
|
5109
|
+
{
|
|
5110
|
+
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`,
|
|
5111
|
+
value: "",
|
|
5112
|
+
disabled: true
|
|
5113
|
+
}
|
|
5114
|
+
];
|
|
5115
|
+
}
|
|
5116
|
+
return listing.candidates.map((c) => ({ name: c.name, value: c.value }));
|
|
5117
|
+
}
|
|
5118
|
+
async function promptBundleDirectory() {
|
|
5119
|
+
if (!process.stdin.isTTY) return void 0;
|
|
5120
|
+
try {
|
|
5121
|
+
return await search({
|
|
5122
|
+
message: "Select the support bundle directory (type to filter):",
|
|
5123
|
+
source: async (term) => listingToChoices(await listDirectoryCandidates(term ?? ""))
|
|
5124
|
+
});
|
|
5125
|
+
} catch {
|
|
5126
|
+
return void 0;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
|
|
5130
|
+
// src/approval/display.ts
|
|
5131
|
+
var import_picocolors6 = __toESM(require("picocolors"));
|
|
5132
|
+
var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
|
|
5133
|
+
var DIVIDER2 = import_picocolors6.default.dim("\u2500".repeat(50));
|
|
5134
|
+
var LABEL_WIDTH = 8;
|
|
5135
|
+
function formatLabel(label) {
|
|
5136
|
+
return ` ${label.padEnd(LABEL_WIDTH)}: `;
|
|
5137
|
+
}
|
|
5138
|
+
function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
|
|
5139
|
+
const header = import_picocolors6.default.bold(import_picocolors6.default.yellow(" Mutation Approval Required"));
|
|
5140
|
+
const action = `${formatLabel("Action")}${import_picocolors6.default.bold(formatToolName(toolName))}`;
|
|
5141
|
+
const paramEntries = Object.entries(toolInput);
|
|
5142
|
+
const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
|
|
5143
|
+
const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
5144
|
+
return ` ${import_picocolors6.default.dim(key)}: ${formatted}`;
|
|
5145
|
+
}).join("\n");
|
|
5146
|
+
const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
|
|
5147
|
+
const prompt = import_picocolors6.default.dim(
|
|
5148
|
+
`${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
|
|
5149
|
+
);
|
|
5150
|
+
const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
|
|
5151
|
+
const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
|
|
5152
|
+
(entry) => ` ${import_picocolors6.default.dim(entry.key)}: ${entry.current} ${import_picocolors6.default.yellow("->")} ${entry.proposed}`
|
|
5153
|
+
).join("\n") : null;
|
|
5154
|
+
const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
|
|
5155
|
+
const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
|
|
5156
|
+
const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
|
|
5157
|
+
if (beforeAfterSection !== null) {
|
|
5158
|
+
sections.push(beforeAfterSection, "");
|
|
5159
|
+
}
|
|
5160
|
+
if (reasoningSection !== null) {
|
|
5161
|
+
sections.push(reasoningSection, "");
|
|
5162
|
+
}
|
|
5163
|
+
sections.push(impactLine, "", prompt, DIVIDER2, "");
|
|
5164
|
+
return sections.join("\n");
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5167
|
+
// src/approval/gate.ts
|
|
5168
|
+
var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
|
|
5169
|
+
var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
|
|
5170
|
+
function createApprovalGate(isReadOnly) {
|
|
5171
|
+
return async (toolName, toolInput, options) => {
|
|
5172
|
+
if (isReadOnly(toolName)) {
|
|
5173
|
+
return {
|
|
5174
|
+
behavior: "allow",
|
|
5175
|
+
updatedInput: toolInput,
|
|
5176
|
+
toolUseID: options.toolUseID
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
const impact = options.decisionReason;
|
|
5180
|
+
const panel = renderApprovalPanel(toolName, toolInput, impact);
|
|
5181
|
+
console.error(panel);
|
|
5182
|
+
while (true) {
|
|
5183
|
+
try {
|
|
5184
|
+
const raw = await input({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
|
|
5185
|
+
const normalized = raw.trim().toLowerCase();
|
|
5186
|
+
if (normalized === "y") {
|
|
5187
|
+
process.stderr.write("\n");
|
|
5188
|
+
return {
|
|
5189
|
+
behavior: "allow",
|
|
5190
|
+
updatedInput: toolInput,
|
|
5191
|
+
toolUseID: options.toolUseID
|
|
5192
|
+
};
|
|
5193
|
+
}
|
|
5194
|
+
if (normalized === "n") {
|
|
5195
|
+
process.stderr.write("\n");
|
|
5196
|
+
return {
|
|
5197
|
+
behavior: "deny",
|
|
5198
|
+
message: DENY_MESSAGE,
|
|
5199
|
+
toolUseID: options.toolUseID
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
if (normalized === "explain") {
|
|
5203
|
+
const reasoning = options.decisionReason;
|
|
5204
|
+
if (reasoning) {
|
|
5205
|
+
console.error(`
|
|
5206
|
+
Agent reasoning: ${reasoning}
|
|
5207
|
+
`);
|
|
5208
|
+
} else {
|
|
5209
|
+
console.error(`
|
|
5210
|
+
${REASONING_FALLBACK}
|
|
5211
|
+
`);
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
} catch {
|
|
5215
|
+
return {
|
|
5216
|
+
behavior: "deny",
|
|
5217
|
+
message: DENY_MESSAGE,
|
|
5218
|
+
toolUseID: options.toolUseID
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
// src/agent/turn-gate.ts
|
|
5226
|
+
function createTurnGate() {
|
|
5227
|
+
let resolve3 = () => {
|
|
5228
|
+
};
|
|
5229
|
+
let promise = new Promise((r) => {
|
|
5230
|
+
resolve3 = r;
|
|
5231
|
+
});
|
|
5232
|
+
return Object.freeze({
|
|
5233
|
+
wait: () => promise,
|
|
5234
|
+
open: () => {
|
|
5235
|
+
resolve3();
|
|
5236
|
+
},
|
|
5237
|
+
close: () => {
|
|
5238
|
+
promise = new Promise((r) => {
|
|
5239
|
+
resolve3 = r;
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5245
|
+
// src/output/render-markdown.ts
|
|
5246
|
+
var import_picocolors7 = __toESM(require("picocolors"));
|
|
5247
|
+
var HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
5248
|
+
var HRULE_RE = /^\s*([-*_])\1{2,}\s*$/;
|
|
5249
|
+
var BULLET_RE = /^(\s*)[-*+]\s+(.*)$/;
|
|
5250
|
+
var BOLD_RE = /\*\*(.+?)\*\*/g;
|
|
5251
|
+
var INLINE_CODE_RE = /`([^`]+)`/g;
|
|
5252
|
+
var UPPERWORD_RE = /[A-Z]{2,}/;
|
|
5253
|
+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
5254
|
+
var DEFAULT_WIDTH = 80;
|
|
5255
|
+
function terminalWidth() {
|
|
5256
|
+
const cols = process.stderr.columns;
|
|
5257
|
+
return typeof cols === "number" && cols > 0 ? cols : DEFAULT_WIDTH;
|
|
5258
|
+
}
|
|
5259
|
+
var SEVERITY_RULES = [
|
|
5260
|
+
{ re: /\b(FATAL|ERROR|UERR|CRITICAL|SEGV|SIGSEGV)\b/, color: import_picocolors7.default.red, glyph: "\u2717" },
|
|
5261
|
+
{ re: /\b(WARN|WARNING)\b/, color: import_picocolors7.default.yellow, glyph: "\u26A0" },
|
|
5262
|
+
{ re: /\b(OK|PASS|PASSED|HEALTHY)\b/, color: import_picocolors7.default.green, glyph: "\u2713" }
|
|
5263
|
+
];
|
|
5264
|
+
function firstSeverityRule(text2) {
|
|
5265
|
+
return SEVERITY_RULES.find((rule) => rule.re.test(text2));
|
|
5266
|
+
}
|
|
5267
|
+
function styleInline(text2) {
|
|
5268
|
+
let out = text2;
|
|
5269
|
+
if (out.includes("`")) out = out.replace(INLINE_CODE_RE, (_, code) => purple(code));
|
|
5270
|
+
if (out.includes("**")) out = out.replace(BOLD_RE, (_, b) => import_picocolors7.default.bold(b));
|
|
5271
|
+
if (UPPERWORD_RE.test(out)) {
|
|
5272
|
+
for (const rule of SEVERITY_RULES) {
|
|
5273
|
+
out = out.replace(rule.re, (token) => rule.color(token));
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
return out;
|
|
5277
|
+
}
|
|
5278
|
+
function bulletFor(itemText) {
|
|
5279
|
+
const rule = firstSeverityRule(itemText);
|
|
5280
|
+
return rule ? rule.color(rule.glyph) : purple("\u2022");
|
|
5281
|
+
}
|
|
5282
|
+
function visibleWidth(text2) {
|
|
5283
|
+
return styleInline(text2).replace(ANSI_RE, "").length;
|
|
5284
|
+
}
|
|
5285
|
+
function renderMarkdownLine(line) {
|
|
5286
|
+
if (HRULE_RE.test(line)) {
|
|
5287
|
+
return import_picocolors7.default.dim("\u2500".repeat(terminalWidth()));
|
|
5288
|
+
}
|
|
5289
|
+
const heading = HEADING_RE.exec(line);
|
|
5290
|
+
if (heading) {
|
|
5291
|
+
const level = heading[1].length;
|
|
5292
|
+
const text2 = heading[2];
|
|
5293
|
+
if (level === 1) {
|
|
5294
|
+
return `
|
|
5295
|
+
${import_picocolors7.default.bold(pink(text2))}
|
|
5296
|
+
${import_picocolors7.default.dim("\u2500".repeat(terminalWidth()))}`;
|
|
5297
|
+
}
|
|
5298
|
+
if (level === 2) {
|
|
5299
|
+
return `
|
|
5300
|
+
${import_picocolors7.default.bold(pink(`\u258C ${text2}`))}`;
|
|
5301
|
+
}
|
|
5302
|
+
return import_picocolors7.default.bold(purple(text2));
|
|
5303
|
+
}
|
|
5304
|
+
const bullet = BULLET_RE.exec(line);
|
|
5305
|
+
if (bullet) {
|
|
5306
|
+
const [, indent, item] = bullet;
|
|
5307
|
+
return `${indent}${bulletFor(item)} ${styleInline(item)}`;
|
|
5308
|
+
}
|
|
5309
|
+
return styleInline(line);
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5312
|
+
// src/output/reformat-tables.ts
|
|
5313
|
+
var SEPARATOR_CELL_RE = /^:?-+:?$/;
|
|
5314
|
+
function isSeparatorCell(cell) {
|
|
5315
|
+
return SEPARATOR_CELL_RE.test(cell);
|
|
5316
|
+
}
|
|
5317
|
+
function isSeparatorRow(cells) {
|
|
5318
|
+
return cells.length > 0 && cells.every(isSeparatorCell);
|
|
5319
|
+
}
|
|
5320
|
+
function parseCells(line) {
|
|
5321
|
+
return line.split("|").slice(1, -1).map((c) => c.trim());
|
|
5322
|
+
}
|
|
5323
|
+
function reformatTableBlock(lines) {
|
|
5324
|
+
const parsed = lines.map(parseCells);
|
|
5325
|
+
const colCount = Math.max(...parsed.map((row) => row.length));
|
|
5326
|
+
const normalised = parsed.map((row) => {
|
|
5327
|
+
const padded = [...row];
|
|
5328
|
+
while (padded.length < colCount) {
|
|
5329
|
+
padded.push("");
|
|
5330
|
+
}
|
|
5331
|
+
return padded;
|
|
5332
|
+
});
|
|
5333
|
+
const colWidths = Array.from(
|
|
5334
|
+
{ length: colCount },
|
|
5335
|
+
(_, col) => Math.max(
|
|
5336
|
+
3,
|
|
5337
|
+
...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visibleWidth(row[col]))
|
|
5338
|
+
)
|
|
5339
|
+
);
|
|
5340
|
+
const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
|
|
5341
|
+
const bodyRows = normalised.map((row) => {
|
|
3960
5342
|
if (isSeparatorRow(row)) {
|
|
3961
5343
|
return borderRow;
|
|
3962
5344
|
}
|
|
3963
5345
|
const cells = row.map((cell, col) => {
|
|
3964
|
-
const rendered =
|
|
3965
|
-
const pad = colWidths[col] -
|
|
5346
|
+
const rendered = styleInline(cell);
|
|
5347
|
+
const pad = colWidths[col] - visibleWidth(cell);
|
|
3966
5348
|
return rendered + " ".repeat(Math.max(0, pad));
|
|
3967
5349
|
});
|
|
3968
5350
|
return `| ${cells.join(" | ")} |`;
|
|
@@ -3981,9 +5363,9 @@ function createStreamingTableAligner() {
|
|
|
3981
5363
|
tableLines = [];
|
|
3982
5364
|
return aligned.join("\n") + "\n";
|
|
3983
5365
|
}
|
|
3984
|
-
function push(
|
|
3985
|
-
if (!
|
|
3986
|
-
const combined = lineBuffer +
|
|
5366
|
+
function push(text2) {
|
|
5367
|
+
if (!text2) return "";
|
|
5368
|
+
const combined = lineBuffer + text2;
|
|
3987
5369
|
const segments = combined.split("\n");
|
|
3988
5370
|
lineBuffer = segments[segments.length - 1];
|
|
3989
5371
|
const completeLines = segments.slice(0, -1);
|
|
@@ -4011,7 +5393,7 @@ function createStreamingTableAligner() {
|
|
|
4011
5393
|
}
|
|
4012
5394
|
|
|
4013
5395
|
// src/output/spinner.ts
|
|
4014
|
-
var
|
|
5396
|
+
var import_picocolors8 = __toESM(require("picocolors"));
|
|
4015
5397
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4016
5398
|
var FRAME_INTERVAL_MS = 80;
|
|
4017
5399
|
var DEFAULT_LABEL = "Thinking";
|
|
@@ -4023,7 +5405,7 @@ function createSpinner() {
|
|
|
4023
5405
|
frameIndex = 0;
|
|
4024
5406
|
timer = setInterval(() => {
|
|
4025
5407
|
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
4026
|
-
process.stderr.write(`\r${
|
|
5408
|
+
process.stderr.write(`\r${import_picocolors8.default.dim(`${frame} ${label}...`)}`);
|
|
4027
5409
|
frameIndex += 1;
|
|
4028
5410
|
}, FRAME_INTERVAL_MS);
|
|
4029
5411
|
timer.unref();
|
|
@@ -4060,11 +5442,17 @@ function formatMetricsLine(turns, durationMs, durationApiMs, costUsd) {
|
|
|
4060
5442
|
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
|
|
4061
5443
|
var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
|
|
4062
5444
|
var DEFAULT_AGENT_MODEL = "sonnet";
|
|
5445
|
+
var LIVE_MAX_TURNS = 100;
|
|
5446
|
+
var BUNDLE_MAX_TURNS = 40;
|
|
4063
5447
|
var ALLOWED_TOOL_NAMES = [
|
|
4064
5448
|
...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
4065
5449
|
SAVE_REPORT_TOOL_NAME,
|
|
4066
5450
|
`mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
|
|
4067
5451
|
];
|
|
5452
|
+
var BUNDLE_ALLOWED_TOOL_NAMES = [
|
|
5453
|
+
...BUNDLE_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
5454
|
+
SAVE_REPORT_TOOL_NAME
|
|
5455
|
+
];
|
|
4068
5456
|
var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
|
|
4069
5457
|
var ERROR_LABELS = {
|
|
4070
5458
|
authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
|
|
@@ -4075,8 +5463,8 @@ var ERROR_LABELS = {
|
|
|
4075
5463
|
max_output_tokens: "Response exceeded maximum output length",
|
|
4076
5464
|
unknown: "Unknown API error"
|
|
4077
5465
|
};
|
|
4078
|
-
function isExitCommand(
|
|
4079
|
-
return EXIT_COMMANDS.has(
|
|
5466
|
+
function isExitCommand(text2) {
|
|
5467
|
+
return EXIT_COMMANDS.has(text2.trim().toLowerCase());
|
|
4080
5468
|
}
|
|
4081
5469
|
function makeUserMessage(content) {
|
|
4082
5470
|
return {
|
|
@@ -4090,7 +5478,9 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
|
|
|
4090
5478
|
while (!abortController.signal.aborted) {
|
|
4091
5479
|
try {
|
|
4092
5480
|
process.stderr.write("\n");
|
|
4093
|
-
const issue = await
|
|
5481
|
+
const issue = await input({
|
|
5482
|
+
message: "Describe the issue to investigate:"
|
|
5483
|
+
});
|
|
4094
5484
|
process.stderr.write("\n");
|
|
4095
5485
|
const trimmed = issue.trim();
|
|
4096
5486
|
if (!trimmed) continue;
|
|
@@ -4107,7 +5497,7 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
|
|
|
4107
5497
|
await turnGate.wait();
|
|
4108
5498
|
if (abortController.signal.aborted) break;
|
|
4109
5499
|
process.stderr.write("\n");
|
|
4110
|
-
const response = await
|
|
5500
|
+
const response = await input({ message: "You:" });
|
|
4111
5501
|
process.stderr.write("\n");
|
|
4112
5502
|
const trimmed = response.trim();
|
|
4113
5503
|
if (!trimmed) continue;
|
|
@@ -4125,79 +5515,116 @@ async function displayDegradedStatus(session2) {
|
|
|
4125
5515
|
hostManagerStatus(session2),
|
|
4126
5516
|
hostManagerAlerts(session2)
|
|
4127
5517
|
]);
|
|
4128
|
-
process.stderr.write(
|
|
5518
|
+
process.stderr.write(import_picocolors9.default.bold("\u2500\u2500 Host Manager Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
4129
5519
|
if (statusResult.ok) {
|
|
4130
5520
|
const rows = statusResult.data;
|
|
4131
5521
|
const maxKeyLen = rows.reduce((max, r) => Math.max(max, r.key.length), 0);
|
|
4132
5522
|
for (const row of rows) {
|
|
4133
|
-
process.stderr.write(` ${
|
|
5523
|
+
process.stderr.write(` ${import_picocolors9.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
|
|
4134
5524
|
`);
|
|
4135
5525
|
}
|
|
4136
5526
|
} else {
|
|
4137
|
-
process.stderr.write(` ${
|
|
5527
|
+
process.stderr.write(` ${import_picocolors9.default.red(`Error: ${statusResult.error}`)}
|
|
4138
5528
|
`);
|
|
4139
5529
|
}
|
|
4140
5530
|
process.stderr.write("\n");
|
|
4141
|
-
process.stderr.write(
|
|
5531
|
+
process.stderr.write(import_picocolors9.default.bold("\u2500\u2500 Recent Alerts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
4142
5532
|
if (alertsResult.ok) {
|
|
4143
5533
|
const alerts = alertsResult.data;
|
|
4144
5534
|
if (alerts.length === 0) {
|
|
4145
|
-
process.stderr.write(` ${
|
|
5535
|
+
process.stderr.write(` ${import_picocolors9.default.dim("No recent alerts.")}
|
|
4146
5536
|
`);
|
|
4147
5537
|
} else {
|
|
4148
5538
|
for (const alert of alerts) {
|
|
4149
|
-
process.stderr.write(` ${
|
|
5539
|
+
process.stderr.write(` ${import_picocolors9.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
|
|
4150
5540
|
`);
|
|
4151
5541
|
}
|
|
4152
5542
|
}
|
|
4153
5543
|
} else {
|
|
4154
|
-
process.stderr.write(` ${
|
|
5544
|
+
process.stderr.write(` ${import_picocolors9.default.dim(`Unavailable: ${alertsResult.error}`)}
|
|
4155
5545
|
`);
|
|
4156
5546
|
}
|
|
4157
5547
|
process.stderr.write("\n");
|
|
4158
5548
|
}
|
|
4159
5549
|
async function runAgent(session2, kineticaVersion, degraded, model, runOptions) {
|
|
5550
|
+
const bundleSource = runOptions?.bundleSource;
|
|
4160
5551
|
const authMethod = runOptions?.authMethod ?? "api_key";
|
|
4161
5552
|
const dollarCapped = authMethod === "api_key";
|
|
4162
5553
|
const resolvedBudgetUsd = runOptions?.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
|
|
4163
|
-
const [catalogSchemas, playbooks, references] = await Promise.all([
|
|
4164
|
-
degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
|
|
5554
|
+
const [catalogSchemas, playbooks, references, bundleReferences] = await Promise.all([
|
|
5555
|
+
degraded || !session2 ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
|
|
4165
5556
|
loadPlaybooks(),
|
|
4166
|
-
loadReferences()
|
|
5557
|
+
loadReferences(),
|
|
5558
|
+
loadBundleReferences()
|
|
4167
5559
|
]);
|
|
4168
|
-
const
|
|
5560
|
+
const bundleHolder = createBundleHolder(bundleSource);
|
|
5561
|
+
const systemPrompt = session2 ? buildSystemPrompt(
|
|
4169
5562
|
kineticaVersion,
|
|
4170
5563
|
catalogSchemas,
|
|
4171
5564
|
playbooks,
|
|
4172
5565
|
references,
|
|
4173
|
-
degraded
|
|
4174
|
-
|
|
5566
|
+
degraded,
|
|
5567
|
+
bundleHolder.isLoaded() ? "attached" : "available",
|
|
5568
|
+
bundleReferences
|
|
5569
|
+
) : buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences);
|
|
4175
5570
|
const budget = checkPromptBudget(systemPrompt);
|
|
4176
5571
|
if (process.env.DEBUG) {
|
|
4177
5572
|
process.stderr.write(
|
|
4178
|
-
|
|
5573
|
+
import_picocolors9.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
|
|
4179
5574
|
`)
|
|
4180
5575
|
);
|
|
4181
5576
|
}
|
|
4182
5577
|
if (budget.overBudget) {
|
|
4183
5578
|
process.stderr.write(
|
|
4184
|
-
|
|
5579
|
+
import_picocolors9.default.yellow(
|
|
4185
5580
|
`\u26A0 system prompt is ~${budget.tokens} tokens (threshold ${budget.threshold}) \u2014 knowledge corpus is getting expensive; consider keyword-based playbook selection.
|
|
4186
5581
|
`
|
|
4187
5582
|
)
|
|
4188
5583
|
);
|
|
4189
5584
|
}
|
|
4190
|
-
const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
|
|
4191
|
-
const mutationTools = makeMutationTools(session2);
|
|
4192
5585
|
const saveReportTool = makeSaveReportTool();
|
|
4193
|
-
|
|
4194
|
-
|
|
5586
|
+
if (!session2 && !bundleSource) {
|
|
5587
|
+
throw new Error("runAgent requires a Kinetica session, a bundleSource, or both.");
|
|
5588
|
+
}
|
|
5589
|
+
const spinner = createSpinner();
|
|
5590
|
+
const promptForPath = async () => {
|
|
5591
|
+
spinner.stop();
|
|
5592
|
+
return promptBundleDirectory();
|
|
5593
|
+
};
|
|
5594
|
+
const confirmPath = async (path2) => {
|
|
5595
|
+
spinner.stop();
|
|
5596
|
+
try {
|
|
5597
|
+
const answer = await input({
|
|
5598
|
+
message: `Load support bundle from "${path2}"? The agent will be able to read files under that directory. (y/n):`
|
|
5599
|
+
});
|
|
5600
|
+
return answer.trim().toLowerCase() === "y";
|
|
5601
|
+
} catch {
|
|
5602
|
+
return false;
|
|
5603
|
+
}
|
|
5604
|
+
};
|
|
5605
|
+
const bundleTools = makeBundleTools(bundleHolder, { promptForPath, confirmPath });
|
|
5606
|
+
const liveTools = session2 ? [
|
|
5607
|
+
...makeDiagnosticTools(session2, catalogSchemas),
|
|
5608
|
+
...makeMutationTools(session2),
|
|
5609
|
+
makeAlterTableColumnsToolWithDeps(session2)
|
|
5610
|
+
] : [];
|
|
5611
|
+
const serverTools = [...liveTools, ...bundleTools, saveReportTool];
|
|
5612
|
+
const allowedTools = [
|
|
5613
|
+
.../* @__PURE__ */ new Set([...session2 ? ALLOWED_TOOL_NAMES : [], ...BUNDLE_ALLOWED_TOOL_NAMES])
|
|
5614
|
+
];
|
|
5615
|
+
let registry = createBundleRegistry();
|
|
5616
|
+
if (session2) {
|
|
5617
|
+
registry = DIAGNOSTIC_TOOL_NAMES.reduce(
|
|
5618
|
+
(reg, name) => reg.registerReadOnlyTool(name),
|
|
5619
|
+
registry
|
|
5620
|
+
);
|
|
5621
|
+
}
|
|
5622
|
+
const maxTurns = session2 ? LIVE_MAX_TURNS : BUNDLE_MAX_TURNS;
|
|
5623
|
+
const server = (0, import_claude_agent_sdk5.createSdkMcpServer)({
|
|
4195
5624
|
name: MCP_SERVER_NAME,
|
|
4196
5625
|
version: "1.0.0",
|
|
4197
|
-
tools:
|
|
5626
|
+
tools: serverTools
|
|
4198
5627
|
});
|
|
4199
|
-
const spinner = createSpinner();
|
|
4200
|
-
const registry = createDiagnosticRegistry();
|
|
4201
5628
|
const approvalGate = createApprovalGate(registry.isReadOnlyTool);
|
|
4202
5629
|
const canUseTool = async (toolName, toolInput, options2) => {
|
|
4203
5630
|
spinner.stop();
|
|
@@ -4207,14 +5634,14 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
|
|
|
4207
5634
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4208
5635
|
const options = {
|
|
4209
5636
|
mcpServers: { [MCP_SERVER_NAME]: server },
|
|
4210
|
-
allowedTools
|
|
5637
|
+
allowedTools,
|
|
4211
5638
|
disallowedTools: [...DISALLOWED_TOOLS],
|
|
4212
5639
|
canUseTool,
|
|
4213
5640
|
systemPrompt,
|
|
4214
5641
|
model: effectiveModel,
|
|
4215
5642
|
fallbackModel: "haiku",
|
|
4216
5643
|
thinking: { type: "adaptive" },
|
|
4217
|
-
maxTurns
|
|
5644
|
+
maxTurns,
|
|
4218
5645
|
// Only impose a dollar cap for per-token billing. For OAuth subscription users
|
|
4219
5646
|
// the SDK would otherwise cut them off at a notional dollar figure they never pay;
|
|
4220
5647
|
// omitting it leaves the turn limit (maxTurns) as their guard.
|
|
@@ -4224,21 +5651,47 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
|
|
|
4224
5651
|
abortController,
|
|
4225
5652
|
env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
|
|
4226
5653
|
};
|
|
4227
|
-
const guardLine = dollarCapped ?
|
|
4228
|
-
`) :
|
|
4229
|
-
|
|
4230
|
-
|
|
5654
|
+
const guardLine = dollarCapped ? import_picocolors9.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
|
|
5655
|
+
`) : import_picocolors9.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
|
|
5656
|
+
const bundleSummary = () => {
|
|
5657
|
+
const src = bundleHolder.get();
|
|
5658
|
+
if (!src) return "";
|
|
5659
|
+
const { totalFiles, ranks } = src.inventory();
|
|
5660
|
+
const versionStr = kineticaVersion ? ` (version ${kineticaVersion})` : "";
|
|
5661
|
+
return `${totalFiles} files, ranks: ${ranks.join(", ") || "none"}${versionStr}`;
|
|
5662
|
+
};
|
|
5663
|
+
if (session2) {
|
|
5664
|
+
if (degraded) {
|
|
5665
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
|
|
5666
|
+
process.stderr.write(
|
|
5667
|
+
"DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
|
|
5668
|
+
);
|
|
5669
|
+
await displayDegradedStatus(session2);
|
|
5670
|
+
} else {
|
|
5671
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
5672
|
+
}
|
|
5673
|
+
if (bundleHolder.isLoaded()) {
|
|
5674
|
+
process.stderr.write(
|
|
5675
|
+
`Support bundle attached: ${bundleSummary()}. Live + bundle tools available.
|
|
5676
|
+
`
|
|
5677
|
+
);
|
|
5678
|
+
} else {
|
|
5679
|
+
process.stderr.write(
|
|
5680
|
+
import_picocolors9.default.dim("Tip: ask me to analyze a support bundle to add offline log/config analysis.\n")
|
|
5681
|
+
);
|
|
5682
|
+
}
|
|
5683
|
+
} else {
|
|
5684
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready (OFFLINE BUNDLE MODE)\n");
|
|
5685
|
+
process.stderr.write(`Analyzing extracted support bundle: ${bundleSummary()}.
|
|
5686
|
+
`);
|
|
4231
5687
|
process.stderr.write(
|
|
4232
|
-
"
|
|
5688
|
+
"Read-only \u2014 diagnoses from captured logs/config; live verification unavailable (no DB connection).\n"
|
|
4233
5689
|
);
|
|
4234
|
-
await displayDegradedStatus(session2);
|
|
4235
|
-
} else {
|
|
4236
|
-
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
4237
5690
|
}
|
|
4238
5691
|
process.stderr.write(guardLine);
|
|
4239
5692
|
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4240
5693
|
const turnGate = createTurnGate();
|
|
4241
|
-
const agentQuery = (0,
|
|
5694
|
+
const agentQuery = (0, import_claude_agent_sdk5.query)({
|
|
4242
5695
|
prompt: makeInteractivePrompt(abortController, turnGate, spinner),
|
|
4243
5696
|
options
|
|
4244
5697
|
});
|
|
@@ -4265,10 +5718,10 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
|
|
|
4265
5718
|
if (message.type === "stream_event") {
|
|
4266
5719
|
const { event: evt } = message;
|
|
4267
5720
|
if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
|
|
4268
|
-
const
|
|
4269
|
-
if (
|
|
5721
|
+
const text2 = evt.delta.text ?? "";
|
|
5722
|
+
if (text2) {
|
|
4270
5723
|
spinner.stop();
|
|
4271
|
-
const output = tableAligner.push(
|
|
5724
|
+
const output = tableAligner.push(text2);
|
|
4272
5725
|
if (output) {
|
|
4273
5726
|
process.stderr.write(output);
|
|
4274
5727
|
lastStreamCharWasNewline = output.endsWith("\n");
|
|
@@ -4291,7 +5744,7 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
|
|
|
4291
5744
|
if (budgetTracker.shouldWarn()) {
|
|
4292
5745
|
spinner.stop();
|
|
4293
5746
|
process.stderr.write(
|
|
4294
|
-
|
|
5747
|
+
import_picocolors9.default.yellow(
|
|
4295
5748
|
`
|
|
4296
5749
|
\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.
|
|
4297
5750
|
`
|
|
@@ -4314,7 +5767,7 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
|
|
|
4314
5767
|
if (assistantMsg.error) {
|
|
4315
5768
|
spinner.stop();
|
|
4316
5769
|
const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
|
|
4317
|
-
process.stderr.write(
|
|
5770
|
+
process.stderr.write(import_picocolors9.default.yellow(`
|
|
4318
5771
|
API error: ${label}
|
|
4319
5772
|
`));
|
|
4320
5773
|
turnGate.open();
|
|
@@ -4331,7 +5784,7 @@ API error: ${label}
|
|
|
4331
5784
|
cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
|
|
4332
5785
|
if (resultMsg.subtype === "error_max_turns") {
|
|
4333
5786
|
process.stderr.write(
|
|
4334
|
-
|
|
5787
|
+
import_picocolors9.default.yellow(
|
|
4335
5788
|
`
|
|
4336
5789
|
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.
|
|
4337
5790
|
`
|
|
@@ -4340,7 +5793,7 @@ Reached the turn limit (${numTurns} turns) \u2014 a safety guard, not an error.
|
|
|
4340
5793
|
} else if (resultMsg.subtype === "error_max_budget_usd") {
|
|
4341
5794
|
const spentStr = totalCostUsd > 0 ? ` ($${totalCostUsd.toFixed(2)} spent)` : "";
|
|
4342
5795
|
process.stderr.write(
|
|
4343
|
-
|
|
5796
|
+
import_picocolors9.default.yellow(
|
|
4344
5797
|
`
|
|
4345
5798
|
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/.
|
|
4346
5799
|
`
|
|
@@ -4399,7 +5852,7 @@ Warning: MCP server "${s.name}" failed to connect (${s.status})
|
|
|
4399
5852
|
const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
|
|
4400
5853
|
const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
|
|
4401
5854
|
process.stderr.write(
|
|
4402
|
-
|
|
5855
|
+
import_picocolors9.default.yellow(
|
|
4403
5856
|
`
|
|
4404
5857
|
API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
|
|
4405
5858
|
`
|
|
@@ -4428,18 +5881,18 @@ Rate limited \u2014 requests rejected.${resetStr}
|
|
|
4428
5881
|
} else if (message.type === "control_request") {
|
|
4429
5882
|
const controlMsg = message;
|
|
4430
5883
|
if (controlMsg.request.subtype === "claude_authenticate") {
|
|
4431
|
-
process.stderr.write(
|
|
5884
|
+
process.stderr.write(import_picocolors9.default.yellow("\nRe-authentication requested by SDK...\n"));
|
|
4432
5885
|
}
|
|
4433
5886
|
}
|
|
4434
5887
|
}
|
|
4435
5888
|
} catch (error) {
|
|
4436
5889
|
spinner.stop();
|
|
4437
|
-
if (error instanceof
|
|
5890
|
+
if (error instanceof import_claude_agent_sdk5.AbortError) {
|
|
4438
5891
|
hadNonAbortError = false;
|
|
4439
5892
|
} else {
|
|
4440
5893
|
hadNonAbortError = true;
|
|
4441
5894
|
const message = error instanceof Error ? error.message : String(error);
|
|
4442
|
-
process.stderr.write(
|
|
5895
|
+
process.stderr.write(import_picocolors9.default.red(`
|
|
4443
5896
|
Agent error: ${message}
|
|
4444
5897
|
`));
|
|
4445
5898
|
}
|
|
@@ -4453,7 +5906,7 @@ Agent error: ${message}
|
|
|
4453
5906
|
const sessionCost = dollarCapped ? totalCostUsd : void 0;
|
|
4454
5907
|
if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
|
|
4455
5908
|
process.stderr.write(
|
|
4456
|
-
|
|
5909
|
+
import_picocolors9.default.dim(
|
|
4457
5910
|
`Cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
|
|
4458
5911
|
`
|
|
4459
5912
|
)
|
|
@@ -4481,7 +5934,7 @@ var MODEL_LABELS = {
|
|
|
4481
5934
|
opus: "Opus \u2014 deepest reasoning, slower & pricier"
|
|
4482
5935
|
};
|
|
4483
5936
|
async function selectModel() {
|
|
4484
|
-
return
|
|
5937
|
+
return select({
|
|
4485
5938
|
message: "Select model for this session:",
|
|
4486
5939
|
default: DEFAULT_AGENT_MODEL,
|
|
4487
5940
|
choices: SUPPORTED_MODELS.map((value) => ({ value, name: MODEL_LABELS[value] }))
|
|
@@ -4489,10 +5942,10 @@ async function selectModel() {
|
|
|
4489
5942
|
}
|
|
4490
5943
|
|
|
4491
5944
|
// src/auth/preflight.ts
|
|
4492
|
-
var
|
|
5945
|
+
var import_claude_agent_sdk6 = require("@anthropic-ai/claude-agent-sdk");
|
|
4493
5946
|
|
|
4494
5947
|
// src/auth/oauth-flow.ts
|
|
4495
|
-
var
|
|
5948
|
+
var import_picocolors10 = __toESM(require("picocolors"));
|
|
4496
5949
|
|
|
4497
5950
|
// src/auth/open-browser.ts
|
|
4498
5951
|
var import_child_process = require("child_process");
|
|
@@ -4518,21 +5971,21 @@ async function resolveAuthentication(agentQuery, options) {
|
|
|
4518
5971
|
const { manualUrl, automaticUrl } = await query3.claudeAuthenticate(options.loginWithClaudeAi);
|
|
4519
5972
|
const opened = openBrowser(automaticUrl);
|
|
4520
5973
|
if (opened) {
|
|
4521
|
-
process.stderr.write(
|
|
5974
|
+
process.stderr.write(import_picocolors10.default.dim("Browser opened for login. Waiting for authentication...\n"));
|
|
4522
5975
|
} else {
|
|
4523
5976
|
process.stderr.write(`
|
|
4524
5977
|
Open this URL in your browser to log in:
|
|
4525
|
-
${
|
|
5978
|
+
${import_picocolors10.default.bold(manualUrl)}
|
|
4526
5979
|
|
|
4527
5980
|
`);
|
|
4528
|
-
process.stderr.write(
|
|
5981
|
+
process.stderr.write(import_picocolors10.default.dim("Waiting for browser login to complete...\n"));
|
|
4529
5982
|
}
|
|
4530
5983
|
await query3.claudeOAuthWaitForCompletion();
|
|
4531
5984
|
return { method: "oauth" };
|
|
4532
5985
|
} catch (err) {
|
|
4533
5986
|
const message = err instanceof Error ? err.message : String(err);
|
|
4534
5987
|
process.stderr.write(
|
|
4535
|
-
|
|
5988
|
+
import_picocolors10.default.yellow(`
|
|
4536
5989
|
Warning: OAuth login failed (${message}). SDK may retry automatically.
|
|
4537
5990
|
`)
|
|
4538
5991
|
);
|
|
@@ -4577,7 +6030,7 @@ async function authenticateAnthropic(options) {
|
|
|
4577
6030
|
await new Promise(() => {
|
|
4578
6031
|
});
|
|
4579
6032
|
}
|
|
4580
|
-
const authQuery = (0,
|
|
6033
|
+
const authQuery = (0, import_claude_agent_sdk6.query)({
|
|
4581
6034
|
prompt: hangingPrompt(),
|
|
4582
6035
|
options: {
|
|
4583
6036
|
persistSession: false,
|
|
@@ -4609,12 +6062,12 @@ async function authenticateAnthropic(options) {
|
|
|
4609
6062
|
// src/auth/logout.ts
|
|
4610
6063
|
var import_child_process2 = require("child_process");
|
|
4611
6064
|
var import_node_module = require("module");
|
|
4612
|
-
var
|
|
6065
|
+
var import_node_path8 = __toESM(require("path"));
|
|
4613
6066
|
var import_util = require("util");
|
|
4614
6067
|
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
4615
6068
|
function resolveSdkCliPath() {
|
|
4616
6069
|
const require_ = (0, import_node_module.createRequire)(__filename);
|
|
4617
|
-
return
|
|
6070
|
+
return import_node_path8.default.join(import_node_path8.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
|
|
4618
6071
|
}
|
|
4619
6072
|
async function logout() {
|
|
4620
6073
|
try {
|
|
@@ -4634,10 +6087,9 @@ async function logout() {
|
|
|
4634
6087
|
|
|
4635
6088
|
// src/session/env-file.ts
|
|
4636
6089
|
var import_fs2 = require("fs");
|
|
4637
|
-
var
|
|
6090
|
+
var import_promises8 = require("fs/promises");
|
|
4638
6091
|
var import_path2 = require("path");
|
|
4639
|
-
var
|
|
4640
|
-
var import_picocolors10 = __toESM(require("picocolors"));
|
|
6092
|
+
var import_picocolors11 = __toESM(require("picocolors"));
|
|
4641
6093
|
function parseEnvContent(content) {
|
|
4642
6094
|
const entries = [];
|
|
4643
6095
|
for (const line of content.split("\n")) {
|
|
@@ -4719,7 +6171,7 @@ function loadEnvFile(dir, env = process.env) {
|
|
|
4719
6171
|
async function offerSaveCredentials(url, user, dir) {
|
|
4720
6172
|
if (!process.stdin.isTTY) return;
|
|
4721
6173
|
try {
|
|
4722
|
-
const shouldSave = await
|
|
6174
|
+
const shouldSave = await confirm({
|
|
4723
6175
|
message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
|
|
4724
6176
|
default: true
|
|
4725
6177
|
});
|
|
@@ -4727,52 +6179,50 @@ async function offerSaveCredentials(url, user, dir) {
|
|
|
4727
6179
|
const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
|
|
4728
6180
|
let existing;
|
|
4729
6181
|
try {
|
|
4730
|
-
existing = await (0,
|
|
6182
|
+
existing = await (0, import_promises8.readFile)(filePath, "utf8");
|
|
4731
6183
|
} catch {
|
|
4732
6184
|
}
|
|
4733
6185
|
const content = buildEnvContent(url, user, existing);
|
|
4734
|
-
await (0,
|
|
4735
|
-
console.error(
|
|
6186
|
+
await (0, import_promises8.writeFile)(filePath, content, "utf8");
|
|
6187
|
+
console.error(import_picocolors11.default.dim("Saved to .env"));
|
|
4736
6188
|
} catch (err) {
|
|
4737
6189
|
const message = err instanceof Error ? err.message : String(err);
|
|
4738
|
-
console.error(
|
|
6190
|
+
console.error(import_picocolors11.default.yellow(`Could not save .env file: ${message}`));
|
|
4739
6191
|
}
|
|
4740
6192
|
}
|
|
4741
6193
|
|
|
4742
6194
|
// src/session/verify.ts
|
|
4743
|
-
var
|
|
4744
|
-
var import_prompts9 = require("@inquirer/prompts");
|
|
6195
|
+
var import_picocolors14 = __toESM(require("picocolors"));
|
|
4745
6196
|
|
|
4746
6197
|
// src/session/collect.ts
|
|
4747
|
-
var
|
|
4748
|
-
var import_picocolors11 = __toESM(require("picocolors"));
|
|
6198
|
+
var import_picocolors12 = __toESM(require("picocolors"));
|
|
4749
6199
|
async function collectCredentials() {
|
|
4750
6200
|
const prompted = /* @__PURE__ */ new Set();
|
|
4751
6201
|
const envUrl = process.env.KINETICA_URL;
|
|
4752
6202
|
const envUser = process.env.KINETICA_USER;
|
|
4753
6203
|
if (envUrl && envUser && process.stdin.isTTY) {
|
|
4754
|
-
console.error(
|
|
4755
|
-
const useSaved = await
|
|
6204
|
+
console.error(import_picocolors12.default.dim(`Saved connection: ${envUrl} (${envUser})`));
|
|
6205
|
+
const useSaved = await confirm({
|
|
4756
6206
|
message: "Use saved connection?",
|
|
4757
6207
|
default: true
|
|
4758
6208
|
});
|
|
4759
6209
|
if (!useSaved) {
|
|
4760
6210
|
prompted.add("url");
|
|
4761
6211
|
prompted.add("user");
|
|
4762
|
-
const url2 = await
|
|
4763
|
-
const user2 = await
|
|
4764
|
-
const pass2 = await
|
|
6212
|
+
const url2 = await input({ message: "Kinetica endpoint URL:" });
|
|
6213
|
+
const user2 = await input({ message: "Admin username:" });
|
|
6214
|
+
const pass2 = await password({ message: "Admin password:", mask: "*" });
|
|
4765
6215
|
return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
|
|
4766
6216
|
}
|
|
4767
6217
|
}
|
|
4768
|
-
const url = envUrl ?? (prompted.add("url"), await
|
|
4769
|
-
const user = envUser ?? (prompted.add("user"), await
|
|
4770
|
-
const pass = process.env.KINETICA_PASS ?? await
|
|
6218
|
+
const url = envUrl ?? (prompted.add("url"), await input({ message: "Kinetica endpoint URL:" }));
|
|
6219
|
+
const user = envUser ?? (prompted.add("user"), await input({ message: "Admin username:" }));
|
|
6220
|
+
const pass = process.env.KINETICA_PASS ?? await password({ message: "Admin password:", mask: "*" });
|
|
4771
6221
|
return { credentials: { url, user, pass }, prompted };
|
|
4772
6222
|
}
|
|
4773
6223
|
async function repromptCredentials() {
|
|
4774
|
-
const user = await
|
|
4775
|
-
const pass = await
|
|
6224
|
+
const user = await input({ message: "Admin username:" });
|
|
6225
|
+
const pass = await password({ message: "Admin password:", mask: "*" });
|
|
4776
6226
|
return { user, pass };
|
|
4777
6227
|
}
|
|
4778
6228
|
|
|
@@ -4783,8 +6233,9 @@ function replacePort(baseUrl, port) {
|
|
|
4783
6233
|
parsed.port = String(port);
|
|
4784
6234
|
return parsed.origin;
|
|
4785
6235
|
}
|
|
4786
|
-
function createSession(url, user, pass) {
|
|
6236
|
+
function createSession(url, user, pass, options) {
|
|
4787
6237
|
const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
|
|
6238
|
+
const timeoutMs = options?.timeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
4788
6239
|
const doFetch = async (fullUrl, body) => {
|
|
4789
6240
|
if (process.env.DEBUG) {
|
|
4790
6241
|
console.error(`[DEBUG] POST ${fullUrl}`);
|
|
@@ -4796,7 +6247,7 @@ function createSession(url, user, pass) {
|
|
|
4796
6247
|
"Content-Type": "application/json"
|
|
4797
6248
|
},
|
|
4798
6249
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
4799
|
-
signal: AbortSignal.timeout(
|
|
6250
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
4800
6251
|
});
|
|
4801
6252
|
};
|
|
4802
6253
|
return {
|
|
@@ -4807,13 +6258,12 @@ function createSession(url, user, pass) {
|
|
|
4807
6258
|
}
|
|
4808
6259
|
|
|
4809
6260
|
// src/session/resolve-url.ts
|
|
4810
|
-
var
|
|
4811
|
-
var import_prompts8 = require("@inquirer/prompts");
|
|
6261
|
+
var import_picocolors13 = __toESM(require("picocolors"));
|
|
4812
6262
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
4813
6263
|
var HTTP_PROTOCOL_RE = /^https?:\/\//i;
|
|
4814
6264
|
var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
4815
|
-
function hasProtocol(
|
|
4816
|
-
return HTTP_PROTOCOL_RE.test(
|
|
6265
|
+
function hasProtocol(input2) {
|
|
6266
|
+
return HTTP_PROTOCOL_RE.test(input2);
|
|
4817
6267
|
}
|
|
4818
6268
|
function stripTrailingSlashes(url) {
|
|
4819
6269
|
return url.replace(/\/+$/, "");
|
|
@@ -4837,20 +6287,20 @@ function isInteractive() {
|
|
|
4837
6287
|
}
|
|
4838
6288
|
async function confirmHttpFallback(host) {
|
|
4839
6289
|
process.stderr.write(
|
|
4840
|
-
"\n" +
|
|
4841
|
-
|
|
6290
|
+
"\n" + import_picocolors13.default.red(
|
|
6291
|
+
import_picocolors13.default.bold(
|
|
4842
6292
|
` WARNING: HTTPS unavailable at ${host}.
|
|
4843
6293
|
Falling back to plaintext HTTP will transmit your Kinetica credentials in the clear.
|
|
4844
6294
|
`
|
|
4845
6295
|
)
|
|
4846
|
-
) +
|
|
6296
|
+
) + import_picocolors13.default.dim(
|
|
4847
6297
|
` Set KINETICA_HTTPS_ONLY=1 to refuse this fallback automatically, or pass an explicit http:// prefix to silence this prompt.
|
|
4848
6298
|
|
|
4849
6299
|
`
|
|
4850
6300
|
)
|
|
4851
6301
|
);
|
|
4852
6302
|
try {
|
|
4853
|
-
return await
|
|
6303
|
+
return await confirm({
|
|
4854
6304
|
message: "Continue over plaintext HTTP?",
|
|
4855
6305
|
default: false
|
|
4856
6306
|
});
|
|
@@ -4858,8 +6308,8 @@ async function confirmHttpFallback(host) {
|
|
|
4858
6308
|
return false;
|
|
4859
6309
|
}
|
|
4860
6310
|
}
|
|
4861
|
-
async function resolveUrl(
|
|
4862
|
-
const trimmed =
|
|
6311
|
+
async function resolveUrl(input2, options = {}) {
|
|
6312
|
+
const trimmed = input2.trim();
|
|
4863
6313
|
if (trimmed === "") {
|
|
4864
6314
|
return { ok: false, error: "URL is empty" };
|
|
4865
6315
|
}
|
|
@@ -4876,7 +6326,7 @@ async function resolveUrl(input5) {
|
|
|
4876
6326
|
}
|
|
4877
6327
|
const httpsUrl = `https://${normalized}`;
|
|
4878
6328
|
const httpUrl = `http://${normalized}`;
|
|
4879
|
-
console.error(
|
|
6329
|
+
console.error(import_picocolors13.default.dim("Detecting protocol..."));
|
|
4880
6330
|
if (await probeProtocol(httpsUrl)) {
|
|
4881
6331
|
return { ok: true, url: httpsUrl };
|
|
4882
6332
|
}
|
|
@@ -4892,7 +6342,7 @@ async function resolveUrl(input5) {
|
|
|
4892
6342
|
error: `Could not connect to ${normalized} via https:// or http://`
|
|
4893
6343
|
};
|
|
4894
6344
|
}
|
|
4895
|
-
if (!isInteractive()) {
|
|
6345
|
+
if (options.nonInteractive || !isInteractive()) {
|
|
4896
6346
|
return {
|
|
4897
6347
|
ok: false,
|
|
4898
6348
|
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.`
|
|
@@ -4912,6 +6362,7 @@ async function resolveUrl(input5) {
|
|
|
4912
6362
|
var MAX_RETRIES = 3;
|
|
4913
6363
|
var MAX_REPROMPTS = 2;
|
|
4914
6364
|
var DEFAULT_HM_PORT2 = 9300;
|
|
6365
|
+
var BEST_EFFORT_PROBE_TIMEOUT_MS = 5e3;
|
|
4915
6366
|
function extractVersion(responseBody) {
|
|
4916
6367
|
try {
|
|
4917
6368
|
const outer = JSON.parse(responseBody);
|
|
@@ -4958,11 +6409,29 @@ async function verifyConnectivity(session2) {
|
|
|
4958
6409
|
function isCredentialError(errorMessage) {
|
|
4959
6410
|
return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
|
|
4960
6411
|
}
|
|
6412
|
+
async function connectBestEffort() {
|
|
6413
|
+
const url = process.env.KINETICA_URL;
|
|
6414
|
+
const user = process.env.KINETICA_USER;
|
|
6415
|
+
const pass = process.env.KINETICA_PASS;
|
|
6416
|
+
if (!url || !user || !pass) return void 0;
|
|
6417
|
+
try {
|
|
6418
|
+
const resolved = await resolveUrl(url, { nonInteractive: true });
|
|
6419
|
+
if (!resolved.ok) return void 0;
|
|
6420
|
+
const probe = createSession(resolved.url, user, pass, {
|
|
6421
|
+
timeoutMs: BEST_EFFORT_PROBE_TIMEOUT_MS
|
|
6422
|
+
});
|
|
6423
|
+
const kineticaVersion = await verifyConnectivity(probe);
|
|
6424
|
+
const session2 = createSession(resolved.url, user, pass);
|
|
6425
|
+
return { session: session2, kineticaVersion, degraded: false };
|
|
6426
|
+
} catch {
|
|
6427
|
+
return void 0;
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
4961
6430
|
async function connectWithRetry() {
|
|
4962
6431
|
const { credentials, prompted } = await collectCredentials();
|
|
4963
6432
|
const resolved = await resolveUrl(credentials.url);
|
|
4964
6433
|
if (!resolved.ok) {
|
|
4965
|
-
console.error(
|
|
6434
|
+
console.error(import_picocolors14.default.red(resolved.error));
|
|
4966
6435
|
process.exit(1);
|
|
4967
6436
|
}
|
|
4968
6437
|
const resolvedUrl = resolved.url;
|
|
@@ -4974,17 +6443,17 @@ async function connectWithRetry() {
|
|
|
4974
6443
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
4975
6444
|
try {
|
|
4976
6445
|
const kineticaVersion = await verifyConnectivity(session2);
|
|
4977
|
-
console.error(
|
|
6446
|
+
console.error(import_picocolors14.default.green("Connected to Kinetica successfully."));
|
|
4978
6447
|
if (prompted.size > 0 || wasReprompted) {
|
|
4979
6448
|
await offerSaveCredentials(resolvedUrl, currentUser);
|
|
4980
6449
|
}
|
|
4981
6450
|
return { session: session2, kineticaVersion, degraded: false };
|
|
4982
6451
|
} catch (err) {
|
|
4983
6452
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4984
|
-
console.error(
|
|
6453
|
+
console.error(import_picocolors14.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
|
|
4985
6454
|
if (isCredentialError(msg)) {
|
|
4986
6455
|
if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
|
|
4987
|
-
const shouldRetry = await
|
|
6456
|
+
const shouldRetry = await confirm({
|
|
4988
6457
|
message: "Credentials may be incorrect. Re-enter?",
|
|
4989
6458
|
default: true
|
|
4990
6459
|
});
|
|
@@ -4999,15 +6468,15 @@ async function connectWithRetry() {
|
|
|
4999
6468
|
continue;
|
|
5000
6469
|
}
|
|
5001
6470
|
}
|
|
5002
|
-
console.error(
|
|
6471
|
+
console.error(import_picocolors14.default.red("Authentication failed. Exiting."));
|
|
5003
6472
|
process.exit(1);
|
|
5004
6473
|
}
|
|
5005
6474
|
if (attempt === MAX_RETRIES) {
|
|
5006
|
-
console.error(
|
|
6475
|
+
console.error(import_picocolors14.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
|
|
5007
6476
|
const hmResult = await probeHostManager(session2);
|
|
5008
6477
|
if (hmResult.ok) {
|
|
5009
6478
|
console.error(
|
|
5010
|
-
|
|
6479
|
+
import_picocolors14.default.yellow(
|
|
5011
6480
|
"Connected in DEGRADED MODE (host manager only). Most diagnostic tools will be unavailable."
|
|
5012
6481
|
)
|
|
5013
6482
|
);
|
|
@@ -5016,7 +6485,7 @@ async function connectWithRetry() {
|
|
|
5016
6485
|
}
|
|
5017
6486
|
return { session: session2, kineticaVersion: hmResult.version, degraded: true };
|
|
5018
6487
|
}
|
|
5019
|
-
console.error(
|
|
6488
|
+
console.error(import_picocolors14.default.red("Host manager also unreachable. Exiting."));
|
|
5020
6489
|
process.exit(1);
|
|
5021
6490
|
}
|
|
5022
6491
|
}
|
|
@@ -5027,6 +6496,14 @@ async function connectWithRetry() {
|
|
|
5027
6496
|
// src/cli/index.ts
|
|
5028
6497
|
var verbose = false;
|
|
5029
6498
|
var session;
|
|
6499
|
+
function flagValue(args, name) {
|
|
6500
|
+
const prefix = `${name}=`;
|
|
6501
|
+
const arg = args.find((a) => a.startsWith(prefix));
|
|
6502
|
+
return arg === void 0 ? void 0 : arg.slice(prefix.length);
|
|
6503
|
+
}
|
|
6504
|
+
function chooseBundleSessionVersion(bundleVersion, liveVersion) {
|
|
6505
|
+
return bundleVersion ?? liveVersion;
|
|
6506
|
+
}
|
|
5030
6507
|
function printHelp() {
|
|
5031
6508
|
const lines = [
|
|
5032
6509
|
"",
|
|
@@ -5047,6 +6524,7 @@ function printHelp() {
|
|
|
5047
6524
|
" --logout Log out from Anthropic account and exit",
|
|
5048
6525
|
` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
|
|
5049
6526
|
" --max-budget=USD Per-session budget cap in USD (API-key billing only); default: 5.00",
|
|
6527
|
+
" --bundle=PATH Offline mode: diagnose from an extracted support bundle directory",
|
|
5050
6528
|
"",
|
|
5051
6529
|
" Environment variables:",
|
|
5052
6530
|
" ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
|
|
@@ -5078,12 +6556,9 @@ async function main() {
|
|
|
5078
6556
|
return;
|
|
5079
6557
|
}
|
|
5080
6558
|
const forceLogin = args.includes("--login");
|
|
5081
|
-
const
|
|
5082
|
-
const
|
|
5083
|
-
const
|
|
5084
|
-
const loginOrgUUID = loginOrgArg?.split("=")[1];
|
|
5085
|
-
const modelArg = args.find((a) => a.startsWith("--model="));
|
|
5086
|
-
const modelValue = modelArg?.split("=")[1];
|
|
6559
|
+
const loginMethod = flagValue(args, "--login-method");
|
|
6560
|
+
const loginOrgUUID = flagValue(args, "--login-org");
|
|
6561
|
+
const modelValue = flagValue(args, "--model");
|
|
5087
6562
|
let model;
|
|
5088
6563
|
if (modelValue !== void 0) {
|
|
5089
6564
|
if (SUPPORTED_MODELS.includes(modelValue)) {
|
|
@@ -5091,21 +6566,20 @@ async function main() {
|
|
|
5091
6566
|
} else {
|
|
5092
6567
|
const valid = SUPPORTED_MODELS.join(", ");
|
|
5093
6568
|
process.stderr.write(
|
|
5094
|
-
|
|
6569
|
+
import_picocolors15.default.red(`Error: unknown --model value "${modelValue}". Valid models: ${valid}
|
|
5095
6570
|
`)
|
|
5096
6571
|
);
|
|
5097
6572
|
process.exitCode = 1;
|
|
5098
6573
|
return;
|
|
5099
6574
|
}
|
|
5100
6575
|
}
|
|
5101
|
-
const
|
|
5102
|
-
const budgetValue = budgetArg?.split("=")[1];
|
|
6576
|
+
const budgetValue = flagValue(args, "--max-budget");
|
|
5103
6577
|
let maxBudgetFlag;
|
|
5104
6578
|
if (budgetValue !== void 0) {
|
|
5105
6579
|
const parsed = Number(budgetValue);
|
|
5106
6580
|
if (!isValidBudget(parsed)) {
|
|
5107
6581
|
process.stderr.write(
|
|
5108
|
-
|
|
6582
|
+
import_picocolors15.default.red(
|
|
5109
6583
|
`Error: invalid --max-budget value "${budgetValue}". Use a positive number, e.g. --max-budget=10
|
|
5110
6584
|
`
|
|
5111
6585
|
)
|
|
@@ -5115,23 +6589,74 @@ async function main() {
|
|
|
5115
6589
|
}
|
|
5116
6590
|
maxBudgetFlag = parsed;
|
|
5117
6591
|
}
|
|
6592
|
+
const bundlePath = flagValue(args, "--bundle");
|
|
6593
|
+
if (bundlePath?.trim() === "") {
|
|
6594
|
+
process.stderr.write(
|
|
6595
|
+
import_picocolors15.default.red(
|
|
6596
|
+
"Error: --bundle requires a directory path, e.g. --bundle=/path/to/extracted-bundle\n"
|
|
6597
|
+
)
|
|
6598
|
+
);
|
|
6599
|
+
process.exitCode = 1;
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
5118
6602
|
loadEnvFile();
|
|
5119
6603
|
printBanner();
|
|
5120
6604
|
if (model === void 0 && process.stdin.isTTY) {
|
|
5121
6605
|
model = await selectModel();
|
|
5122
6606
|
}
|
|
5123
6607
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
5124
|
-
process.stderr.write(
|
|
6608
|
+
process.stderr.write(import_picocolors15.default.dim(`Model: ${effectiveModel}
|
|
5125
6609
|
`));
|
|
5126
6610
|
const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
|
|
5127
6611
|
if (authResult.method === "oauth") {
|
|
5128
6612
|
const acctInfo = authResult.email ? ` (${authResult.email})` : "";
|
|
5129
|
-
process.stderr.write(
|
|
6613
|
+
process.stderr.write(import_picocolors15.default.dim(`Authenticated via OAuth${acctInfo}
|
|
5130
6614
|
`));
|
|
5131
6615
|
} else {
|
|
5132
|
-
process.stderr.write(
|
|
6616
|
+
process.stderr.write(import_picocolors15.default.dim("Authenticated via API key\n"));
|
|
5133
6617
|
}
|
|
5134
6618
|
const maxBudgetUsd = resolveMaxBudgetUsd(maxBudgetFlag);
|
|
6619
|
+
if (bundlePath !== void 0) {
|
|
6620
|
+
const result = await verifyBundle(bundlePath);
|
|
6621
|
+
if (!result.ok) {
|
|
6622
|
+
process.stderr.write(import_picocolors15.default.red(`Error: ${result.error}
|
|
6623
|
+
`));
|
|
6624
|
+
process.exitCode = 1;
|
|
6625
|
+
return;
|
|
6626
|
+
}
|
|
6627
|
+
if (result.missingExpected.length > 0) {
|
|
6628
|
+
process.stderr.write(
|
|
6629
|
+
import_picocolors15.default.yellow(
|
|
6630
|
+
`Warning: bundle is missing expected artifact(s): ${result.missingExpected.join(", ")}. Diagnosing with what is present.
|
|
6631
|
+
`
|
|
6632
|
+
)
|
|
6633
|
+
);
|
|
6634
|
+
}
|
|
6635
|
+
const live = await connectBestEffort();
|
|
6636
|
+
process.stderr.write(
|
|
6637
|
+
live ? import_picocolors15.default.dim("Live connection available \u2014 bundle + live verification enabled.\n") : import_picocolors15.default.dim("No reachable live connection \u2014 offline bundle analysis only.\n")
|
|
6638
|
+
);
|
|
6639
|
+
if (live?.kineticaVersion && result.kineticaVersion && live.kineticaVersion !== result.kineticaVersion) {
|
|
6640
|
+
process.stderr.write(
|
|
6641
|
+
import_picocolors15.default.yellow(
|
|
6642
|
+
`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.
|
|
6643
|
+
`
|
|
6644
|
+
)
|
|
6645
|
+
);
|
|
6646
|
+
}
|
|
6647
|
+
await runAgent(
|
|
6648
|
+
live?.session,
|
|
6649
|
+
chooseBundleSessionVersion(result.kineticaVersion, live?.kineticaVersion),
|
|
6650
|
+
false,
|
|
6651
|
+
model,
|
|
6652
|
+
{
|
|
6653
|
+
authMethod: authResult.method,
|
|
6654
|
+
maxBudgetUsd,
|
|
6655
|
+
bundleSource: result.bundleSource
|
|
6656
|
+
}
|
|
6657
|
+
);
|
|
6658
|
+
return;
|
|
6659
|
+
}
|
|
5135
6660
|
const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
|
|
5136
6661
|
session = connectedSession;
|
|
5137
6662
|
await runAgent(session, kineticaVersion, degraded, model, {
|
|
@@ -5145,7 +6670,7 @@ function getSession() {
|
|
|
5145
6670
|
if (process.env.NODE_ENV !== "test") {
|
|
5146
6671
|
main().catch((err) => {
|
|
5147
6672
|
const message = err instanceof Error ? err.message : String(err);
|
|
5148
|
-
process.stderr.write(
|
|
6673
|
+
process.stderr.write(import_picocolors15.default.red(`Error: ${message}
|
|
5149
6674
|
`));
|
|
5150
6675
|
if (verbose && err instanceof Error && err.stack) {
|
|
5151
6676
|
process.stderr.write(err.stack + "\n");
|
|
@@ -5155,6 +6680,7 @@ if (process.env.NODE_ENV !== "test") {
|
|
|
5155
6680
|
}
|
|
5156
6681
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5157
6682
|
0 && (module.exports = {
|
|
6683
|
+
chooseBundleSessionVersion,
|
|
5158
6684
|
getSession,
|
|
5159
6685
|
main,
|
|
5160
6686
|
verbose
|